Add hover documentation tooltips for frontmatter keys in Playground#16785
Add hover documentation tooltips for frontmatter keys in Playground#16785
Conversation
…ditor When hovering over a YAML frontmatter key (e.g., engine, permissions, safe-outputs), the editor now shows a tooltip with the key's description, type, allowed values (enum), and available sub-keys from the schema. New files: - hover-tooltips.js: CodeMirror hoverTooltip extension that detects frontmatter boundaries, resolves nested key paths via indentation, and looks up schema entries from autocomplete-data.json - autocomplete-data.json: Compact schema data generated from the main workflow JSON Schema (generated by generate-autocomplete-data.js) - generate-autocomplete-data.js: Build script that extracts key descriptions, types, enums, and hierarchy from the JSON Schema Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds schema-driven hover tooltips in the Playground editor for YAML frontmatter keys, backed by a generated compact schema lookup file.
Changes:
- Introduces a CodeMirror
hoverTooltipextension to show key docs (type/description/enum/child keys) only within the frontmatter block. - Adds a build script to generate
autocomplete-data.jsonfrom the main workflow JSON Schema. - Adds tooltip styling and wires the extension into the Playground editor.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/scripts/generate-autocomplete-data.js | Generates the compact schema-derived data consumed by the editor tooltip feature. |
| docs/public/editor/hover-tooltips.js | Implements frontmatter detection, key-path resolution, schema lookup, and tooltip rendering. |
| docs/public/editor/editor.js | Registers the new hover tooltip extension with the editor instance. |
| docs/public/editor/index.html | Adds CSS styles for the tooltip UI. |
| docs/public/editor/autocomplete-data.json | Generated schema data used at runtime for tooltip content. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
docs/public/editor/hover-tooltips.js
Outdated
| const text = doc.toString(); | ||
| // Frontmatter must start at the very beginning of the document | ||
| if (!text.startsWith('---')) return null; | ||
|
|
||
| // Find the closing --- on its own line | ||
| // Search for \n--- followed by end-of-string, newline, or whitespace | ||
| const match = text.match(/\n---[ \t]*(\n|$)/); | ||
| if (!match || match.index <= 2) return null; | ||
|
|
||
| // The frontmatter region spans from character 0 to the end of the closing --- | ||
| return { | ||
| start: 0, | ||
| end: match.index + match[0].length, | ||
| }; |
There was a problem hiding this comment.
findFrontmatterRegion calls doc.toString() and runs a regex over the entire document on every hover event. For larger docs this can become noticeable since hover tooltip handlers run frequently. Consider scanning line-by-line from the start until the closing --- (or caching the computed region in editor state / recalculating only on doc changes).
| const text = doc.toString(); | |
| // Frontmatter must start at the very beginning of the document | |
| if (!text.startsWith('---')) return null; | |
| // Find the closing --- on its own line | |
| // Search for \n--- followed by end-of-string, newline, or whitespace | |
| const match = text.match(/\n---[ \t]*(\n|$)/); | |
| if (!match || match.index <= 2) return null; | |
| // The frontmatter region spans from character 0 to the end of the closing --- | |
| return { | |
| start: 0, | |
| end: match.index + match[0].length, | |
| }; | |
| // Frontmatter must start at the very beginning of the document. | |
| // We check the first line rather than converting the entire | |
| // document to a string for performance reasons. | |
| if (doc.lines === 0) return null; | |
| const firstLine = doc.line(1); | |
| if (!firstLine.text.startsWith('---')) return null; | |
| // Scan forward line-by-line to find the closing --- on its own line. | |
| // A closing line looks like: "---" followed by optional spaces/tabs. | |
| for (let lineNumber = 2; lineNumber <= doc.lines; lineNumber++) { | |
| const line = doc.line(lineNumber); | |
| if (/^---[ \t]*$/.test(line.text)) { | |
| // The frontmatter region spans from character 0 to the end of | |
| // the closing --- line. | |
| return { | |
| start: 0, | |
| end: line.to, | |
| }; | |
| } | |
| } | |
| // No valid closing delimiter found. | |
| return null; |
docs/public/editor/hover-tooltips.js
Outdated
| const keyEnd = indent + key.length; | ||
|
|
||
| // Only trigger if the hover position is within the key text | ||
| if (posInLine < keyStart || posInLine > keyEnd) return null; |
There was a problem hiding this comment.
The hover hit-test includes posInLine === keyEnd, which means hovering the colon/space immediately after a key can still trigger a tooltip. Since the tooltip should only appear when hovering over the key text, change the bound check to treat keyEnd as exclusive (e.g., posInLine >= keyEnd should return null).
| if (posInLine < keyStart || posInLine > keyEnd) return null; | |
| if (posInLine < keyStart || posInLine >= keyEnd) return null; |
| /* Override CodeMirror tooltip container styling to avoid clashing backgrounds */ | ||
| .cm-tooltip:has(.cm-tooltip-docs) { | ||
| background: transparent !important; | ||
| border: none !important; | ||
| padding: 0 !important; | ||
| } |
There was a problem hiding this comment.
This relies on the CSS :has() selector to target the CodeMirror tooltip container. In environments without :has() support, the container styling won’t be overridden and the tooltip may render with clashing backgrounds/borders. Consider avoiding :has() here (e.g., target .cm-tooltip.cm-tooltip-hover if acceptable, or apply a dedicated class via the tooltip API if supported) to improve cross-browser robustness.
| * Input: pkg/parser/schemas/main_workflow_schema.json (~322KB, 7200 lines) | ||
| * Output: docs/public/editor/autocomplete-data.json (~20KB compact) |
There was a problem hiding this comment.
The header comment claims the generated output is "(~20KB compact)", but the committed autocomplete-data.json is significantly larger (and the PR description says ~57KB). Please update this comment to reflect the current output size (or remove the size estimate) so it doesn’t become misleading as the schema evolves.
| * Input: pkg/parser/schemas/main_workflow_schema.json (~322KB, 7200 lines) | |
| * Output: docs/public/editor/autocomplete-data.json (~20KB compact) | |
| * Input: pkg/parser/schemas/main_workflow_schema.json | |
| * Output: docs/public/editor/autocomplete-data.json |
| const enumVals = getEnum(resolved); | ||
| if (enumVals) entry.enum = enumVals.filter(v => v != null); | ||
|
|
||
| if (type === 'boolean' && !entry.enum) { | ||
| entry.enum = [true, false]; | ||
| } | ||
|
|
There was a problem hiding this comment.
buildEntry writes entry.enum even when the resulting enum array is empty (after filtering nulls). This produces "enum": [] entries in the generated JSON, which adds noise and size without being useful (the tooltip UI ignores empty enums). Consider only setting entry.enum when the filtered list has at least one value.
| const resolved = resolveRef(node); | ||
| const type = resolved.type; | ||
| if (type === 'string' || type === 'integer' || type === 'number' || type === 'boolean') return true; | ||
| if (Array.isArray(type) && type.some(t => t === 'string' || t === 'integer' || t === 'number')) return true; |
There was a problem hiding this comment.
canBeLeaf treats array-typed schemas as leaves only if the union includes string/integer/number, but it omits boolean. The main schema contains nodes like type: ["boolean", "null"], so these won’t be marked as leaf even though they’re scalar. Include boolean in this check to keep the generated leaf metadata accurate.
| if (Array.isArray(type) && type.some(t => t === 'string' || t === 'integer' || t === 'number')) return true; | |
| if (Array.isArray(type) && type.some(t => t === 'string' || t === 'integer' || t === 'number' || t === 'boolean')) return true; |
|
Hey Process Issue This repository requires contributions to follow the agentic workflow process outlined in CONTRIBUTING.md. Traditional pull requests are not enabled — instead, contributors should:
Since this PR was created directly (via Claude Code), it bypasses the required process. The project uses agentic workflows to build agentic workflows (dogfooding). Missing Tests While the PR includes a manual test plan, there are no automated tests for the frontmatter detection logic, key path resolution, schema lookup, or edge cases. Next Steps As a COLLABORATOR, you may have special permissions, but if you'd like to align with the project's contribution model, consider creating an issue with an agentic plan and letting the agent create the PR. If you'd prefer to keep this PR open for expedited review given your collaborator status, please coordinate with maintainers directly.
|
- Replace doc.toString() with line-by-line scanning in findFrontmatterRegion for performance - Make key boundary check exclusive so hovering the colon doesn't trigger tooltip - Replace CSS :has() selector with .cm-tooltip-hover for cross-browser compatibility - Remove stale size estimates from generate-autocomplete-data.js header comment - Only emit enum arrays when non-empty to reduce JSON noise - Include boolean in canBeLeaf array type check for accurate leaf detection - Regenerate autocomplete-data.json with fixes applied Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The responsive CSS media query and mobile touch/divider logic was breaking the side-by-side layout on laptop browsers. Reverts the mobile-specific code: @media breakpoint, isMobile divider branching, and touch event handlers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolves merge conflicts from main's mobile UI additions (tab bar, FAB compile button, swipe gestures, responsive media queries). Removes all mobile-specific code to keep the desktop side-by-side editor layout working correctly on all screen sizes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
engine,permissions, orsafe-outputs, a tooltip appears with the key's description, type, allowed values (enum), and available sub-keys---delimiters), not in the markdown bodytoolsetsinsidetools.githublooks up the correct nested schema entry)autocomplete-data.json, which is generated from the main workflow JSON Schema via a new build script (docs/scripts/generate-autocomplete-data.js)New files
docs/public/editor/hover-tooltips.jshoverTooltipextension with frontmatter detection, key path resolution, schema lookup, and tooltip DOM constructiondocs/public/editor/autocomplete-data.jsondocs/scripts/generate-autocomplete-data.jspkg/parser/schemas/main_workflow_schema.jsonModified files
docs/public/editor/editor.jsfrontmatterHoverTooltipextensiondocs/public/editor/index.htmlTest plan
engine:-- should show description and enum values (copilot, claude, codex)on:-- should show trigger description and child keyspermissions:-- should show permissions descriptiontoolsetsundertools.github-- should resolve correctly---) -- should NOT show any tooltip🤖 Generated with Claude Code