docs: add Control Center Draft Mode guide (#942)

* docs: add Control Center Draft Mode guide

Add a how-to page for the new Draft Mode in Control Center: build a
change on a working copy of the canvas, review the exact API requests,
and deploy everything as one batch. A single running example (giving
DevOps HTTPS access to a not-yet-installed staging server) carries
through entering a draft, the canvas toolbar, node interactions,
placeholder-peer installs, and Review & Deploy.

Along the way:
- Nest Control Center in the sidebar (Overview + Draft Mode) and update
  the overview page: Users view in the intro and quick start, an Edit
  Nodes section covering live edits vs Draft Mode, permissions notes
  including the Network Admin setup-key limitation, and a HashRedirect
  for the renamed #editing-policies-from-the-graph anchor.
- Add a shared <Video> component for screen recordings: lazy playback
  via IntersectionObserver, visible controls, preload="metadata", and
  no autoplay under prefers-reduced-motion.
- Optimize media: re-encode recordings (H.264 CRF 26, 30 fps,
  faststart, audio stripped) and losslessly recompress screenshots,
  cutting the page's media payload from 6.3 MB to 1.2 MB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: image zoom overlay flickering on close

The closing fade-out ran without animation-fill-mode: forwards, so when
the 200ms animation finished the overlay snapped back to full opacity
until React's unmount timeout fired, flashing for a frame or two.
Holding the animation end state covers that gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: tighten Draft Mode intro

Give the running example its own paragraph and drop the header-chrome
description; the video right below it shows the same thing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: add Focus Mode section and group-from-selection video

Document Focus Mode on the Control Center overview (right-click a node
and choose Focus, or select it and press F) with two screenshots, and
move the F shortcut prose there from the Draft Mode page. Add a
recording of creating a group from a multi-peer selection to the
Draft Mode page. New media compressed like the rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: correct Focus Mode shortcut order

F is pressed first, then the node is selected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: drop setup-key caveat from Draft Mode permissions note

The Network Admin limitation is already covered where it bites, in the
placeholder install section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: drop Agent Network disambiguation from Add Nodes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: reorder Assign Peers to Groups videos

Show the drag-to-group recording right after the text it illustrates,
then the group-from-selection flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: remove change-type badge list from review section

The review rows do not carry Add/Modify/Delete/Install badges.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: trim group-membership parenthetical from review example

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: react to prefers-reduced-motion changes in Video

The reduced-motion check ran once on mount, so toggling the OS setting
while the page was open either kept videos auto-playing or left them
permanently inert. Listen for MediaQueryList changes: pause and drop
the observer when reduced motion turns on, re-observe when it turns
off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eduard Gert
2026-09-04 06:12:55 -07:00
committed by GitHub
co-authored by Claude Fable 5
parent af75bc95ec
commit e246829450
19 changed files with 366 additions and 20 deletions
+8 -1
View File
@@ -81,7 +81,14 @@ export const docsNavigation = [
{
title: 'MANAGE NETBIRD',
links: [
{ title: 'Control Center', href: '/manage/control-center' },
{
title: 'Control Center',
isOpen: false,
links: [
{ title: 'Overview', href: '/manage/control-center' },
{ title: 'Draft Mode', href: '/manage/control-center/draft-mode' },
],
},
{
title: 'Peers',
isOpen: false,
+74
View File
@@ -0,0 +1,74 @@
import { useEffect, useRef } from 'react'
import clsx from 'clsx'
/**
* Looping screen-recording embed for docs pages.
*
* Recordings load lazily and play only while on screen: `preload="metadata"`
* avoids buffering full files up front, and an IntersectionObserver starts
* playback when the video scrolls into view and pauses it when it leaves.
* Controls stay visible so the loop can be paused (WCAG 2.2.2), and autoplay
* is skipped entirely for users who prefer reduced motion.
*
* Usage:
* <Video src="/docs-static/img/manage/example.mp4" label="What the recording shows" />
*/
export function Video({ src, label, className, ...props }) {
const ref = useRef(null)
useEffect(() => {
const video = ref.current
if (!video) return
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')
let observer = null
const observe = () => {
observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
video.play().catch(() => {})
} else {
video.pause()
}
},
{ threshold: 0.25 }
)
observer.observe(video)
}
// React to the OS setting changing while the page is open.
const handleMotionChange = () => {
if (reducedMotion.matches) {
observer?.disconnect()
observer = null
video.pause()
} else if (!observer) {
observe()
}
}
if (!reducedMotion.matches) observe()
reducedMotion.addEventListener('change', handleMotionChange)
return () => {
reducedMotion.removeEventListener('change', handleMotionChange)
observer?.disconnect()
}
}, [])
return (
<video
ref={ref}
src={src}
loop
muted
playsInline
controls
preload="metadata"
aria-label={label}
className={clsx('imagewrapper-big', className)}
{...props}
/>
)
}
+1
View File
@@ -9,6 +9,7 @@ export { Button } from '@/components/Button'
export { CodeGroup, Code as code, Pre as pre } from '@/components/Code'
export { Badge } from '@/components/Badge'
export { YouTube }
export { Video } from '@/components/Video'
export const h2 = function H2(props) {
return <Heading level={2} {...props} />