feat(sync): 按 Vault 持久化可移植侧栏布局

This commit is contained in:
2026-09-09 06:49:10 +08:00
parent 65a658fa75
commit a751d80459
10 changed files with 133 additions and 13 deletions
@@ -4,6 +4,13 @@ use serde::Deserialize;
use serde_json::Value;
#[derive(Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct Layout {
primary_expanded: bool,
workspace_width: f64,
chat_width: f64,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
struct Level {
size: f64,
weight: u16,
@@ -84,6 +91,12 @@ fn markdown(value: &Markdown) -> bool {
}
pub fn validate(kind: &str, value: &Value) -> Result<()> {
let valid = match kind {
"layout" => {
let value: Layout = decode(value)?;
let _ = value.primary_expanded;
(200.0..=520.0).contains(&value.workspace_width)
&& (200.0..=520.0).contains(&value.chat_width)
}
"theme_settings" => {
let value: Theme = decode(value)?;
let _ = value.headings.custom;
@@ -144,6 +157,23 @@ mod tests {
use super::*;
use serde_json::json;
#[test]
fn layout_schema_limits_widths_and_rejects_device_fields() {
let good = json!({"primaryExpanded":true,"workspaceWidth":400.5,"chatWidth":320});
let path = crate::records::path_for("layout", "sidebars").unwrap();
assert!(crate::records::allowed(&path));
let record = json!({"schema":1,"kind":"layout","id":"sidebars","data":good});
crate::records::validate(&path, &serde_json::to_vec(&record).unwrap()).unwrap();
for invalid in [199.0, 521.0, -1.0] {
let mut data = good.clone();
data["workspaceWidth"] = json!(invalid);
assert!(validate("layout", &data).is_err());
}
let mut bad = good;
bad["windowPath"] = json!("private-device-path");
assert!(validate("layout", &bad).is_err());
assert!(crate::records::path_for("layout", "other").is_err());
}
#[test]
fn portable_preferences_reject_unowned_nested_fields_and_invalid_values() {
let theme = json!({"themeId":"dark","fontEditorSize":18,"fontEditorFamily":"system-ui","lineHeight":1.7,"codeBlockTheme":"auto","headings":{"custom":false,"family":"inherit","levels":([32,28,24,21,18,16].map(|size|json!({"size":size,"weight":700})))}});
validate("theme_settings", &theme).unwrap();
+2
View File
@@ -38,6 +38,7 @@ pub fn path_for(kind: &str, id: &str) -> Result<String> {
("theme_settings", "appearance") => {
Ok("opennexus-records/v1/theme-settings/appearance.json".into())
}
("layout", "sidebars") => Ok("opennexus-records/v1/layout/sidebars.json".into()),
("preferences", "editor") => Ok("opennexus-records/v1/preferences/editor.json".into()),
_ => Err(HostError::new("RECORD_ID_INVALID")),
}
@@ -50,6 +51,7 @@ pub fn allowed(path_value: &str) -> bool {
path_value,
"opennexus-records/v1/theme-settings/appearance.json"
| "opennexus-records/v1/preferences/editor.json"
| "opennexus-records/v1/layout/sidebars.json"
) {
return true;
}