Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 89 additions & 3 deletions apps/skit/src/bin/gen-docs-reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ fn main() -> Result<()> {
}

// Index page for built-in nodes.
let nodes_index = render_nodes_index(&built_in_nodes);
write_if_changed(&nodes_dir.join("index.md"), &nodes_index)?;
let nodes_index_path = nodes_dir.join("index.md");
let nodes_index = render_nodes_index(&built_in_nodes, &nodes_index_path);
write_if_changed(&nodes_index_path, &nodes_index)?;

// --- Official native plugins (from repo artifacts) ---
let mut official_plugins = load_official_native_plugins(&repo_root)?;
Expand Down Expand Up @@ -617,7 +618,9 @@ In oneshot pipelines it is resolved during compilation. In dynamic pipelines it
}
}

fn render_nodes_index(defs: &[NodeDefinition]) -> String {
const MANUAL_CONTENT_MARKER: &str = "<!-- manual-content-below -->";

fn render_nodes_index(defs: &[NodeDefinition], existing_path: &Path) -> String {
let mut by_namespace: BTreeMap<String, Vec<&NodeDefinition>> = BTreeMap::new();
for def in defs {
let ns = def.kind.split("::").next().unwrap_or("unknown").to_string();
Expand Down Expand Up @@ -657,9 +660,26 @@ Notes:
}
}

out.push_str(&format!("\n{MANUAL_CONTENT_MARKER}\n"));

if let Some(manual) = read_manual_content(existing_path) {
out.push_str(&manual);
Comment on lines +663 to +666
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Manual node-index content now depends on the marker being present

The generator now preserves hand-written node-index content only by slicing the existing file after <!-- manual-content-below -->. This means future manual additions above the marker will still be regenerated away, and if the marker is accidentally removed in a future edit the manual section will be dropped on the next generation. That behavior appears intentional for a generated page rather than a bug in this PR, but reviewers should keep the marker placement in mind when editing docs/src/content/docs/reference/nodes/index.md.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

}

out
}

fn read_manual_content(path: &Path) -> Option<String> {
let existing = fs::read_to_string(path).ok()?;
let marker_pos = existing.find(MANUAL_CONTENT_MARKER)?;
let after_marker = &existing[marker_pos + MANUAL_CONTENT_MARKER.len()..];
let content = after_marker.strip_prefix('\n').unwrap_or(after_marker);
if content.is_empty() {
return None;
}
Some(content.to_string())
}

fn render_plugins_index(defs: &[PluginNodeDoc]) -> String {
let mut out = String::new();
out.push_str(
Expand Down Expand Up @@ -1458,4 +1478,70 @@ fn render_config_page(schema: &Value, defaults: &Value) -> Result<String> {
Ok(out)
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)] // Tests use infallible tempfile/write helpers; panicking on failure is appropriate.
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;

#[test]
fn read_manual_content_preserves_text_below_marker() {
let mut f = NamedTempFile::new().unwrap();
writeln!(f, "generated stuff\n{MANUAL_CONTENT_MARKER}\n## Hand-written\nKeep me.").unwrap();
let result = read_manual_content(f.path()).unwrap();
assert!(result.contains("## Hand-written"));
assert!(result.contains("Keep me."));
}

#[test]
fn read_manual_content_returns_none_when_no_marker() {
let mut f = NamedTempFile::new().unwrap();
writeln!(f, "generated stuff only").unwrap();
assert!(read_manual_content(f.path()).is_none());
}

#[test]
fn read_manual_content_returns_none_for_missing_file() {
assert!(read_manual_content(Path::new("/does/not/exist")).is_none());
}

#[test]
fn read_manual_content_returns_none_when_nothing_after_marker() {
let mut f = NamedTempFile::new().unwrap();
write!(f, "stuff\n{MANUAL_CONTENT_MARKER}\n").unwrap();
assert!(read_manual_content(f.path()).is_none());
}

#[test]
fn render_nodes_index_embeds_marker_and_preserves_manual_section() {
let mut f = NamedTempFile::with_suffix(".md").unwrap();
writeln!(f, "old generated\n{MANUAL_CONTENT_MARKER}\n## Custom\nHello").unwrap();

let defs = vec![NodeDefinition {
kind: "test::dummy".to_string(),
description: None,
param_schema: serde_json::json!({}),
inputs: vec![],
outputs: vec![],
categories: vec![],
bidirectional: false,
}];

let result = render_nodes_index(&defs, f.path());
assert!(result.contains(MANUAL_CONTENT_MARKER));
assert!(result.contains("## Custom"));
assert!(result.contains("Hello"));
assert!(result.contains("[`test::dummy`]"));
}

#[test]
fn config_schema_includes_mcp_section() {
let schema = serde_json::to_value(schemars::schema_for!(Config))
.expect("failed to generate Config schema");
let props = schema.get("properties").and_then(serde_json::Value::as_object).unwrap();
assert!(props.contains_key("mcp"), "Config schema must include the mcp section");
}
}

