Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1e37e36
feat(samples): discovery UX with variant grouping and faceted search
streamkit-devin May 30, 2026
ccee3af
test(ui): add discovery fields to SamplePipeline test fixtures
streamkit-devin May 30, 2026
ecae7da
fix(samples): make grouped variant pills selectable by full name in E2E
streamkit-devin May 30, 2026
474d8f2
Merge branch 'main' into devin/1780157211-sample-discovery-ux
streamer45 May 30, 2026
f26f2b0
refactor(samples): address discovery UX review feedback
streamkit-devin May 30, 2026
2b2e461
docs(ui): clarify expandTerm synonym match is substring not prefix
streamkit-devin May 31, 2026
f9a38a4
feat(ui): polish sample discovery facets and variant cards
streamkit-devin May 31, 2026
0a16be6
fix(ui): render VAD acronym and drop redundant vad tag
streamkit-devin May 31, 2026
a79862a
fix(ui): undo lossy facet dedup, rename Needs GPU, unify clear control
streamkit-devin May 31, 2026
5630252
refactor(samples): typed codec derivation + discovery precision fixes
streamkit-devin May 31, 2026
f7d6db0
feat(convert): add "Open in Design view" handoff from Customize step
streamkit-devin May 31, 2026
9be8422
refactor(samples): drop dead capability label map, lowercase kind once
streamkit-devin May 31, 2026
3db13ca
refactor(samples): explicit YAML discovery contract, no runtime heuri…
streamkit-devin May 31, 2026
62f90d9
fix(discovery): validation, a11y, and design-handoff fixes
streamkit-devin May 31, 2026
9740783
test(e2e): select grouped variant pills by variant label
streamkit-devin May 31, 2026
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
1 change: 1 addition & 0 deletions apps/skit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod plugin_records;
pub mod plugins;
pub mod profiling;
pub mod role_extractor;
pub mod sample_discovery;
pub mod samples;
pub mod server;
pub mod session;
Expand Down
1 change: 1 addition & 0 deletions apps/skit/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ mod plugin_records;
mod plugins;
mod profiling;
mod role_extractor;
mod sample_discovery;
mod samples;
mod server;
mod session;
Expand Down
128 changes: 128 additions & 0 deletions apps/skit/src/sample_discovery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: © 2025 StreamKit Contributors
//
// SPDX-License-Identifier: MPL-2.0

//! Explicit discovery metadata for sample pipelines.
//!
//! The Convert and Stream views group near-duplicate samples (e.g. the
//! colorbars codec/hardware family) into a single card with a variant selector,
//! and expose faceted/fuzzy search over capability tags and categories. All of
//! this is driven by metadata authored directly in each sample's YAML
//! (`group`/`variant`/`canonical`/`category`/`tags`/`keywords`) — there is no
//! runtime derivation from filenames or node-kind substrings. Bundled samples
//! are required to carry a consistent set of fields, enforced by
//! `apps/skit/tests/sample_discovery_metadata_test.rs` so a missing or
//! inconsistent field breaks CI rather than silently degrading the UI.

/// Discovery metadata parsed from a sample's YAML.
#[derive(Debug, Default, Clone)]
pub struct Discovery {
pub group: Option<String>,
pub variant: Option<String>,
pub canonical: bool,
pub category: Option<String>,
pub tags: Vec<String>,
pub keywords: Vec<String>,
}

fn push_term(terms: &mut Vec<String>, value: &str) {
let term = value.trim().to_lowercase();
if !term.is_empty() && !terms.contains(&term) {
terms.push(term);
}
}

/// Builds the backend search document the UI matches queries against.
///
/// Combines the human-facing fields with the sample id (so slug fragments like
/// `vaapi_h264_colorbars` stay searchable), authored keywords, and the
/// pipeline's node kinds (with `::`/`_` separators flattened to spaces so
/// individual segments like `whisper` or `vaapi` are matchable). Lowercased and
/// de-duplicated.
pub fn build_search_terms(
id: &str,
name: &str,
description: &str,
discovery: &Discovery,
node_kinds: &[String],
) -> Vec<String> {
let mut terms = Vec::new();

push_term(&mut terms, id);
push_term(&mut terms, name);
push_term(&mut terms, description);
if let Some(category) = discovery.category.as_deref() {
push_term(&mut terms, category);
}
if let Some(group) = discovery.group.as_deref() {
push_term(&mut terms, group);
}
if let Some(variant) = discovery.variant.as_deref() {
push_term(&mut terms, variant);
}
for tag in &discovery.tags {
push_term(&mut terms, tag);
}
for keyword in &discovery.keywords {
push_term(&mut terms, keyword);
}
for kind in node_kinds {
push_term(&mut terms, &kind.replace("::", " ").replace('_', " "));
}

terms
}

#[cfg(test)]
mod tests {
use super::*;

fn kinds(list: &[&str]) -> Vec<String> {
list.iter().map(|s| (*s).to_string()).collect()
}

#[test]
fn search_terms_include_authored_metadata() {
let discovery = Discovery {
group: Some("video-colorbars".to_string()),
variant: Some("VA-API H.264".to_string()),
canonical: false,
category: Some("Video Encoding".to_string()),
tags: vec!["video-encoding".to_string(), "hardware:vaapi".to_string()],
keywords: vec!["test pattern".to_string(), "smpte".to_string()],
};
let terms = build_search_terms(
"dynamic/video_moq_vaapi_h264_colorbars",
"Video Color Bars (VA-API H.264)",
"Encodes SMPTE color bars",
&discovery,
&kinds(&["video::vaapi::h264_encoder"]),
);

assert!(terms.contains(&"video encoding".to_string()));
assert!(terms.contains(&"hardware:vaapi".to_string()));
assert!(terms.contains(&"test pattern".to_string()));
assert!(terms.contains(&"video vaapi h264 encoder".to_string()));
assert!(terms.contains(&"dynamic/video_moq_vaapi_h264_colorbars".to_string()));
}

#[test]
fn search_terms_are_lowercased_and_deduped() {
let discovery = Discovery {
category: Some("Streaming".to_string()),
tags: vec!["streaming".to_string()],
..Discovery::default()
};
let terms = build_search_terms("streaming", "Streaming", "STREAMING", &discovery, &[]);

assert_eq!(terms.iter().filter(|t| *t == "streaming").count(), 1);
assert!(terms.iter().all(|t| t == &t.to_lowercase()));
}

#[test]
fn search_terms_skip_absent_optional_fields() {
let discovery = Discovery::default();
let terms = build_search_terms("oneshot/solo", "Solo Sample", "", &discovery, &[]);
assert_eq!(terms, vec!["oneshot/solo".to_string(), "solo sample".to_string()]);
}
}
Loading
Loading