/// 视觉文档模型:唯一事实源(docs/02)。
///
/// 关键设计:
/// - revision 单调递增,补丁必须基于最新 revision 提交(乐观并发控制)。
/// - journal 记录每一次合并。人类操作也被规范化为 Op 写入日志,
/// 保证 Agent 读到的永远是完整、规范的代码表示,而不是事件流。
///|
/// 操作来源:两条路线在 journal 里的身份
pub(all) enum OpSource {
HumanOp
AgentOp
}
///|
pub fn OpSource::name(self : OpSource) -> String {
match self {
HumanOp => "human"
AgentOp => "agent"
}
}
///|
pub(all) struct JournalEntry {
revision : Int
source : OpSource
message : String
violation_count : Int // 合并时携带的视觉债(人类路线可以 > 0)
}
///|
pub(all) struct Document {
mut root_id : String
mut nodes : Map[String, Node]
mut revision : Int
journal : Array[JournalEntry]
/// 内容序号(issue #28):任何节点变更 +1——快照按板失效的 O(1) 键。
/// 与 revision 不同:revision 是文档/历史语义,content_seq 只服务增量。
mut content_seq : Int
}
///|
pub fn Document::new(viewport : Size) -> Document {
let root = {
..Node::make("root", Frame),
name: "画布",
width: Fixed(viewport.width),
height: Fixed(viewport.height),
x: Fixed(0.0),
y: Fixed(0.0),
}
let nodes : Map[String, Node] = Map::new()
nodes.set("root", root)
{ root_id: "root", nodes, revision: 1, journal: [], content_seq: 0, }
}
///|
pub fn Document::get(self : Document, id : String) -> Node? {
self.nodes.get(id)
}
///|
pub fn Document::has(self : Document, id : String) -> Bool {
self.nodes.contains(id)
}
///|
/// 内部插入(不验证)。对外入口一律走补丁(docs/05)。
pub fn Document::insert_node(
self : Document,
node : Node,
parent_id : String,
) -> Bool {
if self.has(node.id) || !self.has(parent_id) {
return false
}
self.nodes.set(node.id, node)
match self.nodes.get(parent_id) {
Some(parent) => {
let kids = parent.children.copy()
kids.push(node.id)
self.nodes.set(parent_id, { ..parent, children: kids, })
self.content_seq = self.content_seq + 1
true
}
None => false
}
}
///|
/// 直接注册节点到 nodes 表(不挂到任何父;克隆/批量构建用)
pub fn Document::register(self : Document, node : Node) -> Bool {
if self.has(node.id) {
return false
}
self.nodes.set(node.id, node)
self.content_seq = self.content_seq + 1
true
}
///|
pub fn Document::update_node(
self : Document,
id : String,
f : (Node) -> Node,
) -> Bool {
match self.nodes.get(id) {
Some(n) => {
self.nodes.set(id, f(n))
self.content_seq = self.content_seq + 1
true
}
None => false
}
}
///|
/// 广度优先找父节点(v1 树很浅,无需缓存父指针)
pub fn Document::find_parent(self : Document, id : String) -> String? {
let queue : Array[String] = [self.root_id]
let mut result : String? = None
let mut running = true
while running && queue.length() > 0 {
match queue.pop() {
None => running = false
Some(cur) =>
match self.nodes.get(cur) {
None => running = false
Some(n) =>
for cid in n.children.iter() {
if cid == id {
result = Some(cur)
running = false
} else {
queue.push(cid)
}
}
}
}
}
result
}
///|
/// 深拷贝:事务式合并的候选副本(apply 前先在副本上验证,见 docs/05)
pub fn Document::clone(self : Document) -> Document {
let nodes : Map[String, Node] = Map::new()
for id in self.nodes.keys() {
match self.nodes.get(id) {
Some(n) => nodes.set(id, { ..n, children: n.children.copy(), })
None => ()
}
}
let journal : Array[JournalEntry] = Array::new()
for e in self.journal.iter() {
journal.push(e)
}
{
root_id: self.root_id,
nodes,
revision: self.revision,
journal,
content_seq: self.content_seq,
}
}
///|
/// 示例:登录页(栈布局 + Fill/Hug),与 docs/05 的 Agent 工作流示例一致。
pub fn Document::sample_login() -> Document {
let doc = Document::new({ width: 390.0, height: 844.0, })
let root_layout : StackLayout = {
dir: Vertical,
gap: 16.0,
justify: Start,
padding: Padding::uniform(24.0),
}
let _ = doc.update_node("root", fn(r) { { ..r, layout: Some(root_layout), } })
let title = {
..Node::make("title", Text),
name: "标题",
text: "欢迎回来",
width: Hug,
height: Hug,
x: AlignStart(0.0),
y: AlignStart(0.0),
style: { ..NodeStyle::default(), font_size: 28.0, font_weight: "bold", },
}
let input_user = {
..Node::make("input-user", Rect),
name: "用户名输入框",
width: Fill,
height: Fixed(44.0),
x: AlignStart(0.0),
y: AlignStart(0.0),
style: { ..NodeStyle::default(), fill: "#F2F3F5", corner_radius: 8.0, },
}
let input_pass = {
..Node::make("input-pass", Rect),
name: "密码输入框",
width: Fill,
height: Fixed(44.0),
x: AlignStart(0.0),
y: AlignStart(0.0),
style: { ..NodeStyle::default(), fill: "#F2F3F5", corner_radius: 8.0, },
}
let btn = {
..Node::make("btn-login", Frame),
name: "登录按钮",
width: Fill,
height: Fixed(48.0),
x: AlignStart(0.0),
y: AlignStart(0.0),
layout: Some({
dir: Horizontal,
gap: 0.0,
justify: Center,
padding: Padding::uniform(0.0),
}),
style: { ..NodeStyle::default(), fill: "#4B6BFB", corner_radius: 10.0, },
}
let btn_text = {
..Node::make("text-btn", Text),
name: "按钮文字",
text: "登录",
width: Hug,
height: Hug,
x: CenterInParent,
y: CenterInParent,
style: {
..NodeStyle::default(),
font_size: 28.0,
text_color: "#FFFFFF",
font_weight: "bold",
},
}
let _ = doc.insert_node(title, "root")
let _ = doc.insert_node(input_user, "root")
let _ = doc.insert_node(input_pass, "root")
let _ = doc.insert_node(btn, "root")
let _ = doc.insert_node(btn_text, "btn-login")
doc
}