diff --git a/SCRIPTING.md b/SCRIPTING.md index aea8d2f3..81600f88 100644 --- a/SCRIPTING.md +++ b/SCRIPTING.md @@ -161,10 +161,17 @@ Each entry in `fields` is a map: show_on_hover: false, // optional — show in hover tooltip (default: false) options: ["A", "B"], // required for "select" fields max: 5, // required for "rating" fields + default_value: "Draft", // optional — initial value for new notes (any matching type) + min: 0, // optional — minimum for "number" fields + max: 100, // optional — maximum for "number" fields (also star count for "rating") + pattern: "^[A-Z]+$", // optional — regex for "text"/"email" fields + pattern_message: "...", // optional — custom error shown when pattern fails validate: |v| (), // optional — return an error string or () } ``` +> **Note:** `default` is a reserved word in Rhai, so the key is `default_value`. + `can_edit: false` marks a derived/computed field — it can be written by an `on_save` hook but users cannot change it directly. @@ -174,12 +181,12 @@ but users cannot change it directly. | Type | Storage | Notes | |---|---|---| -| `"text"` | String | Single-line text input | +| `"text"` | String | Single-line text input. Optional `pattern` for regex validation. | | `"textarea"` | String | Multi-line text input; auto-rendered as markdown in view mode | -| `"number"` | Float | Numeric input | +| `"number"` | Float | Numeric input. Optional `min` / `max` for range validation. | | `"boolean"` | Bool | Checkbox | | `"date"` | String (ISO `YYYY-MM-DD`) or `null` | Date picker | -| `"email"` | String | Email input with mailto link in view mode | +| `"email"` | String | Email input with mailto link in view mode. Optional `pattern` for regex validation. | | `"select"` | String | Dropdown; requires `options: [...]` | | `"rating"` | Float | Star rating; requires `max: N` (e.g. `max: 5`) | | `"note_link"` | String (UUID) or `null` | Link to another note; optional `target_schema` restricts the picker to notes of that schema type | @@ -490,8 +497,55 @@ not saved. ## 6. Field validation -Individual fields can declare a `validate` closure that returns an error string (on failure) -or `()` (on success): +### Built-in validation shortcuts + +For the most common validations, declarative options avoid the need for a `validate` closure: + +| Option | Applies to | Description | +|---|---|---| +| `min: N` | `"number"` fields | Rejects values below `N` | +| `max: N` | `"number"` fields | Rejects values above `N` | +| `pattern: "regex"` | `"text"`, `"email"` fields | Rejects non-empty values that don't match the regex | +| `pattern_message: "..."` | `"text"`, `"email"` fields | Custom error shown when `pattern` fails; defaults to showing the regex | + +```rhai +// Range validation — no closure needed +#{ name: "price", type: "number", min: 0, max: 99999 } + +// Regex validation with custom error message +#{ name: "sku", type: "text", + pattern: "^[A-Z]{2,4}-\\d{3,6}$", + pattern_message: "SKU must be 2-4 letters, a dash, then 3-6 digits (e.g. AB-123)" } +``` + +Built-in checks run **before** any `validate` closure. If a built-in check fails, the closure +is skipped. If both are present and the built-in passes, the closure runs next — errors from +either path are shown to the user. + +Empty strings are not checked against `pattern` — use `required: true` to enforce non-empty. + +> **Note:** For `"rating"` fields, `max` is the star count (e.g. `max: 10` gives a 10-star +> widget), not a validation bound. For `"number"` fields, `max` is a validation bound. + +### Default values + +Any field can specify a `default_value` that pre-populates the field when a new note is created: + +```rhai +#{ name: "status", type: "select", options: ["Draft", "Active"], default_value: "Draft" } +#{ name: "stock", type: "number", default_value: 0, min: 0 } +#{ name: "urgent", type: "boolean", default_value: true } +``` + +Without `default_value`, fields start at their zero-value (empty string, 0, false, null). + +> **Note:** The Rhai key is `default_value` (not `default`) because `default` is a reserved +> word in Rhai. + +### Custom validation closures + +For validations that can't be expressed with `min`/`max`/`pattern`, declare a `validate` +closure that returns an error string (on failure) or `()` (on success): ```rhai #{ diff --git a/example-scripts/product-inventory/product.schema.rhai b/example-scripts/product-inventory/product.schema.rhai index e427e28c..14296b75 100644 --- a/example-scripts/product-inventory/product.schema.rhai +++ b/example-scripts/product-inventory/product.schema.rhai @@ -6,16 +6,24 @@ // @name: Product // @description: An inventory item with auto-formatted title and stock status. +// +// Demonstrates: default_value, min/max range validation, pattern regex validation. schema("Product", #{ version: 1, title_can_edit: false, fields: [ #{ name: "product_name", type: "text", required: true }, - #{ name: "sku", type: "text", required: false }, - #{ name: "price", type: "number", required: false }, - #{ name: "stock", type: "number", required: false }, - #{ name: "category", type: "text", required: false }, + #{ name: "sku", type: "text", required: false, + pattern: "^[A-Z]{2,4}-\\d{3,6}$", + pattern_message: "SKU must be 2-4 uppercase letters, a dash, then 3-6 digits (e.g. AB-123)" }, + #{ name: "price", type: "number", required: false, + min: 0, max: 999999, default_value: 0 }, + #{ name: "stock", type: "number", required: false, + min: 0, default_value: 0 }, + #{ name: "category", type: "select", required: false, + options: ["Electronics", "Books", "Clothing", "Food", "Other"], + default_value: "Other" }, #{ name: "description", type: "textarea", required: false }, #{ name: "stock_status", type: "text", required: false, can_edit: false }, ], diff --git a/krillnotes-core/src/core/scripting/display_helpers.rs b/krillnotes-core/src/core/scripting/display_helpers.rs index e9db86a2..115dc048 100644 --- a/krillnotes-core/src/core/scripting/display_helpers.rs +++ b/krillnotes-core/src/core/scripting/display_helpers.rs @@ -961,6 +961,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -1029,6 +1034,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -1088,6 +1098,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -1156,6 +1171,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -1245,6 +1265,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -1708,6 +1733,11 @@ mod tests { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, diff --git a/krillnotes-core/src/core/scripting/mod.rs b/krillnotes-core/src/core/scripting/mod.rs index c586a935..1884a44a 100644 --- a/krillnotes-core/src/core/scripting/mod.rs +++ b/krillnotes-core/src/core/scripting/mod.rs @@ -574,10 +574,11 @@ impl ScriptRegistry { self.schema_registry.exists(name) } - /// Runs the `validate` closure for a single field, if one is registered. + /// Runs built-in validation (min/max/pattern) and the `validate` closure + /// for a single field, if applicable. /// - /// Returns `Ok(None)` when the field is valid or has no validate closure. - /// Returns `Ok(Some(msg))` when the closure returns an error message. + /// Returns `Ok(None)` when the field is valid or has no validation rules. + /// Returns `Ok(Some(msg))` when validation fails. pub fn validate_field( &self, schema_name: &str, @@ -585,9 +586,6 @@ impl ScriptRegistry { value: &crate::core::note::FieldValue, ) -> crate::Result> { let schema = self.schema_registry.get(schema_name)?; - let Some(ast) = schema.ast.as_ref() else { - return Ok(None); - }; let field = schema .all_fields() @@ -596,6 +594,16 @@ impl ScriptRegistry { let Some(field_def) = field else { return Ok(None); }; + + // Built-in validations (min/max/pattern) + if let Some(msg) = Self::run_builtin_validation(field_def, value) { + return Ok(Some(msg)); + } + + // Closure-based validation + let Some(ast) = schema.ast.as_ref() else { + return Ok(None); + }; let Some(fn_ptr) = field_def.validate.as_ref() else { return Ok(None); }; @@ -617,7 +625,8 @@ impl ScriptRegistry { } } - /// Runs `validate` closures for all fields that have them and have a value. + /// Runs built-in validation (min/max/pattern) and `validate` closures for + /// all fields that have them and have a value. /// /// Returns a map of `field_name → error_message` for each invalid field. pub fn validate_fields( @@ -627,15 +636,23 @@ impl ScriptRegistry { ) -> crate::Result> { let mut errors = std::collections::BTreeMap::new(); let schema = self.schema_registry.get(schema_name)?; - let Some(ast) = schema.ast.as_ref() else { - return Ok(errors); - }; for field_def in schema.all_fields() { + let Some(value) = fields.get(&field_def.name) else { + continue; + }; + + // Built-in validations first + if let Some(msg) = Self::run_builtin_validation(field_def, value) { + errors.insert(field_def.name.clone(), msg); + continue; // skip closure if built-in fails + } + + // Then closure validation let Some(fn_ptr) = field_def.validate.as_ref() else { continue; }; - let Some(value) = fields.get(&field_def.name) else { + let Some(ast) = schema.ast.as_ref() else { continue; }; @@ -656,6 +673,56 @@ impl ScriptRegistry { Ok(errors) } + /// Runs built-in validation rules (min/max for numbers, pattern for + /// text/email) on a single field value. + /// + /// Returns `Some(error_message)` if validation fails, `None` if it passes. + fn run_builtin_validation( + field_def: &schema::FieldDefinition, + value: &crate::core::note::FieldValue, + ) -> Option { + use crate::core::note::FieldValue; + + // min/max for number fields + if let FieldValue::Number(n) = value { + if let Some(min) = field_def.min_value { + if *n < min { + return Some(format!("Value must be at least {min}")); + } + } + if let Some(max) = field_def.max_value { + if *n > max { + return Some(format!("Value must be at most {max}")); + } + } + } + + // pattern for text and email fields + if let Some(ref pat) = field_def.pattern { + let text = match value { + FieldValue::Text(s) | FieldValue::Email(s) => s.as_str(), + _ => return None, + }; + // Only validate non-empty strings (empty is handled by `required`) + if !text.is_empty() { + match regex::Regex::new(pat) { + Ok(re) => { + if !re.is_match(text) { + return Some(field_def.pattern_message.clone().unwrap_or_else(|| { + format!("Value does not match pattern: {pat}") + })); + } + } + Err(e) => { + return Some(format!("Invalid pattern '{pat}': {e}")); + } + } + } + } + + None + } + /// Evaluates the `visible` closure for each `FieldGroup`. /// /// Returns a map of `group_name → bool`. diff --git a/krillnotes-core/src/core/scripting/schema.rs b/krillnotes-core/src/core/scripting/schema.rs index 89b450cd..fa3ad853 100644 --- a/krillnotes-core/src/core/scripting/schema.rs +++ b/krillnotes-core/src/core/scripting/schema.rs @@ -109,6 +109,26 @@ pub struct FieldDefinition { /// for valid or a `String` error message for invalid. #[serde(skip)] pub validate: Option, + /// Script-specified default value for new notes. `None` = use zero-value. + #[serde(skip)] + pub default_value: Option, + /// Minimum allowed value for `number` fields. `None` = no lower bound. + /// Ignored for non-number fields. + #[serde(default)] + pub min_value: Option, + /// Maximum allowed value for `number` fields. `None` = no upper bound. + /// Ignored for non-number fields. This is SEPARATE from the existing `max` + /// field which is specifically the star count for `rating` fields. + #[serde(default)] + pub max_value: Option, + /// Regex pattern for validating `text` and `email` fields. `None` = no + /// pattern check. Ignored for non-text/email fields. + #[serde(default)] + pub pattern: Option, + /// Custom error message for pattern validation failure. + /// If `None`, falls back to "Value does not match pattern: {pattern}". + #[serde(default)] + pub pattern_message: Option, } /// A named group of fields with optional conditional visibility. @@ -210,21 +230,29 @@ impl Schema { } /// Returns a map of field names to their zero-value defaults. + /// + /// If a field has a script-specified `default_value`, it is converted via + /// [`dynamic_to_field_value`]; otherwise the standard zero-value for the + /// field type is used. pub fn default_fields(&self) -> BTreeMap { let mut fields = BTreeMap::new(); for field_def in self.all_fields() { - let default_value = match field_def.field_type.as_str() { - "text" | "textarea" => FieldValue::Text(String::new()), - "number" => FieldValue::Number(0.0), - "boolean" => FieldValue::Boolean(false), - "date" => FieldValue::Date(None), - "email" => FieldValue::Email(String::new()), - "select" => FieldValue::Text(String::new()), - "rating" => FieldValue::Number(0.0), - "note_link" => FieldValue::NoteLink(None), - "file" => FieldValue::File(None), - // Unknown types fall back to empty text; script validation catches typos. - _ => FieldValue::Text(String::new()), + let default_value = if let Some(ref dv) = field_def.default_value { + dynamic_to_field_value(dv.clone(), &field_def.field_type) + } else { + match field_def.field_type.as_str() { + "text" | "textarea" => FieldValue::Text(String::new()), + "number" => FieldValue::Number(0.0), + "boolean" => FieldValue::Boolean(false), + "date" => FieldValue::Date(None), + "email" => FieldValue::Email(String::new()), + "select" => FieldValue::Text(String::new()), + "rating" => FieldValue::Number(0.0), + "note_link" => FieldValue::NoteLink(None), + "file" => FieldValue::File(None), + // Unknown types fall back to empty text; script validation catches typos. + _ => FieldValue::Text(String::new()), + } }; fields.insert(field_def.name.clone(), default_value); } @@ -316,6 +344,38 @@ impl Schema { .get("validate") .and_then(|v| v.clone().try_cast::()); + // Parse default value — store as raw Dynamic, converted in default_fields(). + // Note: "default" is a reserved keyword in Rhai, so we use "default_value" as the key. + let default_value: Option = field_map.get("default_value").cloned(); + + // Parse min for number fields. + let min_value: Option = field_map.get("min").and_then(|v| { + v.clone() + .try_cast::() + .or_else(|| v.clone().try_cast::().map(|i| i as f64)) + }); + + // For number fields, also read "max" as a float for range validation + // (separate from the existing `max: i64` which is the rating star count). + let max_value: Option = if field_type == "number" { + field_map.get("max").and_then(|v| { + v.clone() + .try_cast::() + .or_else(|| v.clone().try_cast::().map(|i| i as f64)) + }) + } else { + None + }; + + // Parse pattern for text/email fields. + let pattern: Option = field_map + .get("pattern") + .and_then(|v| v.clone().try_cast::()); + + let pattern_message: Option = field_map + .get("pattern_message") + .and_then(|v| v.clone().try_cast::()); + Ok(FieldDefinition { name: field_name, field_type, @@ -328,6 +388,11 @@ impl Schema { show_on_hover, allowed_types, validate, + default_value, + min_value, + max_value, + pattern, + pattern_message, }) } diff --git a/krillnotes-core/src/core/scripting/tests.rs b/krillnotes-core/src/core/scripting/tests.rs index f4c176d4..1566b1b2 100644 --- a/krillnotes-core/src/core/scripting/tests.rs +++ b/krillnotes-core/src/core/scripting/tests.rs @@ -266,6 +266,11 @@ fn test_default_fields() { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }, FieldDefinition { name: "count".to_string(), @@ -279,6 +284,11 @@ fn test_default_fields() { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }, ], title_can_view: true, @@ -338,6 +348,11 @@ fn test_date_field_default() { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -376,6 +391,11 @@ fn test_email_field_default() { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -2624,6 +2644,11 @@ fn test_default_field_for_note_link_is_none() { show_on_hover: false, allowed_types: vec![], validate: None, + default_value: None, + min_value: None, + max_value: None, + pattern: None, + pattern_message: None, }], title_can_view: true, title_can_edit: true, @@ -3297,3 +3322,623 @@ fn test_standard_functions_still_available() { let result: String = engine.eval_ast(&ast).unwrap(); assert_eq!(result, "hello world"); } + +// ── default_value field ───────────────────────────────────────────────── + +#[test] +fn test_default_value_text_field() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("DV", #{ version: 1, + fields: [ + #{ name: "status", type: "text", default_value: "draft" } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("DV").unwrap(); + let defaults = schema.default_fields(); + assert_eq!( + defaults["status"], + crate::FieldValue::Text("draft".to_string()) + ); +} + +#[test] +fn test_default_value_number_field() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("DVN", #{ version: 1, + fields: [ + #{ name: "count", type: "number", default_value: 42 } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("DVN").unwrap(); + let defaults = schema.default_fields(); + assert_eq!(defaults["count"], crate::FieldValue::Number(42.0)); +} + +#[test] +fn test_default_value_boolean_field() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("DVB", #{ version: 1, + fields: [ + #{ name: "active", type: "boolean", default_value: true } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("DVB").unwrap(); + let defaults = schema.default_fields(); + assert_eq!(defaults["active"], crate::FieldValue::Boolean(true)); +} + +#[test] +fn test_default_value_select_field() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("DVS", #{ version: 1, + fields: [ + #{ name: "priority", type: "select", options: ["low", "med", "high"], default_value: "med" } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("DVS").unwrap(); + let defaults = schema.default_fields(); + assert_eq!( + defaults["priority"], + crate::FieldValue::Text("med".to_string()) + ); +} + +#[test] +fn test_default_value_date_field() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("DVD", #{ version: 1, + fields: [ + #{ name: "start", type: "date", default_value: "2026-01-15" } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("DVD").unwrap(); + let defaults = schema.default_fields(); + match defaults.get("start") { + Some(crate::FieldValue::Date(Some(d))) => { + assert_eq!(d.to_string(), "2026-01-15"); + } + other => panic!("Expected Date(Some(2026-01-15)), got: {other:?}"), + } +} + +#[test] +fn test_default_value_absent_uses_zero_value() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("NoDefault", #{ version: 1, + fields: [ + #{ name: "body", type: "text" } + ] + }); + "#, + "test", + ) + .unwrap(); + let schema = registry.get_schema("NoDefault").unwrap(); + let defaults = schema.default_fields(); + assert_eq!( + defaults["body"], + crate::FieldValue::Text(String::new()), + "absent default should use zero-value" + ); +} + +// ── min_value / max_value built-in validation ─────────────────────────── + +#[test] +fn test_min_value_rejects_below_minimum() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Ranged", #{ version: 1, + fields: [ + #{ name: "age", type: "number", min: 0, max: 150 } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field("Ranged", "age", &crate::FieldValue::Number(-1.0)) + .unwrap(); + assert_eq!(err, Some("Value must be at least 0".into())); +} + +#[test] +fn test_max_value_rejects_above_maximum() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Ranged2", #{ version: 1, + fields: [ + #{ name: "age", type: "number", min: 0, max: 150 } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field("Ranged2", "age", &crate::FieldValue::Number(200.0)) + .unwrap(); + assert_eq!(err, Some("Value must be at most 150".into())); +} + +#[test] +fn test_min_max_passes_within_range() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Ranged3", #{ version: 1, + fields: [ + #{ name: "age", type: "number", min: 0, max: 150 } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field("Ranged3", "age", &crate::FieldValue::Number(25.0)) + .unwrap(); + assert_eq!(err, None, "value within range should pass validation"); +} + +#[test] +fn test_min_max_boundary_values() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Boundary", #{ version: 1, + fields: [ + #{ name: "score", type: "number", min: 0, max: 100 } + ] + }); + "#, + "test", + ) + .unwrap(); + + // Exactly at min + let err = registry + .validate_field("Boundary", "score", &crate::FieldValue::Number(0.0)) + .unwrap(); + assert_eq!(err, None, "value at min boundary should pass"); + + // Exactly at max + let err = registry + .validate_field("Boundary", "score", &crate::FieldValue::Number(100.0)) + .unwrap(); + assert_eq!(err, None, "value at max boundary should pass"); +} + +#[test] +fn test_min_only_no_upper_bound() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("MinOnly", #{ version: 1, + fields: [ + #{ name: "weight", type: "number", min: 0 } + ] + }); + "#, + "test", + ) + .unwrap(); + + // Below min + let err = registry + .validate_field("MinOnly", "weight", &crate::FieldValue::Number(-1.0)) + .unwrap(); + assert!(err.is_some(), "below min should fail"); + + // Very large value should pass (no upper bound) + let err = registry + .validate_field("MinOnly", "weight", &crate::FieldValue::Number(999999.0)) + .unwrap(); + assert_eq!(err, None, "no upper bound means large values pass"); +} + +// ── pattern built-in validation ───────────────────────────────────────── + +#[test] +fn test_pattern_rejects_non_matching_text() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Patterned", #{ version: 1, + fields: [ + #{ name: "code", type: "text", pattern: "^[A-Z]{3}-\\d{3}$" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field( + "Patterned", + "code", + &crate::FieldValue::Text("invalid".into()), + ) + .unwrap(); + assert!(err.is_some(), "non-matching text should fail pattern"); + assert!( + err.as_ref().unwrap().contains("pattern"), + "error should mention pattern: {:?}", + err + ); +} + +#[test] +fn test_pattern_accepts_matching_text() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Patterned2", #{ version: 1, + fields: [ + #{ name: "code", type: "text", pattern: "^[A-Z]{3}-\\d{3}$" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field( + "Patterned2", + "code", + &crate::FieldValue::Text("ABC-123".into()), + ) + .unwrap(); + assert_eq!(err, None, "matching text should pass pattern"); +} + +#[test] +fn test_pattern_skips_empty_text() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("PatternEmpty", #{ version: 1, + fields: [ + #{ name: "code", type: "text", pattern: "^[A-Z]{3}$" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field("PatternEmpty", "code", &crate::FieldValue::Text("".into())) + .unwrap(); + assert_eq!( + err, None, + "empty text should pass pattern (empty handled by `required`)" + ); +} + +#[test] +fn test_pattern_works_on_email_fields() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("EmailPattern", #{ version: 1, + fields: [ + #{ name: "email", type: "email", pattern: "^.+@example\\.com$" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field( + "EmailPattern", + "email", + &crate::FieldValue::Email("user@other.com".into()), + ) + .unwrap(); + assert!(err.is_some(), "email not matching pattern should fail"); + + let err = registry + .validate_field( + "EmailPattern", + "email", + &crate::FieldValue::Email("user@example.com".into()), + ) + .unwrap(); + assert_eq!(err, None, "email matching pattern should pass"); +} + +// ── pattern_message custom error ──────────────────────────────────────── + +#[test] +fn test_pattern_message_custom() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("PatternMsg", #{ version: 1, + fields: [ + #{ name: "code", type: "text", + pattern: "^[A-Z]{3}-\\d{3}$", + pattern_message: "Code must be like ABC-123" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field( + "PatternMsg", + "code", + &crate::FieldValue::Text("invalid".into()), + ) + .unwrap(); + assert_eq!( + err, + Some("Code must be like ABC-123".to_string()), + "custom pattern_message should be returned on mismatch" + ); +} + +#[test] +fn test_pattern_message_default_when_absent() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("PatternNoMsg", #{ version: 1, + fields: [ + #{ name: "code", type: "text", + pattern: "^[A-Z]{3}$" } + ] + }); + "#, + "test", + ) + .unwrap(); + + let err = registry + .validate_field( + "PatternNoMsg", + "code", + &crate::FieldValue::Text("invalid".into()), + ) + .unwrap(); + assert!( + err.as_ref() + .unwrap() + .contains("Value does not match pattern"), + "default pattern error should be used when pattern_message is absent: {:?}", + err + ); +} + +// ── validate_fields with built-in validation ──────────────────────────── + +#[test] +fn test_validate_fields_with_builtin_validation() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Multi", #{ version: 1, + fields: [ + #{ name: "age", type: "number", min: 0, max: 120 }, + #{ name: "code", type: "text", pattern: "^[A-Z]+$" }, + ] + }); + "#, + "test", + ) + .unwrap(); + + let mut fields = BTreeMap::new(); + fields.insert("age".to_string(), crate::FieldValue::Number(-5.0)); + fields.insert("code".to_string(), crate::FieldValue::Text("abc".into())); + + let errors = registry.validate_fields("Multi", &fields).unwrap(); + assert!(errors.contains_key("age"), "age should fail min check"); + assert!( + errors.contains_key("code"), + "code should fail pattern check" + ); +} + +// ── Both built-in and closure validation ──────────────────────────────── + +#[test] +fn test_builtin_validation_runs_before_closure() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Both", #{ version: 1, + fields: [ + #{ + name: "score", type: "number", min: 0, max: 100, + validate: |v| if v == 50.0 { "No fifties!" } else { () }, + } + ] + }); + "#, + "test", + ) + .unwrap(); + + // Value below min: built-in fails, closure should NOT run + let err = registry + .validate_field("Both", "score", &crate::FieldValue::Number(-1.0)) + .unwrap(); + assert_eq!( + err, + Some("Value must be at least 0".into()), + "built-in should fail before closure runs" + ); + + // Value within range but rejected by closure + let err = registry + .validate_field("Both", "score", &crate::FieldValue::Number(50.0)) + .unwrap(); + assert_eq!( + err, + Some("No fifties!".into()), + "closure should run when built-in passes" + ); + + // Value within range and accepted by closure + let err = registry + .validate_field("Both", "score", &crate::FieldValue::Number(75.0)) + .unwrap(); + assert_eq!(err, None, "valid value should pass both checks"); +} + +// ── rating max vs number max_value ────────────────────────────────────── + +#[test] +fn test_rating_max_not_treated_as_number_range() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Stars", #{ version: 1, + fields: [ + #{ name: "stars", type: "rating", max: 5 } + ] + }); + "#, + "test", + ) + .unwrap(); + + let schema = registry.get_schema("Stars").unwrap(); + assert_eq!(schema.fields[0].max, 5, "rating max should be parsed"); + assert_eq!( + schema.fields[0].max_value, None, + "max_value should not be set for rating fields" + ); + + // Validation should not reject values above the rating max + // (rating max is for display only, not validation) + let err = registry + .validate_field("Stars", "stars", &crate::FieldValue::Number(10.0)) + .unwrap(); + assert_eq!( + err, None, + "rating max is display-only, not a range validator" + ); +} + +#[test] +fn test_number_max_parsed_as_range_validation() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Percent", #{ version: 1, + fields: [ + #{ name: "pct", type: "number", min: 0, max: 100 } + ] + }); + "#, + "test", + ) + .unwrap(); + + let schema = registry.get_schema("Percent").unwrap(); + assert_eq!(schema.fields[0].min_value, Some(0.0)); + assert_eq!(schema.fields[0].max_value, Some(100.0)); +} + +// ── Parsed field struct values ────────────────────────────────────────── + +#[test] +fn test_parse_field_def_new_fields() { + let mut registry = ScriptRegistry::new().unwrap(); + registry + .load_script( + r#" + schema("Parsed", #{ version: 1, + fields: [ + #{ name: "age", type: "number", min: 0, max: 200, default_value: 25 }, + #{ name: "code", type: "text", pattern: "^[A-Z]+$", default_value: "ABC" }, + ] + }); + "#, + "test", + ) + .unwrap(); + + let schema = registry.get_schema("Parsed").unwrap(); + let age = &schema.fields[0]; + assert_eq!(age.min_value, Some(0.0)); + assert_eq!(age.max_value, Some(200.0)); + assert!(age.default_value.is_some()); + assert!( + age.pattern.is_none(), + "pattern should not be set for number" + ); + + let code = &schema.fields[1]; + assert_eq!(code.pattern.as_deref(), Some("^[A-Z]+$")); + assert!(code.default_value.is_some()); + assert!(code.min_value.is_none(), "min should not be set for text"); +} diff --git a/krillnotes-desktop/package-lock.json b/krillnotes-desktop/package-lock.json index fe48eaeb..60ced84b 100644 --- a/krillnotes-desktop/package-lock.json +++ b/krillnotes-desktop/package-lock.json @@ -54,13 +54,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -69,9 +69,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -79,21 +79,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -110,14 +110,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -127,14 +127,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -144,9 +144,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -154,29 +154,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -186,9 +186,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -196,9 +196,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -206,9 +206,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -216,9 +216,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -226,27 +226,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -256,13 +256,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -272,13 +272,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -288,42 +288,42 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -331,14 +331,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -423,9 +423,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.42.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.42.1.tgz", - "integrity": "sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg==", + "version": "6.43.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.0.tgz", + "integrity": "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.6.0", @@ -986,9 +986,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", - "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.61.1.tgz", + "integrity": "sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==", "cpu": [ "arm" ], @@ -1000,9 +1000,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", - "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.61.1.tgz", + "integrity": "sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==", "cpu": [ "arm64" ], @@ -1014,9 +1014,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", - "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.61.1.tgz", + "integrity": "sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==", "cpu": [ "arm64" ], @@ -1028,9 +1028,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", - "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.61.1.tgz", + "integrity": "sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==", "cpu": [ "x64" ], @@ -1042,9 +1042,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", - "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.61.1.tgz", + "integrity": "sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==", "cpu": [ "arm64" ], @@ -1056,9 +1056,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", - "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.61.1.tgz", + "integrity": "sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==", "cpu": [ "x64" ], @@ -1070,9 +1070,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", - "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.61.1.tgz", + "integrity": "sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==", "cpu": [ "arm" ], @@ -1087,9 +1087,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", - "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.61.1.tgz", + "integrity": "sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==", "cpu": [ "arm" ], @@ -1104,9 +1104,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", - "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.61.1.tgz", + "integrity": "sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==", "cpu": [ "arm64" ], @@ -1121,9 +1121,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", - "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.61.1.tgz", + "integrity": "sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==", "cpu": [ "arm64" ], @@ -1138,9 +1138,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", - "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.61.1.tgz", + "integrity": "sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==", "cpu": [ "loong64" ], @@ -1155,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", - "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.61.1.tgz", + "integrity": "sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==", "cpu": [ "loong64" ], @@ -1172,9 +1172,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", - "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.61.1.tgz", + "integrity": "sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==", "cpu": [ "ppc64" ], @@ -1189,9 +1189,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", - "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.61.1.tgz", + "integrity": "sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==", "cpu": [ "ppc64" ], @@ -1206,9 +1206,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", - "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.61.1.tgz", + "integrity": "sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==", "cpu": [ "riscv64" ], @@ -1223,9 +1223,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", - "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.61.1.tgz", + "integrity": "sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==", "cpu": [ "riscv64" ], @@ -1240,9 +1240,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", - "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.61.1.tgz", + "integrity": "sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==", "cpu": [ "s390x" ], @@ -1257,9 +1257,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", - "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.61.1.tgz", + "integrity": "sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==", "cpu": [ "x64" ], @@ -1274,9 +1274,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", - "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.61.1.tgz", + "integrity": "sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==", "cpu": [ "x64" ], @@ -1291,9 +1291,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", - "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.61.1.tgz", + "integrity": "sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==", "cpu": [ "x64" ], @@ -1305,9 +1305,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", - "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.61.1.tgz", + "integrity": "sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==", "cpu": [ "arm64" ], @@ -1319,9 +1319,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", - "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.61.1.tgz", + "integrity": "sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==", "cpu": [ "arm64" ], @@ -1333,9 +1333,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", - "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.61.1.tgz", + "integrity": "sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==", "cpu": [ "ia32" ], @@ -1347,9 +1347,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", - "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.61.1.tgz", + "integrity": "sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==", "cpu": [ "x64" ], @@ -1361,9 +1361,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", - "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.61.1.tgz", + "integrity": "sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==", "cpu": [ "x64" ], @@ -1668,9 +1668,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.1.tgz", - "integrity": "sha512-rpEbaJ/HzNb6fwsquwoAbq29/Vt4gADhS423A8fdkwL4edJ0wZmoB8ar7O6JPDL834MUKOCm/rrJ7c9oAaEaYQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.2.tgz", + "integrity": "sha512-bk3HemqvGRoy+5D/dVMUQHKMYLglD0jVnMm/0iGMH6ufZ+p8r14m6BpIixwij3PBvZdvORUp1YifTD8QxVZ1Nw==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -1684,23 +1684,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.11.1", - "@tauri-apps/cli-darwin-x64": "2.11.1", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.1", - "@tauri-apps/cli-linux-arm64-gnu": "2.11.1", - "@tauri-apps/cli-linux-arm64-musl": "2.11.1", - "@tauri-apps/cli-linux-riscv64-gnu": "2.11.1", - "@tauri-apps/cli-linux-x64-gnu": "2.11.1", - "@tauri-apps/cli-linux-x64-musl": "2.11.1", - "@tauri-apps/cli-win32-arm64-msvc": "2.11.1", - "@tauri-apps/cli-win32-ia32-msvc": "2.11.1", - "@tauri-apps/cli-win32-x64-msvc": "2.11.1" + "@tauri-apps/cli-darwin-arm64": "2.11.2", + "@tauri-apps/cli-darwin-x64": "2.11.2", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.2", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.2", + "@tauri-apps/cli-linux-arm64-musl": "2.11.2", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-gnu": "2.11.2", + "@tauri-apps/cli-linux-x64-musl": "2.11.2", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.2", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.2", + "@tauri-apps/cli-win32-x64-msvc": "2.11.2" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.1.tgz", - "integrity": "sha512-6eEKMBXsQPCuM1EmvrjT2+aBuxWQuFdKdW8pzNuNQtpq45nEEpBlD5gr8pUeAyOU1DQKlkFaEc/MPBxb/Pfjtg==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.2.tgz", + "integrity": "sha512-+4UZzLt+eOAEQCwgd+TqKgyUJMrvx+BgdXLLaqJYmPqzP+nE6YZr/hY6CWLYGQb8jFn99jEkmC6uA3tNvamA1w==", "cpu": [ "arm64" ], @@ -1715,9 +1715,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.1.tgz", - "integrity": "sha512-LQUO7exfRWjWALNhetph5guWpMeHphRpokOLk0OIbTTExaNwJNFu3I4vb+CCM/4G/QGoZe/5XikZOJdNEFP1ig==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.2.tgz", + "integrity": "sha512-VjYYtZUPqDMLutSfJEyxFE3Bz+DPi7c8wC3imckgvciLDZLq4qwKJxBicg0BXGhXjJsl8vKWgWRFNMPELQ+Xyg==", "cpu": [ "x64" ], @@ -1732,9 +1732,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.1.tgz", - "integrity": "sha512-5i/awiBCRRhOUG8yjn0fMHXIWD5Ez8eEk5LtvOxyQrKuJkRaZDvnbIjZbE183blAwkoA4xN3aO/prJiqscl02Q==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.2.tgz", + "integrity": "sha512-yMemD6f4i95AQriS8EazyOFzbE34yjnP16i3IOzpHGQvBoy2DjypFMFBq0NtPuITURv/cOGguRtHR5d79/9CSA==", "cpu": [ "arm" ], @@ -1749,9 +1749,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.1.tgz", - "integrity": "sha512-9LrwDw3S9Fygtw/Q6WDhOP+3svJRGAsejeE+GKrc0eO1ThMVhwi2LL6hw4dlKw93IfS7VY1G19sWGxJ/NcU4nA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.2.tgz", + "integrity": "sha512-cgI91D2wL8GSgoWwZXDqt+DwnuZCP2/bz03QAE4TrhgAKIsrB4hX26W/H1EONPUUNkqrsgeCD0wU6pcNjV/5kw==", "cpu": [ "arm64" ], @@ -1769,9 +1769,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.1.tgz", - "integrity": "sha512-mNA5dbbqPqDUdTIwdUYYuhO2GvIe9UnB2r0VU2njxBOS3Opbx4gKNC5yP0Iu4rYmEmqdlwry9VzGZQ3wq9dyFg==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.2.tgz", + "integrity": "sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==", "cpu": [ "arm64" ], @@ -1789,9 +1789,9 @@ } }, "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.1.tgz", - "integrity": "sha512-fZj3Gwq+6fUs305T5WQiD5iSGJw+j/4w/HGmk4sHDAcy+rp9zU5eaxB7nOyz5/I/nkNAuKPqfp6uIbiUBXkBCw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.2.tgz", + "integrity": "sha512-usbMLJbT3KtkOrBMDVeGYNM35aTHXx38SJSzTMSqqjeUIOQ+iVPjb2yAGNAE+KqmBbAx4FOFIyMeKXx2M/JKGQ==", "cpu": [ "riscv64" ], @@ -1809,9 +1809,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.1.tgz", - "integrity": "sha512-XFxGxOvHM7jjeD6ozCKdGfhzJ7lERYDGZl1/Kb4fsvchaJsfLJ981TlyTG8Qy/gFq+f5GitH3bfrX9JAkjPEyw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.2.tgz", + "integrity": "sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==", "cpu": [ "x64" ], @@ -1829,9 +1829,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.1.tgz", - "integrity": "sha512-d5C2/Zm+68v7R9wTuTCjRQEVrWjcdMkJBZ1+rXse+QdMMlTB9+u9PDNDLw9PQflWxYLaYZ7tjxxL9Nb9II6PbA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.2.tgz", + "integrity": "sha512-eUm7T6clN1MMmNSRQ9gaWsQdyehQx2Gmn5hht/QUlqZQI/qcP2OJK5dnaxqwFzCr2HdsEo9ydxaqcS1oJzMvUw==", "cpu": [ "x64" ], @@ -1849,9 +1849,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.1.tgz", - "integrity": "sha512-YdeVWFAR1pTXzUU6NLstPq4G6OLxuDrXCXEBdmBH+5EZIDXUx0D2kJlz3+YjpazkKvAzYpgziTsyRagls0OfRQ==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.2.tgz", + "integrity": "sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==", "cpu": [ "arm64" ], @@ -1866,9 +1866,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.1.tgz", - "integrity": "sha512-VBGkuH0eB9K9LLSMv361Gzr5Ou72sCS4+ztpmkWEQ+wd/amhcYOsf3X6qn1RJZDzIhiOYHJEOysZUC3baD01rA==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.2.tgz", + "integrity": "sha512-YhjQNZcXfbkCLyazSv1nPnJ9iRFE1wm6kc51FDbU10/Dk09io+6PAGMLjkxnX2GdM0qMnDmTjstY8mTDVvtKeA==", "cpu": [ "ia32" ], @@ -1883,9 +1883,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.1.tgz", - "integrity": "sha512-b3ORhIAKgp9ZYY+zBt7b7r0kLU2kjvyGF0+MS2SBym3emsweGPybEqocJcmtMuxyBhkOKHP4CiuEJEDuAlTx6A==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.2.tgz", + "integrity": "sha512-d2JchlFIpZevZVReyqhQOekJmb1UH3rhZ5VX6sH3ty9ETE0TKQavpihvoScUXfKKpW6HZC0MrFGRU0ZtD+w3gA==", "cpu": [ "x64" ], @@ -1972,16 +1972,16 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", "dependencies": { @@ -2063,9 +2063,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.29", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", - "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "version": "2.10.34", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", + "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2110,9 +2110,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", "dev": true, "funding": [ { @@ -2179,25 +2179,25 @@ } }, "node_modules/dompurify": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", - "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", + "version": "3.4.8", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz", + "integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "node_modules/electron-to-chromium": { - "version": "1.5.353", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", - "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", + "version": "1.5.368", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", + "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.21.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", - "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz", + "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==", "dev": true, "license": "MIT", "dependencies": { @@ -2736,11 +2736,14 @@ } }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/picocolors": { "version": "1.1.1", @@ -2763,9 +2766,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -2783,7 +2786,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2799,24 +2802,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", - "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.6", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", - "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.6" + "react": "^19.2.7" } }, "node_modules/react-i18next": { @@ -2857,13 +2860,13 @@ } }, "node_modules/rollup": { - "version": "4.60.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", - "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "version": "4.61.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", + "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -2873,31 +2876,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.3", - "@rollup/rollup-android-arm64": "4.60.3", - "@rollup/rollup-darwin-arm64": "4.60.3", - "@rollup/rollup-darwin-x64": "4.60.3", - "@rollup/rollup-freebsd-arm64": "4.60.3", - "@rollup/rollup-freebsd-x64": "4.60.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", - "@rollup/rollup-linux-arm-musleabihf": "4.60.3", - "@rollup/rollup-linux-arm64-gnu": "4.60.3", - "@rollup/rollup-linux-arm64-musl": "4.60.3", - "@rollup/rollup-linux-loong64-gnu": "4.60.3", - "@rollup/rollup-linux-loong64-musl": "4.60.3", - "@rollup/rollup-linux-ppc64-gnu": "4.60.3", - "@rollup/rollup-linux-ppc64-musl": "4.60.3", - "@rollup/rollup-linux-riscv64-gnu": "4.60.3", - "@rollup/rollup-linux-riscv64-musl": "4.60.3", - "@rollup/rollup-linux-s390x-gnu": "4.60.3", - "@rollup/rollup-linux-x64-gnu": "4.60.3", - "@rollup/rollup-linux-x64-musl": "4.60.3", - "@rollup/rollup-openbsd-x64": "4.60.3", - "@rollup/rollup-openharmony-arm64": "4.60.3", - "@rollup/rollup-win32-arm64-msvc": "4.60.3", - "@rollup/rollup-win32-ia32-msvc": "4.60.3", - "@rollup/rollup-win32-x64-gnu": "4.60.3", - "@rollup/rollup-win32-x64-msvc": "4.60.3", + "@rollup/rollup-android-arm-eabi": "4.61.1", + "@rollup/rollup-android-arm64": "4.61.1", + "@rollup/rollup-darwin-arm64": "4.61.1", + "@rollup/rollup-darwin-x64": "4.61.1", + "@rollup/rollup-freebsd-arm64": "4.61.1", + "@rollup/rollup-freebsd-x64": "4.61.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.61.1", + "@rollup/rollup-linux-arm-musleabihf": "4.61.1", + "@rollup/rollup-linux-arm64-gnu": "4.61.1", + "@rollup/rollup-linux-arm64-musl": "4.61.1", + "@rollup/rollup-linux-loong64-gnu": "4.61.1", + "@rollup/rollup-linux-loong64-musl": "4.61.1", + "@rollup/rollup-linux-ppc64-gnu": "4.61.1", + "@rollup/rollup-linux-ppc64-musl": "4.61.1", + "@rollup/rollup-linux-riscv64-gnu": "4.61.1", + "@rollup/rollup-linux-riscv64-musl": "4.61.1", + "@rollup/rollup-linux-s390x-gnu": "4.61.1", + "@rollup/rollup-linux-x64-gnu": "4.61.1", + "@rollup/rollup-linux-x64-musl": "4.61.1", + "@rollup/rollup-openbsd-x64": "4.61.1", + "@rollup/rollup-openharmony-arm64": "4.61.1", + "@rollup/rollup-win32-arm64-msvc": "4.61.1", + "@rollup/rollup-win32-ia32-msvc": "4.61.1", + "@rollup/rollup-win32-x64-gnu": "4.61.1", + "@rollup/rollup-win32-x64-msvc": "4.61.1", "fsevents": "~2.3.2" } }, @@ -2955,9 +2958,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3026,9 +3029,9 @@ } }, "node_modules/vite": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", - "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", "dev": true, "license": "MIT", "dependencies": { diff --git a/krillnotes-desktop/src-tauri/src/commands/scripting.rs b/krillnotes-desktop/src-tauri/src/commands/scripting.rs index 051616a1..4819c217 100644 --- a/krillnotes-desktop/src-tauri/src/commands/scripting.rs +++ b/krillnotes-desktop/src-tauri/src/commands/scripting.rs @@ -29,6 +29,11 @@ pub struct FieldDefInfo { pub target_schema: Option, pub show_on_hover: bool, pub allowed_types: Vec, + pub min_value: Option, + pub max_value: Option, + pub pattern: Option, + pub pattern_message: Option, + pub has_default: bool, /// `true` if a `validate` closure is registered for this field. pub has_validate: bool, } @@ -46,6 +51,11 @@ impl From<&crate::FieldDefinition> for FieldDefInfo { target_schema: f.target_schema.clone(), show_on_hover: f.show_on_hover, allowed_types: f.allowed_types.clone(), + min_value: f.min_value, + max_value: f.max_value, + pattern: f.pattern.clone(), + pattern_message: f.pattern_message.clone(), + has_default: f.default_value.is_some(), has_validate: f.validate.is_some(), } } diff --git a/krillnotes-desktop/src/components/FieldEditor.tsx b/krillnotes-desktop/src/components/FieldEditor.tsx index f03eb0d7..f534065a 100644 --- a/krillnotes-desktop/src/components/FieldEditor.tsx +++ b/krillnotes-desktop/src/components/FieldEditor.tsx @@ -86,6 +86,7 @@ function FieldEditor({ fieldName, fieldType, value, required, options, max, targ onChange={(e) => onChange({ Text: e.target.value })} className="w-full p-2 bg-background border border-border rounded-md" required={required} + pattern={fieldDef?.pattern} autoCorrect="off" autoCapitalize="off" spellCheck={false} @@ -119,6 +120,8 @@ function FieldEditor({ fieldName, fieldType, value, required, options, max, targ onChange={(e) => onChange({ Number: parseFloat(e.target.value) || 0 })} className="w-full p-2 bg-background border border-border rounded-md" required={required} + min={fieldDef?.minValue} + max={fieldDef?.maxValue} /> ); } else if ('Boolean' in value) { @@ -138,6 +141,7 @@ function FieldEditor({ fieldName, fieldType, value, required, options, max, targ onChange={(e) => onChange({ Email: e.target.value })} className="w-full p-2 bg-background border border-border rounded-md" required={required} + pattern={fieldDef?.pattern} autoCorrect="off" autoCapitalize="off" spellCheck={false} diff --git a/krillnotes-desktop/src/hooks/useNoteForm.ts b/krillnotes-desktop/src/hooks/useNoteForm.ts index c4bffcef..72fdf505 100644 --- a/krillnotes-desktop/src/hooks/useNoteForm.ts +++ b/krillnotes-desktop/src/hooks/useNoteForm.ts @@ -130,7 +130,8 @@ export function useNoteForm( }, [allTags, editedTags, setTagSuggestions, setTagInput]); const handleFieldBlur = useCallback(async (fieldName: string, fieldDef: FieldDefinition) => { - if (!selectedNote || !fieldDef.hasValidate) return; + const hasBuiltinValidation = fieldDef.minValue != null || fieldDef.maxValue != null || fieldDef.pattern != null; + if (!selectedNote || (!fieldDef.hasValidate && !hasBuiltinValidation)) return; try { const error = await invoke('validate_field', { schemaName: selectedNote.schema, diff --git a/krillnotes-desktop/src/i18n/locales/de.json b/krillnotes-desktop/src/i18n/locales/de.json index 90be2505..c12b9f64 100644 --- a/krillnotes-desktop/src/i18n/locales/de.json +++ b/krillnotes-desktop/src/i18n/locales/de.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "Zur\u00fcck" + }, + "validation": { + "belowMinimum": "Der Wert muss mindestens {{min}} betragen", + "aboveMaximum": "Der Wert darf h\u00f6chstens {{max}} betragen", + "patternMismatch": "Der Wert entspricht nicht dem erforderlichen Format" } } diff --git a/krillnotes-desktop/src/i18n/locales/en.json b/krillnotes-desktop/src/i18n/locales/en.json index d1d1c259..5c60a660 100644 --- a/krillnotes-desktop/src/i18n/locales/en.json +++ b/krillnotes-desktop/src/i18n/locales/en.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "Back" + }, + "validation": { + "belowMinimum": "Value must be at least {{min}}", + "aboveMaximum": "Value must be at most {{max}}", + "patternMismatch": "Value does not match the required format" } } \ No newline at end of file diff --git a/krillnotes-desktop/src/i18n/locales/es.json b/krillnotes-desktop/src/i18n/locales/es.json index 12139ff3..67fedec6 100644 --- a/krillnotes-desktop/src/i18n/locales/es.json +++ b/krillnotes-desktop/src/i18n/locales/es.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "Volver" + }, + "validation": { + "belowMinimum": "El valor debe ser al menos {{min}}", + "aboveMaximum": "El valor debe ser como máximo {{max}}", + "patternMismatch": "El valor no coincide con el formato requerido" } } diff --git a/krillnotes-desktop/src/i18n/locales/fr.json b/krillnotes-desktop/src/i18n/locales/fr.json index cab36392..5f271e69 100644 --- a/krillnotes-desktop/src/i18n/locales/fr.json +++ b/krillnotes-desktop/src/i18n/locales/fr.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "Retour" + }, + "validation": { + "belowMinimum": "La valeur doit être au moins {{min}}", + "aboveMaximum": "La valeur doit être au plus {{max}}", + "patternMismatch": "La valeur ne correspond pas au format requis" } } diff --git a/krillnotes-desktop/src/i18n/locales/ja.json b/krillnotes-desktop/src/i18n/locales/ja.json index 12b8a183..10f39d95 100644 --- a/krillnotes-desktop/src/i18n/locales/ja.json +++ b/krillnotes-desktop/src/i18n/locales/ja.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "戻る" + }, + "validation": { + "belowMinimum": "値は{{min}}以上である必要があります", + "aboveMaximum": "値は{{max}}以下である必要があります", + "patternMismatch": "値が必要な形式と一致しません" } } diff --git a/krillnotes-desktop/src/i18n/locales/ko.json b/krillnotes-desktop/src/i18n/locales/ko.json index 5a7c3020..709cb2c5 100644 --- a/krillnotes-desktop/src/i18n/locales/ko.json +++ b/krillnotes-desktop/src/i18n/locales/ko.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "뒤로" + }, + "validation": { + "belowMinimum": "값은 최소 {{min}}이어야 합니다", + "aboveMaximum": "값은 최대 {{max}}이어야 합니다", + "patternMismatch": "값이 필요한 형식과 일치하지 않습니다" } } diff --git a/krillnotes-desktop/src/i18n/locales/zh.json b/krillnotes-desktop/src/i18n/locales/zh.json index f8d0002c..eea0bd5a 100644 --- a/krillnotes-desktop/src/i18n/locales/zh.json +++ b/krillnotes-desktop/src/i18n/locales/zh.json @@ -792,5 +792,10 @@ }, "mobile": { "backButton": "返回" + }, + "validation": { + "belowMinimum": "值必须至少为 {{min}}", + "aboveMaximum": "值最多为 {{max}}", + "patternMismatch": "值不符合所需格式" } } diff --git a/krillnotes-desktop/src/types.ts b/krillnotes-desktop/src/types.ts index 39ce7adb..8e2c0a6d 100644 --- a/krillnotes-desktop/src/types.ts +++ b/krillnotes-desktop/src/types.ts @@ -66,6 +66,11 @@ export interface FieldDefinition { showOnHover: boolean; allowedTypes: string[]; // MIME types; empty = all allowed; only meaningful for 'file' fields hasValidate: boolean; // true if a validate closure is registered for this field + minValue?: number; // number fields: minimum allowed value + maxValue?: number; // number fields: maximum allowed value + pattern?: string; // text/email fields: regex validation pattern + patternMessage?: string; // custom error message for pattern validation failure + hasDefault: boolean; // true if a default_value is set in the schema } export interface FieldGroup {