Add hidden block directive to validate partial code snippets in docs#600
Add hidden block directive to validate partial code snippets in docs#600
Conversation
directive pair that lets authors include a full compilable code block that is validated but not rendered in docs, while the visible snippet that follows is automatically skipped from validation. Convert 4 skipped samples in getting-started.md to use hidden blocks: - Python event subscription example - Go event subscription example - C# event subscription example - Go CLI server connection example This increases validated code blocks from 182 to 186 and demonstrates the hidden block pattern for future conversions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new docs-validate: hidden directive to the docs-validation extractor so docs can include a “full, compilable” hidden block while keeping a shorter visible snippet, and updates getting-started.md to use this pattern in several sections.
Changes:
- Introduce
<!-- docs-validate: hidden --> ... <!-- /docs-validate: hidden -->handling inextract.ts, including reporting aHiddencount in extraction output. - Update validator CLI output to document the new hidden directive usage.
- Convert 4
getting-started.mdexamples fromskipto the new hidden+visible snippet pattern.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| scripts/docs-validation/validate.ts | Documents the new docs-validate: hidden directive in validation failure help output. |
| scripts/docs-validation/extract.ts | Implements hidden-block parsing, auto-skips the next visible code fence, and reports hidden-block counts. |
| docs/getting-started.md | Converts several examples to hidden “full code” blocks with a visible snippet immediately after. |
Comments suppressed due to low confidence (2)
scripts/docs-validation/extract.ts:117
skipNextis not reset wheninHiddenBlockis true, which can cause skip state to leak across multiple code fences inside a hidden section (e.g., a single<!-- docs-validate: skip -->inside a hidden block would skip all subsequent hidden fences). It can also incorrectly skip the first fence of a following hidden section if hidden blocks appear back-to-back. Suggest restoring per-block reset ofskipNextand using a dedicated flag for “skip next visible block after hidden closes”, or explicitly clearingskipNexton<!-- docs-validate: hidden -->.
// Only reset skipNext when NOT in a hidden block — hidden blocks
// can contain multiple code fences that all get validated.
if (!inHiddenBlock) {
skipNext = false;
}
docs/getting-started.md:492
- The hidden Go “full compilable code” example constructs a session via
session := &copilot.Session{}. While this may compile, it isn’t a valid/representative way to obtain a usable session (sessions are created viaclient.CreateSession(...)). Since hidden blocks are meant to show the complete runnable version, consider creating a client, starting it, creating a session, and deferringsession.Destroy()/client.Stop()in this block.
func main() {
session := &copilot.Session{}
// Subscribe to all events
unsubscribe := session.On(func(event copilot.SessionEvent) {
fmt.Println("Event:", event.Type)
| // Use the client normally | ||
| _, _ = client.CreateSession(ctx, &copilot.SessionConfig{ | ||
| OnPermissionRequest: copilot.PermissionHandler.ApproveAll, | ||
| }) |
There was a problem hiding this comment.
In the hidden Go external-server example, the session creation result and error are discarded (_, _ = client.CreateSession(...)). For a “complete” example, it should at least check the error and (if it returns a session) destroy it, otherwise readers may copy/paste a pattern that silently ignores failures/leaks resources.
This issue also appears on line 487 of the same file.
| // Check for validation directives | ||
| if (line.includes("<!-- docs-validate: skip -->")) { | ||
| skipNext = true; | ||
| continue; | ||
| } | ||
| if (line.includes("<!-- docs-validate: wrap-async -->")) { | ||
| wrapAsync = true; | ||
| continue; | ||
| } | ||
| if (line.includes("<!-- docs-validate: hidden -->")) { | ||
| inHiddenBlock = true; | ||
| continue; | ||
| } | ||
| if (line.includes("<!-- /docs-validate: hidden -->")) { | ||
| inHiddenBlock = false; | ||
| // Skip the next visible code block since the hidden one replaces it | ||
| skipNext = true; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Directive parsing runs even while inside a fenced code block. If a code snippet contains the string <!-- docs-validate: ... -->, it will toggle skip/hidden state mid-block and corrupt extraction. Consider only processing docs-validate directives when !inCodeBlock (or otherwise ensure directives are ignored while inside code fences).
This issue also appears on line 113 of the same file.
See below for a potential fix:
// Check for validation directives (only when not inside a code block)
if (!inCodeBlock) {
if (line.includes("<!-- docs-validate: skip -->")) {
skipNext = true;
continue;
}
if (line.includes("<!-- docs-validate: wrap-async -->")) {
wrapAsync = true;
continue;
}
if (line.includes("<!-- docs-validate: hidden -->")) {
inHiddenBlock = true;
continue;
}
if (line.includes("<!-- /docs-validate: hidden -->")) {
inHiddenBlock = false;
// Skip the next visible code block since the hidden one replaces it
skipNext = true;
continue;
}
}
| from copilot import CopilotClient | ||
| from copilot.generated.session_events import SessionEvent, SessionEventType | ||
|
|
||
| client = CopilotClient() | ||
|
|
||
| session = client.create_session({"on_permission_request": lambda req, inv: {"kind": "approved"}}) | ||
|
|
||
| # Subscribe to all events | ||
| unsubscribe = session.on(lambda event: print(f"Event: {event.type}")) | ||
|
|
||
| # Filter by event type in your handler | ||
| def handle_event(event: SessionEvent) -> None: | ||
| if event.type == SessionEventType.SESSION_IDLE: | ||
| print("Session is idle") | ||
| elif event.type == SessionEventType.ASSISTANT_MESSAGE: | ||
| print(f"Message: {event.data.content}") | ||
|
|
||
| unsubscribe = session.on(handle_event) | ||
|
|
||
| # Later, to unsubscribe: | ||
| unsubscribe() |
There was a problem hiding this comment.
This hidden “full compilable code” Python example calls client.create_session(...) without await, but CopilotClient.create_session is async in the SDK. To keep the hidden block accurate and actually runnable, wrap it in an async main() and use await client.start(), session = await client.create_session(...), and await client.stop() (similar to earlier Python samples in this doc).
| from copilot import CopilotClient | |
| from copilot.generated.session_events import SessionEvent, SessionEventType | |
| client = CopilotClient() | |
| session = client.create_session({"on_permission_request": lambda req, inv: {"kind": "approved"}}) | |
| # Subscribe to all events | |
| unsubscribe = session.on(lambda event: print(f"Event: {event.type}")) | |
| # Filter by event type in your handler | |
| def handle_event(event: SessionEvent) -> None: | |
| if event.type == SessionEventType.SESSION_IDLE: | |
| print("Session is idle") | |
| elif event.type == SessionEventType.ASSISTANT_MESSAGE: | |
| print(f"Message: {event.data.content}") | |
| unsubscribe = session.on(handle_event) | |
| # Later, to unsubscribe: | |
| unsubscribe() | |
| import asyncio | |
| from copilot import CopilotClient | |
| from copilot.generated.session_events import SessionEvent, SessionEventType | |
| async def main() -> None: | |
| client = CopilotClient() | |
| await client.start() | |
| session = await client.create_session( | |
| {"on_permission_request": lambda req, inv: {"kind": "approved"}} | |
| ) | |
| # Subscribe to all events | |
| unsubscribe = session.on(lambda event: print(f"Event: {event.type}")) | |
| # Filter by event type in your handler | |
| def handle_event(event: SessionEvent) -> None: | |
| if event.type == SessionEventType.SESSION_IDLE: | |
| print("Session is idle") | |
| elif event.type == SessionEventType.ASSISTANT_MESSAGE: | |
| print(f"Message: {event.data.content}") | |
| unsubscribe = session.on(handle_event) | |
| # Later, to unsubscribe: | |
| unsubscribe() | |
| await client.stop() | |
| if __name__ == "__main__": | |
| asyncio.run(main()) |
Summary
Changes
New directive in
extract.ts:skipneeded)HiddencountConverted 4 samples in
getting-started.md:Impact
Usage
The hidden block compiles in CI; readers see only the clean snippet.