From 2bf48aa9a38d06b4d1bbe502c29a962e94453e57 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Wed, 2 Sep 2026 15:18:43 -0700 Subject: [PATCH 1/4] docs: document validation key for Edge App settings Documents `properties.validation` as another structured help_text key, alongside the existing `type`/`options`/`display_order`/`depends_on` descriptors: an optional, implicitly-anchored regex the dashboard enforces on blur and on save. No Rust changes needed since help_text is already an opaque JSON blob to the CLI. --- docs/EdgeApps.md | 18 ++++++++++++ src/commands/edge_app/manifest.rs | 47 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/docs/EdgeApps.md b/docs/EdgeApps.md index 0202143c..d9e90596 100644 --- a/docs/EdgeApps.md +++ b/docs/EdgeApps.md @@ -487,6 +487,7 @@ Edge App settings support additional input field types beyond plain text and pas - `properties.options` (only for `select`): Array of `{ label, value }` options. - `properties.display_order`: Optional integer controlling the order settings render in the install/edit UI (ascending). If omitted, `screenly edge-app deploy` auto-assigns one from the setting's position in the manifest's `settings:` mapping, so settings render in declaration order by default. Set an explicit value only to override that default. An explicitly authored `display_order` is never overwritten by the automatic assignment. Note that `deploy` only sends the computed order to the backend; it never rewrites your manifest file. - `properties.depends_on`: Optional `{ setting, values }` object that makes this field's visibility depend on another setting's current value. The field only renders (and is submitted) while `setting`'s current value is one of `values`, otherwise it's hidden and skipped. A malformed or stale reference (a typo in `setting`, or a setting later renamed or removed) fails open, so the field stays visible rather than disappearing. A field with `depends_on` can still be marked `optional: false`; its required-ness is only enforced while the field is visible, and is skipped along with the rest of validation while it's hidden. + - `properties.validation`: Optional regular expression the value must match. The pattern is implicitly anchored to a full match (`[A-Z]{3}` means the whole value is three uppercase letters, not that it contains them somewhere). An empty value on an optional field skips this check; on a required field the required check takes precedence. This is enforced by the dashboard only, on blur and on save; it is not enforced by the CLI or the API, so an Edge App must still treat setting values as untrusted input. - **Storage**: Use `type: string` for all non-secret fields; use `type: secret` for password-like fields. The UI will coerce values appropriately (e.g., booleans) but values are stored as strings unless `type: secret`. - **Defaults**: Provide `default_value` at the setting level. For booleans, use `'true'` or `'false'` as strings. @@ -592,6 +593,23 @@ settings: type: url ``` +**Pattern validation** + +```yaml +settings: + airport_code: + type: string + title: Airport Code + optional: false + help_text: + schema_version: 1 + properties: + help_text: "IATA code for the departure board. Three uppercase letters, like LHR." + validation: '[A-Z]{3}' +``` + +`validation` is not supported on `select` or `boolean` fields, since the widget itself already constrains the value. + **Explicit display order override** ```yaml diff --git a/src/commands/edge_app/manifest.rs b/src/commands/edge_app/manifest.rs index 3fb61463..8a3211e2 100644 --- a/src/commands/edge_app/manifest.rs +++ b/src/commands/edge_app/manifest.rs @@ -586,6 +586,53 @@ settings: assert_eq!(actual, expected); } + #[test] + fn test_manifest_allows_validation_in_structured_help_text() { + let dir = tempdir().unwrap(); + let file_path = write_to_tempfile( + &dir, + "screenly.yml", + r#"--- +syntax: manifest_v1 +settings: + airport_code: + type: string + title: Airport Code + optional: false + help_text: + schema_version: 1 + properties: + help_text: "IATA code for the departure board. Three uppercase letters, like LHR." + validation: '[A-Z]{3}' +"#, + ); + + let manifest = EdgeAppManifest::new(&file_path).unwrap(); + + let actual: serde_json::Value = + serde_json::from_str(&manifest.settings[0].help_text).unwrap(); + let expected = serde_json::json!({ + "schema_version": 1, + "properties": { + "help_text": "IATA code for the departure board. Three uppercase letters, like LHR.", + "validation": "[A-Z]{3}", + } + }); + + assert_eq!(actual, expected); + + let output_path = dir.path().join("roundtrip.yml"); + EdgeAppManifest::save_to_file(&manifest, &output_path).unwrap(); + + let contents = fs::read_to_string(output_path).unwrap(); + let yaml: serde_json::Value = serde_yaml::from_str(&contents).unwrap(); + + assert_eq!( + yaml["settings"]["airport_code"]["help_text"]["properties"]["validation"], + serde_json::json!("[A-Z]{3}") + ); + } + #[test] fn test_save_manifest_to_file_serializes_structured_help_text() { let dir = tempdir().unwrap(); From fe1029693ae1397ed7006ef3e3e0f8ca77f0f868 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Fri, 4 Sep 2026 06:51:41 -0700 Subject: [PATCH 2/4] docs: address review on validation key documentation - Write the example pattern explicitly anchored (^[A-Z]{3}$) instead of relying on implicit anchoring, since it's correct either way. - Say validation is silently ignored on select/boolean fields rather than "not supported", since nothing rejects it at deploy time, and surface that caveat in the main properties.validation bullet. - Drop the added manifest.rs test: it couldn't fail for a validation-specific reason, since no code path inspects that key. The existing structured help_text tests already cover this. --- docs/EdgeApps.md | 6 ++-- src/commands/edge_app/manifest.rs | 47 ------------------------------- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/docs/EdgeApps.md b/docs/EdgeApps.md index b16a9479..f5794dfa 100644 --- a/docs/EdgeApps.md +++ b/docs/EdgeApps.md @@ -491,7 +491,7 @@ Edge App settings support additional input field types beyond plain text and pas - `properties.options` (only for `select`): Array of `{ label, value }` options. - `properties.display_order`: Optional integer controlling the order settings render in the install/edit UI (ascending). If omitted, `screenly edge-app deploy` auto-assigns one from the setting's position in the manifest's `settings:` mapping, so settings render in declaration order by default. Set an explicit value only to override that default. An explicitly authored `display_order` is never overwritten by the automatic assignment. Note that `deploy` only sends the computed order to the backend; it never rewrites your manifest file. - `properties.depends_on`: Optional `{ setting, values }` object that makes this field's visibility depend on another setting's current value. The field only renders (and is submitted) while `setting`'s current value is one of `values`, otherwise it's hidden and skipped. A malformed or stale reference (a typo in `setting`, or a setting later renamed or removed) fails open, so the field stays visible rather than disappearing. A field with `depends_on` can still be marked `optional: false`; its required-ness is only enforced while the field is visible, and is skipped along with the rest of validation while it's hidden. - - `properties.validation`: Optional regular expression the value must match. The pattern is implicitly anchored to a full match (`[A-Z]{3}` means the whole value is three uppercase letters, not that it contains them somewhere). An empty value on an optional field skips this check; on a required field the required check takes precedence. This is enforced by the dashboard only, on blur and on save; it is not enforced by the CLI or the API, so an Edge App must still treat setting values as untrusted input. + - `properties.validation`: Optional regular expression the value must match. The pattern is implicitly anchored to a full match (`[A-Z]{3}` means the whole value is three uppercase letters, not that it contains them somewhere); write it explicitly anchored (`^[A-Z]{3}$`) anyway, since that's correct regardless of how the anchoring is implemented. An empty value on an optional field skips this check; on a required field the required check takes precedence. Not supported on `select` or `boolean` fields, since the widget itself already constrains the value; there's no deploy-time check, so `validation` on those types is silently ignored rather than rejected. This is enforced by the dashboard only, on blur and on save; it is not enforced by the CLI or the API, so an Edge App must still treat setting values as untrusted input. - **Storage**: Use `type: string` for all non-secret fields; use `type: secret` for password-like fields. The UI will coerce values appropriately (e.g., booleans) but values are stored as strings unless `type: secret`. - **Defaults**: Provide `default_value` at the setting level. For booleans, use `'true'` or `'false'` as strings. @@ -609,11 +609,9 @@ settings: schema_version: 1 properties: help_text: "IATA code for the departure board. Three uppercase letters, like LHR." - validation: '[A-Z]{3}' + validation: '^[A-Z]{3}$' ``` -`validation` is not supported on `select` or `boolean` fields, since the widget itself already constrains the value. - **Explicit display order override** ```yaml diff --git a/src/commands/edge_app/manifest.rs b/src/commands/edge_app/manifest.rs index 8a3211e2..3fb61463 100644 --- a/src/commands/edge_app/manifest.rs +++ b/src/commands/edge_app/manifest.rs @@ -586,53 +586,6 @@ settings: assert_eq!(actual, expected); } - #[test] - fn test_manifest_allows_validation_in_structured_help_text() { - let dir = tempdir().unwrap(); - let file_path = write_to_tempfile( - &dir, - "screenly.yml", - r#"--- -syntax: manifest_v1 -settings: - airport_code: - type: string - title: Airport Code - optional: false - help_text: - schema_version: 1 - properties: - help_text: "IATA code for the departure board. Three uppercase letters, like LHR." - validation: '[A-Z]{3}' -"#, - ); - - let manifest = EdgeAppManifest::new(&file_path).unwrap(); - - let actual: serde_json::Value = - serde_json::from_str(&manifest.settings[0].help_text).unwrap(); - let expected = serde_json::json!({ - "schema_version": 1, - "properties": { - "help_text": "IATA code for the departure board. Three uppercase letters, like LHR.", - "validation": "[A-Z]{3}", - } - }); - - assert_eq!(actual, expected); - - let output_path = dir.path().join("roundtrip.yml"); - EdgeAppManifest::save_to_file(&manifest, &output_path).unwrap(); - - let contents = fs::read_to_string(output_path).unwrap(); - let yaml: serde_json::Value = serde_yaml::from_str(&contents).unwrap(); - - assert_eq!( - yaml["settings"]["airport_code"]["help_text"]["properties"]["validation"], - serde_json::json!("[A-Z]{3}") - ); - } - #[test] fn test_save_manifest_to_file_serializes_structured_help_text() { let dir = tempdir().unwrap(); From 806417b2146813c0189eeaba68ef4c5390f181e3 Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Fri, 4 Sep 2026 08:03:46 -0700 Subject: [PATCH 3/4] docs: tighten validation key docs, note untyped and malformed patterns - Note that a syntactically invalid pattern isn't caught by the CLI or the API either, only surfacing when the dashboard compiles it. - Add an explicit type: string to the pattern validation example, so it isn't the only structured help_text example that omits type. - Trim the properties.validation bullet for conciseness. --- docs/EdgeApps.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/EdgeApps.md b/docs/EdgeApps.md index f5794dfa..b3080172 100644 --- a/docs/EdgeApps.md +++ b/docs/EdgeApps.md @@ -491,7 +491,7 @@ Edge App settings support additional input field types beyond plain text and pas - `properties.options` (only for `select`): Array of `{ label, value }` options. - `properties.display_order`: Optional integer controlling the order settings render in the install/edit UI (ascending). If omitted, `screenly edge-app deploy` auto-assigns one from the setting's position in the manifest's `settings:` mapping, so settings render in declaration order by default. Set an explicit value only to override that default. An explicitly authored `display_order` is never overwritten by the automatic assignment. Note that `deploy` only sends the computed order to the backend; it never rewrites your manifest file. - `properties.depends_on`: Optional `{ setting, values }` object that makes this field's visibility depend on another setting's current value. The field only renders (and is submitted) while `setting`'s current value is one of `values`, otherwise it's hidden and skipped. A malformed or stale reference (a typo in `setting`, or a setting later renamed or removed) fails open, so the field stays visible rather than disappearing. A field with `depends_on` can still be marked `optional: false`; its required-ness is only enforced while the field is visible, and is skipped along with the rest of validation while it's hidden. - - `properties.validation`: Optional regular expression the value must match. The pattern is implicitly anchored to a full match (`[A-Z]{3}` means the whole value is three uppercase letters, not that it contains them somewhere); write it explicitly anchored (`^[A-Z]{3}$`) anyway, since that's correct regardless of how the anchoring is implemented. An empty value on an optional field skips this check; on a required field the required check takes precedence. Not supported on `select` or `boolean` fields, since the widget itself already constrains the value; there's no deploy-time check, so `validation` on those types is silently ignored rather than rejected. This is enforced by the dashboard only, on blur and on save; it is not enforced by the CLI or the API, so an Edge App must still treat setting values as untrusted input. + - `properties.validation`: Optional regex the value must match, checked by the dashboard on blur and on save. Patterns are implicitly anchored to a full match (`[A-Z]{3}` matches only exactly three uppercase letters). Empty values skip this check when the field is optional; otherwise the required check takes precedence. Silently ignored on `select`/`boolean` fields, and never checked by the CLI or API, so unsupported types and malformed patterns alike deploy cleanly and surface only in the dashboard. Treat setting values as untrusted input regardless. - **Storage**: Use `type: string` for all non-secret fields; use `type: secret` for password-like fields. The UI will coerce values appropriately (e.g., booleans) but values are stored as strings unless `type: secret`. - **Defaults**: Provide `default_value` at the setting level. For booleans, use `'true'` or `'false'` as strings. @@ -608,8 +608,9 @@ settings: help_text: schema_version: 1 properties: + type: string help_text: "IATA code for the departure board. Three uppercase letters, like LHR." - validation: '^[A-Z]{3}$' + validation: '[A-Z]{3}' ``` **Explicit display order override** From 13c47b380541b51a85df6378e4388917f425007a Mon Sep 17 00:00:00 2001 From: nicomiguelino Date: Fri, 4 Sep 2026 08:10:13 -0700 Subject: [PATCH 4/4] docs: include string in the documented properties.type values string is a real SETTING_TYPE value in the dashboard and the fallback when properties.type is omitted, so listing it removes the inconsistency with the Pattern validation example. --- docs/EdgeApps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/EdgeApps.md b/docs/EdgeApps.md index b3080172..6d614d48 100644 --- a/docs/EdgeApps.md +++ b/docs/EdgeApps.md @@ -486,7 +486,7 @@ Edge App settings support additional input field types beyond plain text and pas - **Schema**: The JSON must include `schema_version` and a `properties` object. - **Common keys**: - - `properties.type`: One of `datetime`, `number`, `select`, `boolean`, `textarea`, `url`. + - `properties.type`: One of `string`, `datetime`, `number`, `select`, `boolean`, `textarea`, `url`. `string` is the default plain text input and can be omitted. - `properties.help_text`: Human-friendly description shown in the UI. - `properties.options` (only for `select`): Array of `{ label, value }` options. - `properties.display_order`: Optional integer controlling the order settings render in the install/edit UI (ascending). If omitted, `screenly edge-app deploy` auto-assigns one from the setting's position in the manifest's `settings:` mapping, so settings render in declaration order by default. Set an explicit value only to override that default. An explicitly authored `display_order` is never overwritten by the automatic assignment. Note that `deploy` only sends the computed order to the backend; it never rewrites your manifest file.