// REUSE-IgnoreEnd
2,294 changes: 1,208 additions & 1,086 deletions docs/src/content/docs/reference/configuration-generated.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ No parameters.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"title": "FlacDecoderConfig",
"type": "object"
}
Expand Down
15 changes: 8 additions & 7 deletions docs/src/content/docs/reference/nodes/audio-gain.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,20 @@ Adjusts audio volume by applying a linear gain multiplier to all samples. Suppor
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "AudioGainConfig",
"additionalProperties": false,
"description": "The configuration struct for the AudioGainNode.",
"type": "object",
"properties": {
"gain": {
"description": "A linear multiplier for the audio amplitude (e.g., 0.5 is -6dB).\nThis parameter can be updated in real-time while the node is running.\nValid range: 0.0 to 4.0",
"type": "number",
"default": 1.0,
"minimum": 0.0,
"description": "A linear multiplier for the audio amplitude (e.g., 0.5 is -6dB).\nThis parameter can be updated in real-time while the node is running.\nValid range: 0.0 to 4.0",
"maximum": 4.0,
"tunable": true
"minimum": 0.0,
"tunable": true,
"type": "number"
}
}
},
"title": "AudioGainConfig",
"type": "object"
}
```

Expand Down
112 changes: 57 additions & 55 deletions docs/src/content/docs/reference/nodes/audio-mixer.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,76 +33,78 @@ Combines multiple audio streams into a single output by summing samples. Support

```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "AudioMixerConfig",
"description": "Configuration for the AudioMixerNode.",
"type": "object",
"properties": {
"sync_timeout_ms": {
"description": "Timeout in milliseconds for waiting for slow inputs.\nIf specified, the mixer will wait up to this duration for all active pins to provide frames.\nIf timeout expires, missing pins will be mixed as silence.\nIf not specified (None), the mixer will wait indefinitely (strict broadcast synchronization).\nDefault: Some(100)",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0,
"default": 100
},
"num_inputs": {
"description": "Number of input pins to pre-create.\nRequired for stateless/oneshot pipelines where pins must exist before graph building.\nOptional for dynamic pipelines where pins are created on-demand.\nIf specified, pins will be named in_0, in_1, ..., in_{N-1}.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0,
"default": null
},
"clocked": {
"description": "Enable clocked mixing mode (dedicated mixing thread + per-input jitter buffers).\n\nWhen enabled, the mixer emits frames on a fixed cadence determined by\n`sample_rate` and `frame_samples_per_channel`.",
"anyOf": [
{
"$ref": "#/$defs/ClockedMixerConfig"
},
{
"type": "null"
}
]
}
},
"$defs": {
"ClockedMixerConfig": {
"type": "object",
"additionalProperties": false,
"properties": {
"sample_rate": {
"description": "Output sample rate (Hz). Inputs are expected to already match this.",
"type": "integer",
"format": "uint32",
"minimum": 0,
"default": 48000
},
"frame_samples_per_channel": {
"default": 960,
"description": "Fixed frame size (samples per channel) for the clocked mixer.\n\nExample: `960` @ `48000` Hz => 20ms frames.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 960
"type": "integer"
},
"generate_silence": {
"default": true,
"description": "If true, emit silence frames on ticks even when no inputs have data.\n\nIf false, the clocked mixer only emits output on ticks where at least one input\ncontributes a frame.",
"type": "boolean"
},
"jitter_buffer_frames": {
"default": 3,
"description": "Per-input jitter buffer depth (in frames).\n\nFrames are queued in order. When full, the oldest frame is dropped (overwrite-oldest).\n\nRecommended: 2-3 for ~40-60ms jitter tolerance at 20ms frames.",
"type": "integer",
"format": "uint",
"minimum": 0,
"default": 3
"type": "integer"
},
"generate_silence": {
"description": "If true, emit silence frames on ticks even when no inputs have data.\n\nIf false, the clocked mixer only emits output on ticks where at least one input\ncontributes a frame.",
"type": "boolean",
"default": true
"sample_rate": {
"default": 48000,
"description": "Output sample rate (Hz). Inputs are expected to already match this.",
"format": "uint32",
"minimum": 0,
"type": "integer"
}
},
"type": "object"
}
},
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"description": "Configuration for the AudioMixerNode.",
"properties": {
"clocked": {
"anyOf": [
{
"$ref": "#/$defs/ClockedMixerConfig"
},
{
"type": "null"
}
}
],
"description": "Enable clocked mixing mode (dedicated mixing thread + per-input jitter buffers).\n\nWhen enabled, the mixer emits frames on a fixed cadence determined by\n`sample_rate` and `frame_samples_per_channel`."
},
"num_inputs": {
"default": null,
"description": "Number of input pins to pre-create.\nRequired for stateless/oneshot pipelines where pins must exist before graph building.\nOptional for dynamic pipelines where pins are created on-demand.\nIf specified, pins will be named in_0, in_1, ..., in_{N-1}.",
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"sync_timeout_ms": {
"default": 100,
"description": "Timeout in milliseconds for waiting for slow inputs.\nIf specified, the mixer will wait up to this duration for all active pins to provide frames.\nIf timeout expires, missing pins will be mixed as silence.\nIf not specified (None), the mixer will wait indefinitely (strict broadcast synchronization).\nDefault: Some(100)",
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
}
}
},
"title": "AudioMixerConfig",
"type": "object"
}
```

Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/reference/nodes/audio-mp3-decoder.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ No parameters.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"title": "Mp3DecoderConfig",
"type": "object"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ No parameters.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"title": "OpusDecoderConfig",
"type": "object"
}
Expand Down
15 changes: 8 additions & 7 deletions docs/src/content/docs/reference/nodes/audio-opus-encoder.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@ Encodes raw PCM audio into Opus-compressed packets. Configurable bitrate, applic
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "OpusEncoderConfig",
"type": "object",
"additionalProperties": false,
"properties": {
"bitrate": {
"type": "integer",
"minimum": 6000,
"default": 64000,
"maximum": 510000,
"minimum": 6000,
"multipleOf": 1000,
"default": 64000,
"tunable": false
"tunable": false,
"type": "integer"
}
}
},
"title": "OpusEncoderConfig",
"type": "object"
}
```

Expand Down
Loading
Loading