fixed lexicon resolution, styling

Orual 9538a97b d8f38532

+153 -62
-8
Cargo.lock
··· 4079 [[package]] 4080 name = "jacquard" 4081 version = "0.9.0" 4082 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4083 dependencies = [ 4084 "bytes", 4085 "getrandom 0.2.16", ··· 4109 [[package]] 4110 name = "jacquard-api" 4111 version = "0.9.0" 4112 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4113 dependencies = [ 4114 "bon", 4115 "bytes", ··· 4127 [[package]] 4128 name = "jacquard-axum" 4129 version = "0.9.0" 4130 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4131 dependencies = [ 4132 "axum", 4133 "bytes", ··· 4149 [[package]] 4150 name = "jacquard-common" 4151 version = "0.9.0" 4152 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4153 dependencies = [ 4154 "base64 0.22.1", 4155 "bon", ··· 4192 [[package]] 4193 name = "jacquard-derive" 4194 version = "0.9.0" 4195 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4196 dependencies = [ 4197 "heck 0.5.0", 4198 "jacquard-lexicon", ··· 4204 [[package]] 4205 name = "jacquard-identity" 4206 version = "0.9.1" 4207 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4208 dependencies = [ 4209 "bon", 4210 "bytes", ··· 4232 [[package]] 4233 name = "jacquard-lexicon" 4234 version = "0.9.1" 4235 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4236 dependencies = [ 4237 "cid", 4238 "dashmap", ··· 4258 [[package]] 4259 name = "jacquard-oauth" 4260 version = "0.9.0" 4261 - source = "git+https://tangled.org/@nonbinary.computer/jacquard#5c79bb76de544cbd4fa8d5d8b01ba6e828f8ba65" 4262 dependencies = [ 4263 "base64 0.22.1", 4264 "bytes",
··· 4079 [[package]] 4080 name = "jacquard" 4081 version = "0.9.0" 4082 dependencies = [ 4083 "bytes", 4084 "getrandom 0.2.16", ··· 4108 [[package]] 4109 name = "jacquard-api" 4110 version = "0.9.0" 4111 dependencies = [ 4112 "bon", 4113 "bytes", ··· 4125 [[package]] 4126 name = "jacquard-axum" 4127 version = "0.9.0" 4128 dependencies = [ 4129 "axum", 4130 "bytes", ··· 4146 [[package]] 4147 name = "jacquard-common" 4148 version = "0.9.0" 4149 dependencies = [ 4150 "base64 0.22.1", 4151 "bon", ··· 4188 [[package]] 4189 name = "jacquard-derive" 4190 version = "0.9.0" 4191 dependencies = [ 4192 "heck 0.5.0", 4193 "jacquard-lexicon", ··· 4199 [[package]] 4200 name = "jacquard-identity" 4201 version = "0.9.1" 4202 dependencies = [ 4203 "bon", 4204 "bytes", ··· 4226 [[package]] 4227 name = "jacquard-lexicon" 4228 version = "0.9.1" 4229 dependencies = [ 4230 "cid", 4231 "dashmap", ··· 4251 [[package]] 4252 name = "jacquard-oauth" 4253 version = "0.9.0" 4254 dependencies = [ 4255 "base64 0.22.1", 4256 "bytes",
+12 -12
Cargo.toml
··· 40 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 41 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 42 43 - jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 44 - jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 45 - jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 46 - jacquard-axum = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 47 - jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 48 - jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false } 49 50 - # jacquard = { path = "../jacquard/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 51 - # jacquard-api = { path = "../jacquard/crates/jacquard-api" } 52 - # jacquard-common = { path = "../jacquard/crates/jacquard-common" } 53 - # jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 54 - # jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 55 - # jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 56 57 [profile] 58
··· 40 markdown-weaver = { git = "https://github.com/rsform/markdown-weaver" } 41 markdown-weaver-escape = { git = "https://github.com/rsform/markdown-weaver" } 42 43 + # jacquard = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 44 + # jacquard-api = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 45 + # jacquard-common = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 46 + # jacquard-axum = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 47 + # jacquard-derive = { git = "https://tangled.org/@nonbinary.computer/jacquard" } 48 + # jacquard-lexicon = { git = "https://tangled.org/@nonbinary.computer/jacquard", default-features = false } 49 50 + jacquard = { path = "../jacquard/crates/jacquard", default-features = false, features = ["derive", "api_bluesky", "tracing"] } 51 + jacquard-api = { path = "../jacquard/crates/jacquard-api" } 52 + jacquard-common = { path = "../jacquard/crates/jacquard-common" } 53 + jacquard-axum = {path = "../jacquard/crates/jacquard-axum" } 54 + jacquard-derive = { path = "../jacquard/crates/jacquard-derive" } 55 + jacquard-lexicon = { path = "../jacquard/crates/jacquard-lexicon", default-features = false } 56 57 [profile] 58
+1 -2
crates/weaver-app/Cargo.toml
··· 20 dashmap = "6.1.0" 21 22 dioxus = { version = "0.7.1", features = ["router"] } 23 weaver-common = { path = "../weaver-common" } 24 jacquard = { workspace = true, features = ["streaming"] } 25 jacquard-lexicon = { workspace = true } ··· 48 serde_html_form = "0.2.8" 49 webbrowser = "1.0.6" 50 tracing.workspace = true 51 - 52 - 53 54 55 [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
··· 20 dashmap = "6.1.0" 21 22 dioxus = { version = "0.7.1", features = ["router"] } 23 + #dioxus-router = { version = "0.7.1", features = ["wasm-split"] } 24 weaver-common = { path = "../weaver-common" } 25 jacquard = { workspace = true, features = ["streaming"] } 26 jacquard-lexicon = { workspace = true } ··· 49 serde_html_form = "0.2.8" 50 webbrowser = "1.0.6" 51 tracing.workspace = true 52 53 54 [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies]
+30 -7
crates/weaver-app/assets/styling/record-view.css
··· 1 .record-view-container { 2 - font-family: var(--font-mono); 3 max-width: 1200px; 4 margin: 2rem auto; 5 padding: 0 1rem; ··· 8 .record-header { 9 margin-bottom: 2rem; 10 padding-bottom: 0.5rem; 11 border-bottom: 1px solid var(--color-border); 12 } 13 ··· 62 } 63 64 .metadata-label { 65 color: var(--color-subtle); 66 font-size: 0.85rem; 67 text-transform: uppercase; ··· 612 border-bottom-color: var(--color-error, #ff6b6b); 613 } 614 615 - .record-field input[type="checkbox"] { 616 - width: 1.1rem; 617 - height: 1.1rem; 618 - margin-top: 0.3rem; 619 - margin-bottom: 0.3rem; 620 cursor: pointer; 621 - accent-color: var(--color-primary); 622 } 623 624 .field-error {
··· 1 .record-view-container { 2 max-width: 1200px; 3 margin: 2rem auto; 4 padding: 0 1rem; ··· 7 .record-header { 8 margin-bottom: 2rem; 9 padding-bottom: 0.5rem; 10 + 11 + font-family: var(--font-mono); 12 border-bottom: 1px solid var(--color-border); 13 } 14 ··· 63 } 64 65 .metadata-label { 66 + font-family: var(--font-mono); 67 color: var(--color-subtle); 68 font-size: 0.85rem; 69 text-transform: uppercase; ··· 614 border-bottom-color: var(--color-error, #ff6b6b); 615 } 616 617 + .boolean-toggle { 618 + font-family: var(--font-mono); 619 + font-size: 0.9rem; 620 + color: var(--color-text); 621 + background: var(--color-surface); 622 + border: 1px solid var(--color-border); 623 + padding: 0.25rem 0.25rem; 624 + margin-right: 0.5rem; 625 + margin-bottom: 0.2rem; 626 cursor: pointer; 627 + transition: 628 + background-color 0.2s, 629 + border-color 0.2s; 630 + } 631 + 632 + .boolean-toggle-false { 633 + color: var(--color-error); 634 + border: 1px solid var(--color-error); 635 + } 636 + 637 + .boolean-toggle-true { 638 + border: 1px solid var(--color-success); 639 + } 640 + 641 + .boolean-toggle:hover { 642 + border: 1px solid var(--color-primary); 643 + background-color: var(--color-primary); 644 + color: var(--color-surface); 645 } 646 647 .field-error {
+1 -1
crates/weaver-app/src/components/login.rs
··· 37 use crate::Route; 38 use gloo_storage::Storage; 39 let full_route = use_route::<Route>(); 40 - gloo_storage::LocalStorage::set("cached_route", format!("{}", full_route)); 41 } 42 43 use_effect(move || {
··· 37 use crate::Route; 38 use gloo_storage::Storage; 39 let full_route = use_route::<Route>(); 40 + gloo_storage::LocalStorage::set("cached_route", format!("{}", full_route)).ok(); 41 } 42 43 use_effect(move || {
+3 -3
crates/weaver-app/src/data.rs
··· 8 use dioxus::prelude::*; 9 #[cfg(feature = "fullstack-server")] 10 #[allow(unused_imports)] 11 - use dioxus::{fullstack::extract::Extension, CapturedError}; 12 use jacquard::types::{did::Did, string::Handle}; 13 #[allow(unused_imports)] 14 use jacquard::{ ··· 18 }; 19 #[allow(unused_imports)] 20 use std::sync::Arc; 21 - use weaver_api::sh_weaver::notebook::{entry::Entry, BookEntryView}; 22 // ============================================================================ 23 // Wrapper Hooks (feature-gated) 24 // ============================================================================ ··· 216 async fn render_markdown_impl(content: Entry<'static>, did: Did<'static>) -> String { 217 use n0_future::stream::StreamExt; 218 use weaver_renderer::{ 219 - atproto::{ClientContext, ClientWriter}, 220 ContextIterator, NotebookProcessor, 221 }; 222 223 let ctx = ClientContext::<()>::new(content.clone(), did);
··· 8 use dioxus::prelude::*; 9 #[cfg(feature = "fullstack-server")] 10 #[allow(unused_imports)] 11 + use dioxus::{CapturedError, fullstack::extract::Extension}; 12 use jacquard::types::{did::Did, string::Handle}; 13 #[allow(unused_imports)] 14 use jacquard::{ ··· 18 }; 19 #[allow(unused_imports)] 20 use std::sync::Arc; 21 + use weaver_api::sh_weaver::notebook::{BookEntryView, entry::Entry}; 22 // ============================================================================ 23 // Wrapper Hooks (feature-gated) 24 // ============================================================================ ··· 216 async fn render_markdown_impl(content: Entry<'static>, did: Did<'static>) -> String { 217 use n0_future::stream::StreamExt; 218 use weaver_renderer::{ 219 ContextIterator, NotebookProcessor, 220 + atproto::{ClientContext, ClientWriter}, 221 }; 222 223 let ctx = ClientContext::<()>::new(content.clone(), did);
+1 -1
crates/weaver-app/src/main.rs
··· 65 #[layout(RecordIndex)] 66 #[route("/:..uri")] 67 RecordView { uri: Vec<String> }, 68 - #[end_layout] 69 #[end_nest] 70 #[route("/callback?:state&:iss&:code")] 71 Callback { state: SmolStr, iss: SmolStr, code: SmolStr },
··· 65 #[layout(RecordIndex)] 66 #[route("/:..uri")] 67 RecordView { uri: Vec<String> }, 68 + #[end_layout] 69 #[end_nest] 70 #[route("/callback?:state&:iss&:code")] 71 Callback { state: SmolStr, iss: SmolStr, code: SmolStr },
-1
crates/weaver-app/src/service_worker.rs
··· 3 4 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 5 use wasm_bindgen_futures::JsFuture; 6 - 7 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 8 use web_sys::{RegistrationOptions, ServiceWorkerContainer, Window}; 9
··· 3 4 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 5 use wasm_bindgen_futures::JsFuture; 6 #[cfg(all(target_family = "wasm", target_os = "unknown"))] 7 use web_sys::{RegistrationOptions, ServiceWorkerContainer, Window}; 8
+105 -27
crates/weaver-app/src/views/record.rs
··· 6 use humansize::format_size; 7 use jacquard::api::com_atproto::repo::get_record::GetRecordOutput; 8 use jacquard::client::AgentError; 9 use jacquard::prelude::*; 10 use jacquard::smol_str::ToSmolStr; 11 use jacquard::{ ··· 14 identity::lexicon_resolver::LexiconSchemaResolver, 15 types::{aturi::AtUri, cid::Cid, ident::AtIdentifier, string::Nsid}, 16 }; 17 use mime_sniffer::MimeTypeSniffer; 18 use weaver_api::com_atproto::repo::{ 19 create_record::CreateRecord, delete_record::DeleteRecord, put_record::PutRecord, ··· 24 enum ViewMode { 25 Pretty, 26 Json, 27 } 28 29 #[component] 30 pub fn RecordIndex() -> Element { 31 let navigator = use_navigator(); 32 let mut uri_input = use_signal(|| String::new()); 33 - 34 let handle_uri_submit = move || { 35 let input_uri = uri_input.read().clone(); 36 if !input_uri.is_empty() { ··· 85 async move { client.fetch_record_slingshot(&*uri.read()).await } 86 }); 87 88 // Check ownership for edit access 89 let auth_state = use_context::<Signal<AuthState>>(); 90 let is_owner = use_memo(move || { ··· 119 view_mode: view_mode, 120 edit_mode: edit_mode, 121 record_resource: record_resource, 122 } 123 } else { 124 div { ··· 133 onclick: move |_| view_mode.set(ViewMode::Json), 134 "JSON" 135 } 136 if is_owner() { 137 button { 138 class: "tab-button edit-button", ··· 158 lang: Some("json".to_string()), 159 } 160 } 161 }, 162 } 163 } ··· 180 } 181 } 182 } 183 fn get_hex_rep(byte_array: &mut [u8]) -> String { 184 let build_string_vec: Vec<String> = byte_array 185 .chunks(2) ··· 563 } 564 565 #[component] 566 - fn JsonEditor(data: Signal<Data<'static>>, nsid: ReadSignal<Option<String>>) -> Element { 567 let mut json_text = 568 use_signal(|| serde_json::to_string_pretty(&*data.read()).unwrap_or_default()); 569 - let mut parse_error = use_signal(|| None::<String>); 570 571 let height = use_memo(move || { 572 let line_count = json_text().lines().count(); ··· 577 format!("{}px", lines * 22 + 32) 578 }); 579 580 - let fetcher = use_context::<CachedFetcher>(); 581 - 582 let validation = use_resource(move || { 583 let text = json_text(); 584 let nsid_val = nsid(); 585 - let fetcher = fetcher.clone(); 586 587 async move { 588 // Only validate if we have an NSID ··· 596 } 597 }; 598 599 - // Resolve lexicon if needed 600 - let registry = jacquard_lexicon::schema::SchemaRegistry::from_inventory(); 601 - if registry.get(&nsid_str).is_none() { 602 - let nsid_str = nsid_str.split('#').next(); 603 - if let Some(Ok(nsid_parsed)) = nsid_str.map(|s| Nsid::new(s)) { 604 - if let Ok(schema) = fetcher.resolve_lexicon_schema(&nsid_parsed).await { 605 - registry.insert(nsid_parsed.to_smolstr(), schema.doc); 606 - } 607 - } 608 - } 609 - 610 - // Validate 611 - let validator = jacquard_lexicon::validation::SchemaValidator::from_registry(registry); 612 let result = validator.validate_by_nsid(&nsid_str, &parsed); 613 614 Some((Some(result), None)) ··· 1133 } 1134 } 1135 1136 - /// Boolean field (checkbox) 1137 #[component] 1138 fn EditableBooleanField( 1139 root: Signal<Data<'static>>, ··· 1155 PathLabel { path: path.clone() } 1156 {remove_button} 1157 } 1158 - input { 1159 - r#type: "checkbox", 1160 - checked: current_value(), 1161 - onchange: move |evt| { 1162 root.with_mut(|data| { 1163 if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 1164 - *target = Data::Boolean(evt.checked()); 1165 } 1166 }); 1167 - } 1168 } 1169 } 1170 } ··· 2133 view_mode: Signal<ViewMode>, 2134 edit_mode: Signal<bool>, 2135 record_resource: Resource<Result<GetRecordOutput<'static>, AgentError>>, 2136 ) -> Element { 2137 let mut edit_data = use_signal(use_reactive!(|record_value| record_value.clone())); 2138 let nsid = use_memo(move || edit_data().type_discriminator().map(|s| s.to_string())); ··· 2156 class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" }, 2157 onclick: move |_| view_mode.set(ViewMode::Json), 2158 "JSON" 2159 } 2160 ActionButtons { 2161 on_update: move |_| { ··· 2323 } 2324 }, 2325 ViewMode::Json => rsx! { 2326 - JsonEditor { data: edit_data, nsid } 2327 }, 2328 } 2329 }
··· 6 use humansize::format_size; 7 use jacquard::api::com_atproto::repo::get_record::GetRecordOutput; 8 use jacquard::client::AgentError; 9 + use jacquard::common::to_data; 10 use jacquard::prelude::*; 11 use jacquard::smol_str::ToSmolStr; 12 use jacquard::{ ··· 15 identity::lexicon_resolver::LexiconSchemaResolver, 16 types::{aturi::AtUri, cid::Cid, ident::AtIdentifier, string::Nsid}, 17 }; 18 + use jacquard_lexicon::lexicon::LexiconDoc; 19 use mime_sniffer::MimeTypeSniffer; 20 use weaver_api::com_atproto::repo::{ 21 create_record::CreateRecord, delete_record::DeleteRecord, put_record::PutRecord, ··· 26 enum ViewMode { 27 Pretty, 28 Json, 29 + Schema, 30 } 31 32 #[component] 33 pub fn RecordIndex() -> Element { 34 let navigator = use_navigator(); 35 let mut uri_input = use_signal(|| String::new()); 36 let handle_uri_submit = move || { 37 let input_uri = uri_input.read().clone(); 38 if !input_uri.is_empty() { ··· 87 async move { client.fetch_record_slingshot(&*uri.read()).await } 88 }); 89 90 + // Fetch schema for the record 91 + let schema_resource = use_resource(move || { 92 + let fetcher = fetcher.clone(); 93 + async move { 94 + let record_read = record_resource.read(); 95 + let record = record_read.as_ref()?.as_ref().ok()?; 96 + 97 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 98 + let main_type = record.value.type_discriminator(); 99 + let mut main_schema = None; 100 + 101 + // Find and resolve all schemas (including main and nested) 102 + for type_val in record.value.query("...$type").values() { 103 + if let Some(type_str) = type_val.as_str() { 104 + // Skip non-NSID types (like "blob") 105 + if !type_str.contains('.') { 106 + continue; 107 + } 108 + 109 + if let Ok(nsid) = Nsid::new(type_str) { 110 + // Fetch and register schema 111 + if let Ok(schema) = fetcher.resolve_lexicon_schema(&nsid).await { 112 + validator 113 + .registry() 114 + .insert(nsid.to_smolstr(), schema.doc.clone()); 115 + 116 + // Keep the main record schema 117 + if Some(type_str) == main_type { 118 + main_schema = Some(schema.doc); 119 + } 120 + } 121 + } 122 + } 123 + } 124 + 125 + main_schema 126 + } 127 + }); 128 + 129 + let schema_signal = use_memo(move || schema_resource.read().clone().flatten()); 130 + 131 // Check ownership for edit access 132 let auth_state = use_context::<Signal<AuthState>>(); 133 let is_owner = use_memo(move || { ··· 162 view_mode: view_mode, 163 edit_mode: edit_mode, 164 record_resource: record_resource, 165 + schema: schema_signal, 166 } 167 } else { 168 div { ··· 177 onclick: move |_| view_mode.set(ViewMode::Json), 178 "JSON" 179 } 180 + button { 181 + class: if view_mode() == ViewMode::Schema { "tab-button active" } else { "tab-button" }, 182 + onclick: move |_| view_mode.set(ViewMode::Schema), 183 + "Schema" 184 + } 185 if is_owner() { 186 button { 187 class: "tab-button edit-button", ··· 207 lang: Some("json".to_string()), 208 } 209 } 210 + }, 211 + ViewMode::Schema => rsx! { 212 + SchemaView { schema: schema_signal } 213 }, 214 } 215 } ··· 232 } 233 } 234 } 235 + 236 + #[component] 237 + fn SchemaView(schema: ReadSignal<Option<LexiconDoc<'static>>>) -> Element { 238 + if let Some(schema_doc) = schema() { 239 + // Convert LexiconDoc to Data for display 240 + let schema_data = use_memo(move || to_data(&schema_doc).ok().map(|d| d.into_static())); 241 + 242 + if let Some(data) = schema_data() { 243 + rsx! { 244 + div { 245 + class: "pretty-record", 246 + DataView { data: data, path: String::new(), did: String::new() } 247 + } 248 + } 249 + } else { 250 + rsx! { 251 + div { class: "schema-error", "Failed to convert schema to displayable format" } 252 + } 253 + } 254 + } else { 255 + rsx! { 256 + div { class: "schema-loading", "Loading schema..." } 257 + } 258 + } 259 + } 260 fn get_hex_rep(byte_array: &mut [u8]) -> String { 261 let build_string_vec: Vec<String> = byte_array 262 .chunks(2) ··· 640 } 641 642 #[component] 643 + fn JsonEditor( 644 + data: Signal<Data<'static>>, 645 + nsid: ReadSignal<Option<String>>, 646 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 647 + ) -> Element { 648 let mut json_text = 649 use_signal(|| serde_json::to_string_pretty(&*data.read()).unwrap_or_default()); 650 651 let height = use_memo(move || { 652 let line_count = json_text().lines().count(); ··· 657 format!("{}px", lines * 22 + 32) 658 }); 659 660 let validation = use_resource(move || { 661 let text = json_text(); 662 let nsid_val = nsid(); 663 + let _ = schema(); // Track schema changes 664 665 async move { 666 // Only validate if we have an NSID ··· 674 } 675 }; 676 677 + // Use global validator (schema already registered) 678 + let validator = jacquard_lexicon::validation::SchemaValidator::global(); 679 let result = validator.validate_by_nsid(&nsid_str, &parsed); 680 681 Some((Some(result), None)) ··· 1200 } 1201 } 1202 1203 + /// Boolean field (toggle button) 1204 #[component] 1205 fn EditableBooleanField( 1206 root: Signal<Data<'static>>, ··· 1222 PathLabel { path: path.clone() } 1223 {remove_button} 1224 } 1225 + button { 1226 + class: if current_value() { "boolean-toggle boolean-toggle-true" } else { "boolean-toggle boolean-toggle-false" }, 1227 + onclick: move |_| { 1228 root.with_mut(|data| { 1229 if let Some(target) = data.get_at_path_mut(path_for_mutation.as_str()) { 1230 + if let Some(bool_val) = target.as_boolean() { 1231 + *target = Data::Boolean(!bool_val); 1232 + } 1233 } 1234 }); 1235 + }, 1236 + "{current_value()}" 1237 } 1238 } 1239 } ··· 2202 view_mode: Signal<ViewMode>, 2203 edit_mode: Signal<bool>, 2204 record_resource: Resource<Result<GetRecordOutput<'static>, AgentError>>, 2205 + schema: ReadSignal<Option<LexiconDoc<'static>>>, 2206 ) -> Element { 2207 let mut edit_data = use_signal(use_reactive!(|record_value| record_value.clone())); 2208 let nsid = use_memo(move || edit_data().type_discriminator().map(|s| s.to_string())); ··· 2226 class: if view_mode() == ViewMode::Json { "tab-button active" } else { "tab-button" }, 2227 onclick: move |_| view_mode.set(ViewMode::Json), 2228 "JSON" 2229 + } 2230 + button { 2231 + class: if view_mode() == ViewMode::Schema { "tab-button active" } else { "tab-button" }, 2232 + onclick: move |_| view_mode.set(ViewMode::Schema), 2233 + "Schema" 2234 } 2235 ActionButtons { 2236 on_update: move |_| { ··· 2398 } 2399 }, 2400 ViewMode::Json => rsx! { 2401 + JsonEditor { data: edit_data, nsid, schema } 2402 + }, 2403 + ViewMode::Schema => rsx! { 2404 + SchemaView { schema } 2405 }, 2406 } 2407 }