refactor: organize host modules and document architecture with UML
Verify Tessera GUI / verify (push) Failing after 2m2s

This commit is contained in:
2026-10-25 21:25:43 +08:00
parent 48fcb96f42
commit 18376793b0
85 changed files with 4450 additions and 3653 deletions
+198
View File
@@ -0,0 +1,198 @@
use super::*;
pub(super) fn build(
p: &mut Painter,
state: &mut DeveloperToolsState,
records: &[Exchange],
viewport: Rect,
) {
let x = viewport.x;
let w = viewport.width;
let bottom = viewport.height - 22.0;
p.fill(Rect::new(x, 32.0, w, 30.0), BAR);
p.enabled_button(
Rect::new(x + 3.0, 32.0, 62.0, 29.0),
"Clear",
Action::ClearConsole,
records.iter().any(|r| {
r.id > state.console_after
&& matches!(r.method.as_str(), "PUT" | "POST" | "DELETE")
&& !r.path.ends_with("/events")
}),
);
p.button(
Rect::new(x + 68.0, 32.0, 106.0, 29.0),
if state.follow {
"Follow: on"
} else {
"Follow: off"
},
Action::Follow,
state.follow,
);
p.button(
Rect::new(x + 181.0, 32.0, 119.0, 29.0),
"Reset session",
Action::ResetSession,
false,
);
if w > 520.0 {
p.label(x + 309.0, 39.0, "app.main · live objects", MUTED, false);
}
let mut lines = Vec::new();
for r in records.iter().filter(|r| {
r.id > state.console_after
&& (r.method == "PUT" || r.method == "POST" || r.method == "DELETE")
&& !r.path.ends_with("/events")
}) {
let kind = if r.path.ends_with("/repl") {
if r.method == "DELETE" {
"RESET SESSION"
} else {
"REPL · live objects"
}
} else if r.path.ends_with("/evaluate") {
"ISOLATED API EVALUATION"
} else if r.method == "PUT" {
"FULL SOURCE · live program"
} else {
"CLEAR DOCUMENT"
};
let local = r.origin == "Console" && r.path.ends_with("/repl") && r.method == "POST";
if !local {
let mut title = Line::new(
format!("#{} {} {} {}", r.id, r.timestamp, r.origin, kind),
MUTED,
);
title.divider = Some(Divider::Accent);
title.action = Some(Action::Request(r.id));
lines.push(title);
}
for (i, line) in r.request.split('\n').enumerate() {
lines.push(Line::new(
format!("{}{line}", if i == 0 { "> " } else { " " }),
TEXT,
));
}
let color = if r.status.is_some_and(|s| s >= 400) {
ERROR
} else {
OK
};
if let Some(status) = r.status {
if !local {
lines.push(Line::new(
format!("‹ {status} · {} ms", r.elapsed_ms),
color,
));
}
console_result(&mut lines, &r.response, color);
} else {
lines.push(Line::new(" Executing…", MUTED));
}
let mut separator = Line::new("", MUTED);
separator.divider = Some(Divider::Neutral);
lines.push(separator);
}
if lines.is_empty() {
multiline(
&mut lines,
"Endor · live session\nCreate objects here; call page.render(...) to display.\n",
MUTED,
);
}
use std::hash::{Hash, Hasher};
let mut hash = std::collections::hash_map::DefaultHasher::new();
for line in &lines {
line.value.hash(&mut hash);
}
let stamp = hash.finish();
let output_changed = stamp != state.console_stamp;
state.console_stamp = stamp;
let first_input = lines.len();
let area = Rect::new(x + 4.0, 63.0, w - 5.0, (bottom - 88.0).max(1.0));
let mut input = state.input.clone();
if state.focus == Some(Focus::Input) {
input.insert_str(state.cursor, &state.preedit);
}
for (i, line) in input.split('\n').enumerate() {
let mut line = Line::new(format!("{}{line}", if i == 0 { "> " } else { " " }), TEXT);
line.selected = state.selection && state.focus == Some(Focus::Input);
lines.push(line);
}
let prefix = text::measure("> ", TextStyle::code()).width;
let before = &state.input[..state.cursor];
let row = before.matches('\n').count() as f32;
let column = before.rsplit('\n').next().unwrap_or("");
let caret_x =
prefix + text::measure(&format!("{column}{}", state.preedit), TextStyle::code()).width;
let caret_y = (first_input as f32 + row) * ROW;
let follow = state.follow && output_changed && !state.reveal_input;
if state.reveal_input {
let s = &mut state.scroll[Region::Transcript as usize];
s.y = s.y.clamp(
(caret_y + ROW + 16.0 - area.height).max(0.0).min(caret_y),
caret_y,
);
s.x = s.x.clamp((caret_x + 24.0 - area.width).max(0.0), caret_x);
state.reveal_input = false;
}
p.lines(area, Region::Transcript, &lines, state, follow);
let scroll = state.scroll[Region::Transcript as usize];
let origin = Point::new(
area.x + 8.0 + prefix - scroll.x,
area.y + 5.0 + first_input as f32 * ROW - scroll.y,
);
p.layout.input = Some(ConsoleInputLayout {
viewport: area,
origin,
});
let input_top = origin.y.max(area.y);
let input_bottom =
(origin.y + state.input.split('\n').count() as f32 * ROW).min(area.y + area.height - 9.0);
if input_bottom > input_top {
p.ui.inline_input(
Rect::new(
area.x,
input_top,
(area.width - 9.0).max(0.0),
input_bottom - input_top,
),
Action::Focus(Focus::Input),
);
}
if state.focus == Some(Focus::Input) {
p.ui.list.push(DrawCommand::PushClip(Rect::new(
area.x,
area.y,
(area.width - 9.0).max(0.0),
(area.height - 9.0).max(0.0),
)));
p.fill(
Rect::new(
area.x + 8.0 + caret_x - scroll.x,
origin.y + row * ROW + 1.0,
1.0,
ROW - 2.0,
),
TEXT,
);
p.ui.list.push(DrawCommand::PopClip);
}
p.label(
x + 10.0,
bottom - 23.0,
if state.input_error.is_empty() {
"Enter: run · Shift+Enter: newline · ⌘/Ctrl+↑↓: history"
} else {
&state.input_error
},
if state.input_error.is_empty() {
MUTED
} else {
ERROR
},
false,
);
}