Fix native Text navigation layout and focus regressions
This commit is contained in:
@@ -424,6 +424,7 @@ pub enum Message {
|
||||
SourceSymbolsReady(crate::source_navigation::Completion),
|
||||
SourceForSelection,
|
||||
SourceNavigation(crate::source_navigation::Event),
|
||||
SourceFindClose(crate::source_navigation::SymbolContext, Id),
|
||||
Save(bool),
|
||||
SavePath(Option<PathBuf>),
|
||||
Saved(Result<Option<FileSnapshot>, SaveFailure>),
|
||||
@@ -497,6 +498,7 @@ pub struct App {
|
||||
pub source_jump: Option<text_editor::Cursor>,
|
||||
source_select_line: bool,
|
||||
pub source_navigation: crate::source_navigation::State,
|
||||
source_find_instance: Id,
|
||||
pub source_symbols: crate::source_navigation::SymbolState,
|
||||
pub model_issues: Vec<validation::Issue>,
|
||||
pub migration: Option<persistence::MigrationReport>,
|
||||
@@ -603,6 +605,7 @@ impl App {
|
||||
source_jump: None,
|
||||
source_select_line: false,
|
||||
source_navigation: crate::source_navigation::State::default(),
|
||||
source_find_instance: Id::new_v4(),
|
||||
source_symbols: crate::source_navigation::SymbolState::default(),
|
||||
model_issues,
|
||||
migration: None,
|
||||
@@ -1058,7 +1061,7 @@ impl App {
|
||||
self.behavior.creation.has_pending()
|
||||
|| self.behavior.source_active()
|
||||
|| (self.page == Page::Text
|
||||
&& self.source_symbols.open
|
||||
&& (self.source_symbols.open || self.source_navigation.open)
|
||||
&& self.architecture.is_none()),
|
||||
)
|
||||
.map(|()| Message::Escape),
|
||||
@@ -1106,6 +1109,23 @@ impl App {
|
||||
])
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
let message = match message {
|
||||
Message::SourceFindClose(context, instance) => {
|
||||
if context != self.source_symbol_context()
|
||||
|| instance != self.source_find_instance
|
||||
|| !self.source_navigation.open
|
||||
|| !self.source_navigation_available()
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
// Only the checked envelope may enter the legacy Close handler.
|
||||
Message::SourceNavigation(crate::source_navigation::Event::Close)
|
||||
}
|
||||
Message::SourceNavigation(crate::source_navigation::Event::Close) => {
|
||||
return Task::none();
|
||||
}
|
||||
message => message,
|
||||
};
|
||||
if let Message::SourceSymbols(context, _) = &message
|
||||
&& (context.session != self.source_workspace.session()
|
||||
|| context.path != self.source_workspace.active)
|
||||
@@ -1838,6 +1858,8 @@ impl App {
|
||||
return self.update(Message::Add(child));
|
||||
}
|
||||
Message::Escape => {
|
||||
let restore_source_focus = self.source_navigation_available()
|
||||
&& (self.source_navigation.open || self.source_symbols.open);
|
||||
if self.behavior.source_back() {
|
||||
self.status =
|
||||
"Returned to the unchanged parent form; no source was created.".into();
|
||||
@@ -1857,6 +1879,9 @@ impl App {
|
||||
self.source_symbols.open = false;
|
||||
self.source_symbols.results = None;
|
||||
self.source_symbols.notice = None;
|
||||
if restore_source_focus {
|
||||
self.restore_source_editor_focus();
|
||||
}
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.selected_bend = None;
|
||||
}
|
||||
@@ -2971,6 +2996,7 @@ impl App {
|
||||
}
|
||||
Message::SourceRelated(location) => self.jump_to_source(location),
|
||||
Message::SourceViewport(ticket) => return self.source_viewport(ticket),
|
||||
Message::SourceFindClose(..) => return Task::none(), // update checks and unwraps it
|
||||
Message::SourceSymbols(_, event) => return self.source_symbol_event(event),
|
||||
Message::SourceSymbolsReady(completion) => {
|
||||
return self.source_symbols_ready(completion);
|
||||
@@ -3019,6 +3045,7 @@ impl App {
|
||||
return Task::none();
|
||||
}
|
||||
self.page = Page::Text;
|
||||
self.source_find_instance = Id::new_v4();
|
||||
self.source_navigation.open = true;
|
||||
self.source_navigation.refresh(&self.source_editor.text());
|
||||
return iced::widget::operation::focus(if go_to_line {
|
||||
@@ -3027,7 +3054,13 @@ impl App {
|
||||
"source-find"
|
||||
});
|
||||
}
|
||||
Event::Close => self.source_navigation.open = false,
|
||||
Event::Close => {
|
||||
let was_open = self.source_navigation.open;
|
||||
self.source_navigation.open = false;
|
||||
if was_open {
|
||||
self.restore_source_editor_focus();
|
||||
}
|
||||
}
|
||||
Event::Query(query) => {
|
||||
self.source_navigation.query = query;
|
||||
self.source_navigation.refresh(&self.source_editor.text());
|
||||
|
||||
@@ -41,6 +41,10 @@ impl App {
|
||||
pub fn source_symbol_message(&self, event: nav::SymbolEvent) -> Message {
|
||||
Message::SourceSymbols(self.source_symbol_context(), event)
|
||||
}
|
||||
|
||||
pub fn source_find_close_message(&self) -> Message {
|
||||
Message::SourceFindClose(self.source_symbol_context(), self.source_find_instance)
|
||||
}
|
||||
pub fn source_key(&self) -> query::SnapshotKey {
|
||||
query::SnapshotKey {
|
||||
workspace: self.source_workspace.session(),
|
||||
@@ -134,6 +138,15 @@ impl App {
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn restore_source_editor_focus(&mut self) {
|
||||
let cursor = self.source_editor.cursor();
|
||||
if self.source_navigation_available()
|
||||
&& nav::exact_cursor(self.source_workspace.text(), cursor)
|
||||
{
|
||||
self.queue_source_reveal(cursor, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn source_viewport(&mut self, ticket: Option<Id>) -> Task<Message> {
|
||||
let Some(reveal) = &self.source_symbols.reveal else {
|
||||
return Task::none();
|
||||
@@ -180,8 +193,10 @@ impl App {
|
||||
self.source_symbols.notice = None;
|
||||
}
|
||||
nav::SymbolEvent::Back => {
|
||||
self.source_symbols.open = true;
|
||||
self.source_symbol_back();
|
||||
// A successful Back needs no empty results panel. Retain an
|
||||
// already open panel, or reveal an actionable return notice.
|
||||
self.source_symbols.open |= self.source_symbols.notice.is_some();
|
||||
}
|
||||
nav::SymbolEvent::Choose {
|
||||
stamp,
|
||||
|
||||
@@ -461,6 +461,10 @@ fn escape_dismisses_modal_before_underlying_source_results() {
|
||||
app.hosted.visible = true;
|
||||
send(&mut app, Message::Escape);
|
||||
assert!(!app.hosted.visible);
|
||||
assert!(
|
||||
app.source_reveal_ticket().is_none(),
|
||||
"modal dismissal must not focus the editor"
|
||||
);
|
||||
assert!(app.source_symbols.open);
|
||||
assert!(Arc::ptr_eq(
|
||||
app.source_symbols.results.as_ref().unwrap(),
|
||||
@@ -473,6 +477,303 @@ fn escape_dismisses_modal_before_underlying_source_results() {
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice() {
|
||||
for changed in [false, true] {
|
||||
let mut app = fixture();
|
||||
let accepted = app.document.snapshot();
|
||||
let origin = app.source_editor.cursor();
|
||||
finish(&mut app, query::Action::Definition);
|
||||
reveal(&mut app);
|
||||
symbol(&mut app, nav::SymbolEvent::Close);
|
||||
assert!(!app.source_symbols.open && app.source_symbols.results.is_none());
|
||||
if changed {
|
||||
// Use the workspace edit API to give the origin a new generation.
|
||||
show(&mut app, USES);
|
||||
let text = app.source_workspace.text().to_owned();
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceEdit(text_editor::Action::SelectAll),
|
||||
);
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Paste(
|
||||
Arc::new(format!("{text}// current cursor\r\n")),
|
||||
))),
|
||||
);
|
||||
show(&mut app, DEFINITIONS);
|
||||
}
|
||||
let raw = app.source_workspace.bundle();
|
||||
symbol(&mut app, nav::SymbolEvent::Back);
|
||||
assert_eq!(app.source_workspace.active, USES);
|
||||
assert_eq!(app.source_symbols.open, changed);
|
||||
assert_eq!(app.source_symbols.notice.is_some(), changed);
|
||||
reveal(&mut app);
|
||||
if !changed {
|
||||
assert_eq!(app.source_editor.cursor(), origin);
|
||||
}
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
}
|
||||
let mut app = fixture();
|
||||
symbol(&mut app, nav::SymbolEvent::Back);
|
||||
assert!(app.source_symbols.open && app.source_symbols.notice.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection() {
|
||||
let mut app = fixture();
|
||||
let accepted = app.document.snapshot();
|
||||
let raw = app.source_workspace.bundle();
|
||||
let cursor = app.source_editor.cursor();
|
||||
app.source_navigation.open = true;
|
||||
app.source_navigation.query = "送水 🚀".into();
|
||||
assert!(!app.source_symbols.open);
|
||||
let mut messages = captured_input_subscription_messages(&app);
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert!(matches!(messages[0], Message::Escape));
|
||||
send(&mut app, messages.remove(0));
|
||||
assert!(!app.source_navigation.open && !app.source_symbols.open);
|
||||
assert_eq!(app.source_navigation.query, "送水 🚀");
|
||||
assert!(app.source_reveal_ticket().is_some());
|
||||
reveal(&mut app);
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
assert!(captured_input_subscription_messages(&app).is_empty());
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent() {
|
||||
for replacement in 0..3 {
|
||||
let mut app = fixture();
|
||||
if replacement == 0 {
|
||||
show(&mut app, "project.arcadia");
|
||||
}
|
||||
send(&mut app, Message::SourceNavigation(nav::Event::Open(false)));
|
||||
let old_context = app.source_symbol_context();
|
||||
let old_instance = app.source_find_instance;
|
||||
let old_close = app.source_find_close_message();
|
||||
match replacement {
|
||||
0 => {
|
||||
let project = app.project().clone();
|
||||
app.reset(project, None);
|
||||
app.page = Page::Text;
|
||||
assert_eq!(app.source_workspace.active, old_context.path);
|
||||
assert_ne!(app.source_workspace.session(), old_context.session);
|
||||
}
|
||||
1 => {
|
||||
show(&mut app, DEFINITIONS);
|
||||
assert_eq!(app.source_workspace.session(), old_context.session);
|
||||
assert_ne!(app.source_workspace.active, old_context.path);
|
||||
}
|
||||
2 => {
|
||||
send(&mut app, old_close.clone());
|
||||
assert!(!app.source_navigation.open);
|
||||
assert!(app.source_reveal_ticket().is_some());
|
||||
assert_eq!(app.source_symbol_context(), old_context);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
send(&mut app, Message::SourceNavigation(nav::Event::Open(false)));
|
||||
assert_ne!(app.source_find_instance, old_instance);
|
||||
send(
|
||||
&mut app,
|
||||
Message::SourceNavigation(nav::Event::Query("Current Find 雨".into())),
|
||||
);
|
||||
let text = app.source_workspace.text();
|
||||
let (offset, len) = match replacement {
|
||||
0 => (text.find("project model").unwrap() + "project ".len(), 5),
|
||||
1 => (
|
||||
text.find("oa_activity pump").unwrap() + "oa_activity ".len(),
|
||||
4,
|
||||
),
|
||||
_ => (text.find("source @pump").unwrap() + "source @".len(), 4),
|
||||
};
|
||||
let cursor = selection(text, offset, offset + len, true);
|
||||
app.source_editor.move_to(cursor);
|
||||
let request = begin(&mut app, query::Action::References);
|
||||
let ready = completion(&app, request);
|
||||
app.queue_source_reveal(cursor, false);
|
||||
let find_instance = app.source_find_instance;
|
||||
let mut stale = vec![old_close, Message::SourceNavigation(nav::Event::Close)];
|
||||
if replacement != 2 {
|
||||
// Independently require the session/path guard, even with a current
|
||||
// invocation token. A token check alone is not the location contract.
|
||||
stale.push(Message::SourceFindClose(old_context, find_instance));
|
||||
}
|
||||
assert_stale_envelopes_preserve_current(&mut app, &stale);
|
||||
assert!(app.source_navigation.open);
|
||||
assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
assert_eq!(app.source_find_instance, find_instance);
|
||||
send(&mut app, Message::SourceSymbolsReady(ready));
|
||||
assert_stale_envelopes_preserve_current(&mut app, &stale);
|
||||
assert!(app.source_navigation.open);
|
||||
assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
let current_close = app.source_find_close_message();
|
||||
send(&mut app, current_close);
|
||||
assert!(!app.source_navigation.open);
|
||||
assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
assert!(app.source_reveal_ticket().is_some());
|
||||
reveal(&mut app);
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn find_close_releases_removed_input_and_guarded_focus_respects_later_input() {
|
||||
use iced::advanced::{Layout, layout, widget};
|
||||
use iced::futures::StreamExt;
|
||||
use iced::widget::{column, text_input};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Focus(Vec<(Option<widget::Id>, bool)>);
|
||||
impl widget::Operation for Focus {
|
||||
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||
visit(self);
|
||||
}
|
||||
fn focusable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
_: iced::Rectangle,
|
||||
state: &mut dyn widget::operation::Focusable,
|
||||
) {
|
||||
self.0.push((id.cloned(), state.is_focused()));
|
||||
}
|
||||
}
|
||||
fn view(app: &App, later: bool) -> iced::Element<'static, Message, iced::Theme, ()> {
|
||||
let mut content = column![
|
||||
text_input("Editor", "unchanged source")
|
||||
.id(app.source_editor_id())
|
||||
.on_input(|s| Message::SourceNavigation(nav::Event::Query(s)))
|
||||
];
|
||||
if app.source_navigation.open {
|
||||
content = content.push(
|
||||
text_input("Find", "送水")
|
||||
.id("source-find")
|
||||
.on_input(|s| Message::SourceNavigation(nav::Event::Query(s))),
|
||||
);
|
||||
}
|
||||
if later {
|
||||
content = content.push(
|
||||
text_input("Other", "preserve focus")
|
||||
.id("later-input")
|
||||
.on_input(|s| Message::SourceNavigation(nav::Event::Query(s))),
|
||||
);
|
||||
}
|
||||
content.into()
|
||||
}
|
||||
fn run(
|
||||
ui: &mut iced::Element<'_, Message, iced::Theme, ()>,
|
||||
tree: &mut widget::Tree,
|
||||
node: &layout::Node,
|
||||
mut op: Box<dyn widget::Operation>,
|
||||
) {
|
||||
loop {
|
||||
ui.as_widget_mut()
|
||||
.operate(tree, Layout::new(node), &(), op.as_mut());
|
||||
match op.finish() {
|
||||
widget::operation::Outcome::Chain(next) => op = next,
|
||||
widget::operation::Outcome::None => break,
|
||||
widget::operation::Outcome::Some(()) => panic!("focus must not produce a message"),
|
||||
}
|
||||
}
|
||||
}
|
||||
for escape in [false, true] {
|
||||
for later in 0..3 {
|
||||
let mut app = fixture();
|
||||
app.source_navigation.open = true;
|
||||
let accepted = app.document.snapshot();
|
||||
let raw = app.source_workspace.bundle();
|
||||
let cursor = app.source_editor.cursor();
|
||||
let mut ui = view(&app, false);
|
||||
let mut tree = widget::Tree::new(ui.as_widget());
|
||||
let limits = layout::Limits::new(iced::Size::ZERO, iced::Size::new(500., 250.));
|
||||
let node = ui.as_widget_mut().layout(&mut tree, &(), &limits);
|
||||
run(
|
||||
&mut ui,
|
||||
&mut tree,
|
||||
&node,
|
||||
Box::new(widget::operation::focusable::focus(widget::Id::new(
|
||||
"source-find",
|
||||
))),
|
||||
);
|
||||
let mut focus = Focus::default();
|
||||
ui.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &(), &mut focus);
|
||||
assert!(
|
||||
focus
|
||||
.0
|
||||
.contains(&(Some(widget::Id::new("source-find")), true))
|
||||
);
|
||||
|
||||
let close = if escape {
|
||||
Message::Escape
|
||||
} else {
|
||||
app.source_find_close_message()
|
||||
};
|
||||
send(&mut app, close);
|
||||
assert!(!app.source_navigation.open);
|
||||
let ticket = app
|
||||
.source_reveal_ticket()
|
||||
.expect("closing an open Find queues focus");
|
||||
ui = view(&app, later != 0);
|
||||
tree.diff(ui.as_widget());
|
||||
let node = ui.as_widget_mut().layout(&mut tree, &(), &limits);
|
||||
let task = app.update(Message::SourceViewport(Some(ticket)));
|
||||
if later != 0 {
|
||||
run(
|
||||
&mut ui,
|
||||
&mut tree,
|
||||
&node,
|
||||
Box::new(widget::operation::focusable::focus(widget::Id::new(
|
||||
"later-input",
|
||||
))),
|
||||
);
|
||||
}
|
||||
if later == 2 {
|
||||
app.source_symbols
|
||||
.raw_interaction
|
||||
.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
let mut actions =
|
||||
iced_runtime::task::into_stream(task).expect("guarded widget operation");
|
||||
while let Some(action) = actions.next().await {
|
||||
let iced_runtime::Action::Widget(op) = action else {
|
||||
panic!("focus-only task");
|
||||
};
|
||||
run(&mut ui, &mut tree, &node, op);
|
||||
}
|
||||
let mut focus = Focus::default();
|
||||
ui.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &(), &mut focus);
|
||||
assert!(
|
||||
!focus
|
||||
.0
|
||||
.iter()
|
||||
.any(|(id, _)| id.as_ref() == Some(&widget::Id::new("source-find")))
|
||||
);
|
||||
assert!(
|
||||
focus
|
||||
.0
|
||||
.contains(&(Some(app.source_editor_id()), later == 0))
|
||||
);
|
||||
if later != 0 {
|
||||
assert!(
|
||||
focus
|
||||
.0
|
||||
.contains(&(Some(widget::Id::new("later-input")), true))
|
||||
);
|
||||
}
|
||||
assert_eq!(app.source_editor.cursor(), cursor);
|
||||
assert_eq!(app.source_workspace.bundle(), raw);
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed the real App subscription recipes the status produced by a mounted
|
||||
/// TextInput. Poll only immediately available output: no timer wait, worker,
|
||||
/// file dialog or App task is executed by this helper.
|
||||
|
||||
@@ -1020,7 +1020,7 @@ fn source(app: &App) -> View<'_, Message> {
|
||||
),
|
||||
action("Next", Message::SourceNavigation(SourceEvent::Next(false))),
|
||||
label(app.source_navigation.label()),
|
||||
action("Close", Message::SourceNavigation(SourceEvent::Close)),
|
||||
action("Close", app.source_find_close_message()),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
@@ -1208,8 +1208,18 @@ fn source(app: &App) -> View<'_, Message> {
|
||||
} else {
|
||||
container(editor).width(Fill).height(Fill).into()
|
||||
};
|
||||
source_layout(header.into(), workspace, app.window_size.height)
|
||||
}
|
||||
|
||||
fn source_layout<'a, Renderer: iced::advanced::text::Renderer + 'a>(
|
||||
header: View<'a, Message, iced::Theme, Renderer>,
|
||||
workspace: View<'a, Message, iced::Theme, Renderer>,
|
||||
window_height: f32,
|
||||
) -> View<'a, Message, iced::Theme, Renderer> {
|
||||
// Lint clears and repopulates diagnostics asynchronously. Reserve the same
|
||||
// outer slot so a pointer aimed at the editor cannot land in the header.
|
||||
column![
|
||||
container(scrollable(header)).max_height((app.window_size.height * 0.42).max(180.)),
|
||||
container(scrollable(header).id("source-header")).height((window_height * 0.42).max(180.)),
|
||||
workspace,
|
||||
]
|
||||
.height(Fill)
|
||||
@@ -1222,6 +1232,19 @@ fn source_binding(
|
||||
use crate::source_navigation::{SymbolEvent, query::Action};
|
||||
use iced::keyboard::{Key, key::Named};
|
||||
use widget::text_editor::{Binding, Status};
|
||||
// Windows owns Alt+Space for its window menu. Iced's default binding would
|
||||
// otherwise insert a supplied space payload, even after that menu closes.
|
||||
// Keep Ctrl+Alt (AltGr), shifted input, and ordinary Space unchanged.
|
||||
#[cfg(target_os = "windows")]
|
||||
if matches!(press.status, Status::Focused { .. })
|
||||
&& press.modifiers == iced::keyboard::Modifiers::ALT
|
||||
&& matches!(
|
||||
press.key.as_ref(),
|
||||
Key::Named(Named::Space) | Key::Character(" ")
|
||||
)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if matches!(press.status, Status::Focused { .. })
|
||||
&& !press.modifiers.control()
|
||||
&& !press.modifiers.logo()
|
||||
@@ -1248,6 +1271,400 @@ fn source_binding(
|
||||
}
|
||||
Binding::from_key_press(press)
|
||||
}
|
||||
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
mod source_tests {
|
||||
use super::*;
|
||||
use iced::advanced::{
|
||||
Layout, Shell, graphics, layout, renderer, text as shaped, widget as core_widget,
|
||||
};
|
||||
use iced::widget::column;
|
||||
use iced::{Background, Color, Pixels, Point, Rectangle, Size, Transformation, Vector};
|
||||
|
||||
// Measure real Iced paragraphs/editor text without constructing a window or GPU.
|
||||
struct FontLayout;
|
||||
impl renderer::Renderer for FontLayout {
|
||||
fn start_layer(&mut self, _: Rectangle) {}
|
||||
fn end_layer(&mut self) {}
|
||||
fn start_transformation(&mut self, _: Transformation) {}
|
||||
fn end_transformation(&mut self) {}
|
||||
fn reset(&mut self, _: Rectangle) {}
|
||||
fn fill_quad(&mut self, _: renderer::Quad, _: impl Into<Background>) {}
|
||||
fn allocate_image(
|
||||
&mut self,
|
||||
handle: &iced::advanced::image::Handle,
|
||||
callback: impl FnOnce(
|
||||
Result<iced::advanced::image::Allocation, iced::advanced::image::Error>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
renderer::Renderer::allocate_image(&mut (), handle, callback);
|
||||
}
|
||||
}
|
||||
impl shaped::Renderer for FontLayout {
|
||||
type Font = Font;
|
||||
type Paragraph = graphics::text::Paragraph;
|
||||
type Editor = graphics::text::Editor;
|
||||
const ICON_FONT: Font = Font::DEFAULT;
|
||||
const CHECKMARK_ICON: char = '0';
|
||||
const ARROW_DOWN_ICON: char = '0';
|
||||
const SCROLL_UP_ICON: char = '0';
|
||||
const SCROLL_DOWN_ICON: char = '0';
|
||||
const SCROLL_LEFT_ICON: char = '0';
|
||||
const SCROLL_RIGHT_ICON: char = '0';
|
||||
const ICED_LOGO: char = '0';
|
||||
fn default_font(&self) -> Font {
|
||||
Font::DEFAULT
|
||||
}
|
||||
fn default_size(&self) -> Pixels {
|
||||
Pixels(16.)
|
||||
}
|
||||
fn fill_paragraph(&mut self, _: &Self::Paragraph, _: Point, _: Color, _: Rectangle) {}
|
||||
fn fill_editor(&mut self, _: &Self::Editor, _: Point, _: Color, _: Rectangle) {}
|
||||
fn fill_text(&mut self, _: shaped::Text, _: Point, _: Color, _: Rectangle) {}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Geometry {
|
||||
header: Option<(Rectangle, Rectangle, Vector)>,
|
||||
editor: Option<Rectangle>,
|
||||
}
|
||||
impl core_widget::Operation for Geometry {
|
||||
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn core_widget::Operation)) {
|
||||
visit(self);
|
||||
}
|
||||
fn container(&mut self, id: Option<&core_widget::Id>, bounds: Rectangle) {
|
||||
if id == Some(&core_widget::Id::new("test-source-editor")) {
|
||||
self.editor = Some(bounds);
|
||||
}
|
||||
}
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&core_widget::Id>,
|
||||
bounds: Rectangle,
|
||||
content: Rectangle,
|
||||
offset: Vector,
|
||||
_: &mut dyn core_widget::operation::Scrollable,
|
||||
) {
|
||||
if id == Some(&core_widget::Id::new("source-header")) {
|
||||
self.header = Some((bounds, content, offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mounted_source<'a>(
|
||||
content: &'a widget::text_editor::Content<FontLayout>,
|
||||
diagnostics: usize,
|
||||
find: bool,
|
||||
window_height: f32,
|
||||
) -> View<'a, Message, iced::Theme, FontLayout> {
|
||||
let mut notices = column![].spacing(5);
|
||||
for index in 0..diagnostics {
|
||||
notices = notices.push(button(text(format!(
|
||||
"救援/assess.arcadia:{}:5 · Warning: verify this participant's engineering allocation", index + 1,
|
||||
)).size(12)).padding([4, 6]).width(Fill));
|
||||
}
|
||||
let find: View<'_, Message, iced::Theme, FontLayout> = if find {
|
||||
column![
|
||||
text_input("Find literal text", "assess").on_input(|s| Message::SourceNavigation(
|
||||
crate::source_navigation::Event::Query(s)
|
||||
)),
|
||||
text_input("Line", "1").on_input(|s| Message::SourceNavigation(
|
||||
crate::source_navigation::Event::Line(s)
|
||||
)),
|
||||
]
|
||||
.spacing(8)
|
||||
.into()
|
||||
} else {
|
||||
Space::new().into()
|
||||
};
|
||||
let header = container(
|
||||
column![
|
||||
text("Textual model").size(24),
|
||||
text("Edit the current file. Apply text validates the complete included model.")
|
||||
.size(11),
|
||||
row![
|
||||
button(text("Apply text")),
|
||||
button(text("Undo text")),
|
||||
button(text("Model Undo"))
|
||||
]
|
||||
.spacing(8),
|
||||
text("救援/assess.arcadia · All files applied to the accepted model").size(11),
|
||||
text("Undo text affects this draft. Model Undo restores accepted files together.")
|
||||
.size(11),
|
||||
find,
|
||||
row![
|
||||
button(text("Definition · F12")),
|
||||
button(text("References · Shift F12")),
|
||||
button(text("Back"))
|
||||
]
|
||||
.spacing(7),
|
||||
scrollable(notices).height((diagnostics as f32 * 32.).min(132.)),
|
||||
]
|
||||
.spacing(12),
|
||||
)
|
||||
.padding(18);
|
||||
let editor = container(
|
||||
widget::text_editor(content)
|
||||
.on_action(Message::SourceEdit)
|
||||
.font(Font::MONOSPACE)
|
||||
.size(14)
|
||||
.padding(16)
|
||||
.height(Fill),
|
||||
)
|
||||
.id("test-source-editor")
|
||||
.height(Fill)
|
||||
.width(Fill);
|
||||
source_layout(header.into(), editor.into(), window_height)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds() {
|
||||
for (host, window_height) in [
|
||||
(Size::new(720., 540.), 700.),
|
||||
(Size::new(1180., 890.), 1080.),
|
||||
] {
|
||||
let content = widget::text_editor::Content::<FontLayout>::with_text(
|
||||
"// is excluded\r\n// 雨\r\n",
|
||||
);
|
||||
let mut tree = core_widget::Tree::empty();
|
||||
let mut baseline = None;
|
||||
for (diagnostics, find) in [
|
||||
(0, false),
|
||||
(6, false),
|
||||
(0, false),
|
||||
(6, true),
|
||||
(1, true),
|
||||
(0, false),
|
||||
] {
|
||||
let mut ui = mounted_source(&content, diagnostics, find, window_height);
|
||||
tree.diff(ui.as_widget());
|
||||
let node = ui.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&FontLayout,
|
||||
&layout::Limits::new(Size::ZERO, host),
|
||||
);
|
||||
let mut geometry = Geometry::default();
|
||||
ui.as_widget_mut().operate(
|
||||
&mut tree,
|
||||
Layout::new(&node),
|
||||
&FontLayout,
|
||||
&mut geometry,
|
||||
);
|
||||
let editor = geometry.editor.unwrap();
|
||||
let (header, _, _) = geometry.header.unwrap();
|
||||
let slot = node.children()[0].bounds();
|
||||
assert_eq!(slot.height, (window_height * 0.42).max(180.));
|
||||
assert!(
|
||||
header.height <= slot.height,
|
||||
"short content may shrink inside the fixed slot"
|
||||
);
|
||||
assert!(
|
||||
editor.height >= 240.,
|
||||
"remaining editor must stay usable: {editor:?}"
|
||||
);
|
||||
assert_eq!(editor.y, slot.y + slot.height);
|
||||
if let Some(expected) = baseline {
|
||||
assert_eq!(editor, expected, "diagnostics={diagnostics}, find={find}");
|
||||
} else {
|
||||
baseline = Some(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh() {
|
||||
let content = widget::text_editor::Content::<FontLayout>::with_text("// 雨\r\n");
|
||||
let host = Size::new(720., 540.);
|
||||
let mut tree = core_widget::Tree::empty();
|
||||
let mut saved = None;
|
||||
for diagnostics in [6, 7] {
|
||||
let mut ui = mounted_source(&content, diagnostics, true, 700.);
|
||||
tree.diff(ui.as_widget());
|
||||
let node = ui.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&FontLayout,
|
||||
&layout::Limits::new(Size::ZERO, host),
|
||||
);
|
||||
let mut geometry = Geometry::default();
|
||||
ui.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &FontLayout, &mut geometry);
|
||||
let (bounds, contents, _) = geometry.header.unwrap();
|
||||
assert!(
|
||||
contents.height > bounds.height,
|
||||
"fixture must actually overflow"
|
||||
);
|
||||
if saved.is_none() {
|
||||
let mut messages = Vec::new();
|
||||
let mut shell = Shell::new(&mut messages);
|
||||
ui.as_widget_mut().update(
|
||||
&mut tree,
|
||||
&iced::Event::Mouse(iced::mouse::Event::WheelScrolled {
|
||||
delta: iced::mouse::ScrollDelta::Pixels { x: 0., y: -45. },
|
||||
}),
|
||||
Layout::new(&node),
|
||||
iced::mouse::Cursor::Available(Point::new(bounds.x + 8., bounds.y + 8.)),
|
||||
&FontLayout,
|
||||
&mut iced::advanced::clipboard::Null,
|
||||
&mut shell,
|
||||
&Rectangle::with_size(host),
|
||||
);
|
||||
assert!(shell.is_event_captured());
|
||||
}
|
||||
let mut geometry = Geometry::default();
|
||||
ui.as_widget_mut()
|
||||
.operate(&mut tree, Layout::new(&node), &FontLayout, &mut geometry);
|
||||
let offset = geometry.header.unwrap().2;
|
||||
assert!(offset.y > 0., "the actual mounted Scrollable must move");
|
||||
if let Some(before) = saved {
|
||||
assert_eq!(offset, before);
|
||||
} else {
|
||||
saved = Some(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit() {
|
||||
use core_widget::operation::Focusable;
|
||||
use iced::keyboard::{
|
||||
self, Key, Modifiers,
|
||||
key::{Code, Named, Physical},
|
||||
};
|
||||
use widget::text_editor::{Binding, Content, KeyPress, State, Status};
|
||||
let baseline = "// is excluded\r\n// 雨\r\n";
|
||||
let context = crate::source_navigation::SymbolContext {
|
||||
session: Id::new_v4(),
|
||||
path: "救援/assess.arcadia".into(),
|
||||
};
|
||||
for (key, physical_key, modifiers, payload, inserted) in [
|
||||
(
|
||||
Key::Named(Named::Space),
|
||||
Physical::Code(Code::Space),
|
||||
Modifiers::ALT,
|
||||
" ",
|
||||
false,
|
||||
),
|
||||
(
|
||||
Key::Character(" ".into()),
|
||||
Physical::Code(Code::Space),
|
||||
Modifiers::ALT,
|
||||
" ",
|
||||
false,
|
||||
),
|
||||
(
|
||||
Key::Named(Named::Space),
|
||||
Physical::Code(Code::Space),
|
||||
Modifiers::empty(),
|
||||
" ",
|
||||
true,
|
||||
),
|
||||
(
|
||||
Key::Named(Named::Space),
|
||||
Physical::Code(Code::Space),
|
||||
Modifiers::CTRL | Modifiers::ALT,
|
||||
" ",
|
||||
true,
|
||||
),
|
||||
(
|
||||
Key::Character("e".into()),
|
||||
Physical::Code(Code::KeyE),
|
||||
Modifiers::CTRL | Modifiers::ALT,
|
||||
"€",
|
||||
true,
|
||||
),
|
||||
(
|
||||
Key::Named(Named::Space),
|
||||
Physical::Code(Code::Space),
|
||||
Modifiers::ALT | Modifiers::SHIFT,
|
||||
" ",
|
||||
true,
|
||||
),
|
||||
] {
|
||||
let mut content = Content::<FontLayout>::with_text(baseline);
|
||||
content.move_to(widget::text_editor::Cursor {
|
||||
position: widget::text_editor::Position { line: 0, column: 5 },
|
||||
selection: None,
|
||||
});
|
||||
let press = KeyPress {
|
||||
key: key.clone(),
|
||||
modified_key: key.clone(),
|
||||
physical_key,
|
||||
modifiers,
|
||||
text: Some(payload.into()),
|
||||
status: Status::Focused { is_hovered: false },
|
||||
};
|
||||
assert!(
|
||||
matches!(
|
||||
Binding::<Message>::from_key_press(press),
|
||||
Some(Binding::Insert(_))
|
||||
),
|
||||
"the supplied event must expose the default insertion path"
|
||||
);
|
||||
let mut messages = Vec::new();
|
||||
{
|
||||
let mut editor: View<'_, Message, iced::Theme, FontLayout> =
|
||||
widget::text_editor(&content)
|
||||
.on_action(Message::SourceEdit)
|
||||
.font(Font::MONOSPACE)
|
||||
.size(14)
|
||||
.padding(16)
|
||||
.height(Fill)
|
||||
.key_binding(|press| source_binding(press, &context))
|
||||
.into();
|
||||
let mut tree = core_widget::Tree::new(editor.as_widget());
|
||||
let size = Size::new(600., 240.);
|
||||
let node = editor.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&FontLayout,
|
||||
&layout::Limits::new(Size::ZERO, size),
|
||||
);
|
||||
tree.state
|
||||
.downcast_mut::<State<shaped::highlighter::PlainText>>()
|
||||
.focus();
|
||||
let event = iced::Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key: key.clone(),
|
||||
modified_key: key,
|
||||
physical_key,
|
||||
location: keyboard::Location::Standard,
|
||||
modifiers,
|
||||
text: Some(payload.into()),
|
||||
repeat: false,
|
||||
});
|
||||
let mut shell = Shell::new(&mut messages);
|
||||
editor.as_widget_mut().update(
|
||||
&mut tree,
|
||||
&event,
|
||||
Layout::new(&node),
|
||||
iced::mouse::Cursor::Unavailable,
|
||||
&FontLayout,
|
||||
&mut iced::advanced::clipboard::Null,
|
||||
&mut shell,
|
||||
&Rectangle::with_size(size),
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
messages.len(),
|
||||
usize::from(inserted),
|
||||
"modifiers={modifiers:?}"
|
||||
);
|
||||
for message in messages {
|
||||
let Message::SourceEdit(action) = message else {
|
||||
panic!("unexpected navigation action");
|
||||
};
|
||||
content.perform(action);
|
||||
}
|
||||
let expected = if inserted {
|
||||
format!("// is{payload} excluded\r\n// 雨\r\n")
|
||||
} else {
|
||||
baseline.into()
|
||||
};
|
||||
assert_eq!(content.text(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn checks(app: &App) -> View<'_, Message> {
|
||||
let issues = &app.model_issues;
|
||||
let mut out = column, preserved
|
||||
at `2dedde2`, verified navigation and exact saved-source/model equality but found
|
||||
four interaction defects. Corrected native and packaged acceptance remain required.
|
||||
The [four-file correction](release-text-navigation-native-corrections-20260911.md)
|
||||
passed 188 focused tests and strict Clippy, with exact input readback and
|
||||
independent review; its matching full-suite/build/native results remain separate.
|
||||
The [integrated evaluation](release-text-navigation-integrated-20260911.md) links
|
||||
exact input archives, earlier full-test scope and independent findings.
|
||||
The published contextual preview remains unchanged.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* -text
|
||||
@@ -0,0 +1,21 @@
|
||||
# Navigation native corrections — private gate evidence
|
||||
|
||||
Final local gates passed on inputs4: focused5 executed **188 passed, 0 failed, 1 ignored**, 562 filtered, in 41.73s after 1m35s compilation. Strict all-target Clippy5 with `-D warnings` passed at 19:40:12 UTC. Scoped rustfmt check and diff whitespace check passed. All **499 files / 9,470,838 bytes** matched after the gates; every source ZIP member also matched its manifest and the current workspace. Source ZIP SHA256: `7ff9e5703e33bd72e5c2186dd61125e965b82b8dfbf085961d514b005e716798`. `final-gate-result.json` records exact four-file hashes and terminal receipts. `test-binary.json` pins the executed test binary without copying it into this archive.
|
||||
|
||||
These are uncommitted changes above `f4e426ae2a6971395e1ca7e6f094b0c20056cf93`. The root owns the subsequent commit, full desktop suite, matching GUI and native verification. No corrected native or release acceptance is implied here. Windows compilation used `.local/toolchain/env.ps1`, stable GNU/LLVM, `.local/target-gnullvm`, `CARGO_BUILD_JOBS=1`, and PowerShell 7.6.5 after the preserved initial launcher mistake.
|
||||
|
||||
Codex-9e8r6t /root/behavior_audit. Bounded four-file correction after the root's preserved DF7126B8 native trial. No GUI was launched by this worker. Production scope: views.rs, app.rs, app/source_symbols.rs and its tests.rs.
|
||||
|
||||
The original byte copies predate these edits. Inputs1 captures the first formatted candidate; gate logs/status record actual execution separately. Fixed-height outer header preserves its existing scrollable content; successful no-notice Back retains a closed panel; Find-only captured Escape uses the existing single bridge; Find Close and ordinary dismissing Escape request current selection through the existing guarded viewport/focus ticket. Modal dismissal does not request editor focus.
|
||||
|
||||
The Windows-only Alt+Space guard blocks exactly Alt with Named Space or the literal space key. It preserves ordinary Space, shifted input, and Ctrl+Alt/AltGr. Root observed deferred space insertion after the OS menu closed and the next neutral pointer click. This worker's mounted TextEditor test supplies an explicit text-bearing event and does not instrument or claim the actual Windows OS payload. Corrected GUI native acceptance remains required.
|
||||
|
||||
Mounted layout tests exercise the production source_layout function with real paragraph shaping, TextEditor, and Scrollable state, dynamic representative header content, minimum/wide host limits, and actual wheel input. They do not render the whole application view. Focus tests execute the real App subscription and emitted guarded widget operation across a mounted focus tree, removing the originating Find input and rejecting later foreign focus/raw input. Whole Project, raw SourceBundle, selection and history assertions remain in affected App tests.
|
||||
|
||||
The first nested PowerShell 5 wrapper stopped at ordinary Cargo stderr; focused1-wrapper-failure.txt records that launcher failure. The corrected PowerShell 7 launch focused2 reached compilation and reported three E0659 macro-import errors in the new view tests. While it ran, independent review confirmed that unscoped Find Close could replay into a newer file/opening. At root request the owned rustc was stopped (focused2-stop-request.json); the terminal log preserves both the real diagnostics and final 0xffffffff/exit101. No test or Clippy pass is claimed for inputs1, whose complete 499-file readback nevertheless matched.
|
||||
|
||||
Inputs2 adds the explicit macro import and scoped Find Close: session/path plus a token regenerated on every Find Open. The check runs before any source interaction invalidation. Only a valid envelope is translated to the existing Close handler; bare legacy Close is refused. New stale Close tests cover actual reset, same-session file switch, and Close/reopen at the same path, retaining the newer query, request, reveal, raw text, complete accepted model and history. A current envelope still closes and restores focus through the guarded ticket. Other legacy navigation events are unchanged.
|
||||
|
||||
Focused3 then terminated normally with two related compile diagnostics (E0277/E0599): the renderer-generic source_layout helper needed Iced's text::Renderer bound for Scrollable. No tests or Clippy ran. The complete inputs2 readback matched. Inputs3 changes only that trait bound from the independently reviewed inputs2 source; the next run is focused4/Clippy4.
|
||||
|
||||
Focused4 compiled in 6m58s and executed 189 selected tests: 187 passed, 1 failed, 1 ignored (46.57s). All new App guards/focus tests, mounted Windows key-payload controls, and real header scrolling passed. The sole failure was the anticipated evaluator mistake: the short inner Scrollable measured 243.70001px while the assertion demanded the 294px reserved outer slot. Production correctly allows that child to shrink. Inputs4 changes only this test to measure the actual first child node (the outer Container) and its boundary with the editor; the existing exact editor-rectangle equality and real overflow/offset regression remain. No production layout adjustment was made to satisfy the test. Inputs3 complete readback matched; Clippy4 did not run after test failure.
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Preserve exact current source bytes before a gate and read them back after it."""
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
root = pathlib.Path(__file__).resolve().parents[2]
|
||||
evidence = pathlib.Path(__file__).resolve().parent
|
||||
mode, label = sys.argv[1:]
|
||||
assert re.fullmatch(r"inputs[0-9]+", label)
|
||||
|
||||
def paths():
|
||||
result = subprocess.run(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", "--",
|
||||
"crates", ".cargo", "Cargo.toml", "Cargo.lock", "docs", "examples", "harness/fixtures"],
|
||||
cwd=root, check=True, capture_output=True)
|
||||
return sorted(set(p.decode("utf-8") for p in result.stdout.split(b"\0") if p))
|
||||
|
||||
def data(path):
|
||||
target = (root / path).resolve()
|
||||
assert target.is_relative_to(root) and target.is_file(), path
|
||||
return target.read_bytes()
|
||||
|
||||
def sha(raw):
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
def write(path, value):
|
||||
with path.open("x", encoding="utf-8", newline="\n") as out:
|
||||
json.dump(value, out, indent=2, ensure_ascii=False)
|
||||
out.write("\n")
|
||||
|
||||
if mode == "capture":
|
||||
entries = []
|
||||
archive = evidence / (label + ".zip")
|
||||
with zipfile.ZipFile(archive, "x", zipfile.ZIP_DEFLATED) as out:
|
||||
for path in paths():
|
||||
raw = data(path)
|
||||
out.writestr(path, raw)
|
||||
entries.append({"path": path, "bytes": len(raw), "sha256": sha(raw)})
|
||||
result = {"atUtc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"head": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root).decode().strip(),
|
||||
"branch": subprocess.check_output(["git", "branch", "--show-current"], cwd=root).decode().strip(),
|
||||
"scope": "Exact Windows gate source inputs; no compiler or native result implied.",
|
||||
"archiveSha256": sha(archive.read_bytes()), "files": entries}
|
||||
write(evidence / (label + ".json"), result)
|
||||
print(json.dumps({"files": len(entries), "bytes": sum(e["bytes"] for e in entries), "archiveSha256": result["archiveSha256"]}))
|
||||
elif mode == "verify":
|
||||
before = json.loads((evidence / (label + ".json")).read_text(encoding="utf-8"))
|
||||
current = paths()
|
||||
expected = [e["path"] for e in before["files"]]
|
||||
changed = [e["path"] for e in before["files"] if sha(data(e["path"])) != e["sha256"]]
|
||||
result = {"atUtc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"changed": changed, "added": sorted(set(current) - set(expected)),
|
||||
"removed": sorted(set(expected) - set(current))}
|
||||
result["passed"] = not any(result[k] for k in ("changed", "added", "removed"))
|
||||
write(evidence / (label + "-readback.json"), result)
|
||||
print(json.dumps(result))
|
||||
sys.exit(0 if result["passed"] else 1)
|
||||
else:
|
||||
raise ValueError(mode)
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"started": "2026-09-11T19:38:11.8128387Z",
|
||||
"finished": "2026-09-11T19:40:12.0873967Z",
|
||||
"exitCode": 0,
|
||||
"args": [
|
||||
"clippy",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 1m 59s
|
||||
@@ -0,0 +1,927 @@
|
||||
diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs
|
||||
index 7e5eee2..35359a8 100644
|
||||
--- a/crates/oa-desktop/src/app.rs
|
||||
+++ b/crates/oa-desktop/src/app.rs
|
||||
@@ -424,6 +424,7 @@ pub enum Message {
|
||||
SourceSymbolsReady(crate::source_navigation::Completion),
|
||||
SourceForSelection,
|
||||
SourceNavigation(crate::source_navigation::Event),
|
||||
+ SourceFindClose(crate::source_navigation::SymbolContext, Id),
|
||||
Save(bool),
|
||||
SavePath(Option<PathBuf>),
|
||||
Saved(Result<Option<FileSnapshot>, SaveFailure>),
|
||||
@@ -497,6 +498,7 @@ pub struct App {
|
||||
pub source_jump: Option<text_editor::Cursor>,
|
||||
source_select_line: bool,
|
||||
pub source_navigation: crate::source_navigation::State,
|
||||
+ source_find_instance: Id,
|
||||
pub source_symbols: crate::source_navigation::SymbolState,
|
||||
pub model_issues: Vec<validation::Issue>,
|
||||
pub migration: Option<persistence::MigrationReport>,
|
||||
@@ -603,6 +605,7 @@ impl App {
|
||||
source_jump: None,
|
||||
source_select_line: false,
|
||||
source_navigation: crate::source_navigation::State::default(),
|
||||
+ source_find_instance: Id::new_v4(),
|
||||
source_symbols: crate::source_navigation::SymbolState::default(),
|
||||
model_issues,
|
||||
migration: None,
|
||||
@@ -1058,7 +1061,7 @@ impl App {
|
||||
self.behavior.creation.has_pending()
|
||||
|| self.behavior.source_active()
|
||||
|| (self.page == Page::Text
|
||||
- && self.source_symbols.open
|
||||
+ && (self.source_symbols.open || self.source_navigation.open)
|
||||
&& self.architecture.is_none()),
|
||||
)
|
||||
.map(|()| Message::Escape),
|
||||
@@ -1106,6 +1109,23 @@ impl App {
|
||||
])
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
+ let message = match message {
|
||||
+ Message::SourceFindClose(context, instance) => {
|
||||
+ if context != self.source_symbol_context()
|
||||
+ || instance != self.source_find_instance
|
||||
+ || !self.source_navigation.open
|
||||
+ || !self.source_navigation_available()
|
||||
+ {
|
||||
+ return Task::none();
|
||||
+ }
|
||||
+ // Only the checked envelope may enter the legacy Close handler.
|
||||
+ Message::SourceNavigation(crate::source_navigation::Event::Close)
|
||||
+ }
|
||||
+ Message::SourceNavigation(crate::source_navigation::Event::Close) => {
|
||||
+ return Task::none();
|
||||
+ }
|
||||
+ message => message,
|
||||
+ };
|
||||
if let Message::SourceSymbols(context, _) = &message
|
||||
&& (context.session != self.source_workspace.session()
|
||||
|| context.path != self.source_workspace.active)
|
||||
@@ -1838,6 +1858,8 @@ impl App {
|
||||
return self.update(Message::Add(child));
|
||||
}
|
||||
Message::Escape => {
|
||||
+ let restore_source_focus = self.source_navigation_available()
|
||||
+ && (self.source_navigation.open || self.source_symbols.open);
|
||||
if self.behavior.source_back() {
|
||||
self.status =
|
||||
"Returned to the unchanged parent form; no source was created.".into();
|
||||
@@ -1857,6 +1879,9 @@ impl App {
|
||||
self.source_symbols.open = false;
|
||||
self.source_symbols.results = None;
|
||||
self.source_symbols.notice = None;
|
||||
+ if restore_source_focus {
|
||||
+ self.restore_source_editor_focus();
|
||||
+ }
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.selected_bend = None;
|
||||
}
|
||||
@@ -2971,6 +2996,7 @@ impl App {
|
||||
}
|
||||
Message::SourceRelated(location) => self.jump_to_source(location),
|
||||
Message::SourceViewport(ticket) => return self.source_viewport(ticket),
|
||||
+ Message::SourceFindClose(..) => return Task::none(), // update checks and unwraps it
|
||||
Message::SourceSymbols(_, event) => return self.source_symbol_event(event),
|
||||
Message::SourceSymbolsReady(completion) => {
|
||||
return self.source_symbols_ready(completion);
|
||||
@@ -3019,6 +3045,7 @@ impl App {
|
||||
return Task::none();
|
||||
}
|
||||
self.page = Page::Text;
|
||||
+ self.source_find_instance = Id::new_v4();
|
||||
self.source_navigation.open = true;
|
||||
self.source_navigation.refresh(&self.source_editor.text());
|
||||
return iced::widget::operation::focus(if go_to_line {
|
||||
@@ -3027,7 +3054,13 @@ impl App {
|
||||
"source-find"
|
||||
});
|
||||
}
|
||||
- Event::Close => self.source_navigation.open = false,
|
||||
+ Event::Close => {
|
||||
+ let was_open = self.source_navigation.open;
|
||||
+ self.source_navigation.open = false;
|
||||
+ if was_open {
|
||||
+ self.restore_source_editor_focus();
|
||||
+ }
|
||||
+ }
|
||||
Event::Query(query) => {
|
||||
self.source_navigation.query = query;
|
||||
self.source_navigation.refresh(&self.source_editor.text());
|
||||
diff --git a/crates/oa-desktop/src/app/source_symbols.rs b/crates/oa-desktop/src/app/source_symbols.rs
|
||||
index 9516a2b..20d0ce5 100644
|
||||
--- a/crates/oa-desktop/src/app/source_symbols.rs
|
||||
+++ b/crates/oa-desktop/src/app/source_symbols.rs
|
||||
@@ -41,6 +41,10 @@ impl App {
|
||||
pub fn source_symbol_message(&self, event: nav::SymbolEvent) -> Message {
|
||||
Message::SourceSymbols(self.source_symbol_context(), event)
|
||||
}
|
||||
+
|
||||
+ pub fn source_find_close_message(&self) -> Message {
|
||||
+ Message::SourceFindClose(self.source_symbol_context(), self.source_find_instance)
|
||||
+ }
|
||||
pub fn source_key(&self) -> query::SnapshotKey {
|
||||
query::SnapshotKey {
|
||||
workspace: self.source_workspace.session(),
|
||||
@@ -134,6 +138,15 @@ impl App {
|
||||
});
|
||||
}
|
||||
|
||||
+ pub(super) fn restore_source_editor_focus(&mut self) {
|
||||
+ let cursor = self.source_editor.cursor();
|
||||
+ if self.source_navigation_available()
|
||||
+ && nav::exact_cursor(self.source_workspace.text(), cursor)
|
||||
+ {
|
||||
+ self.queue_source_reveal(cursor, false);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
pub(super) fn source_viewport(&mut self, ticket: Option<Id>) -> Task<Message> {
|
||||
let Some(reveal) = &self.source_symbols.reveal else {
|
||||
return Task::none();
|
||||
@@ -180,8 +193,10 @@ impl App {
|
||||
self.source_symbols.notice = None;
|
||||
}
|
||||
nav::SymbolEvent::Back => {
|
||||
- self.source_symbols.open = true;
|
||||
self.source_symbol_back();
|
||||
+ // A successful Back needs no empty results panel. Retain an
|
||||
+ // already open panel, or reveal an actionable return notice.
|
||||
+ self.source_symbols.open |= self.source_symbols.notice.is_some();
|
||||
}
|
||||
nav::SymbolEvent::Choose {
|
||||
stamp,
|
||||
diff --git a/crates/oa-desktop/src/app/source_symbols/tests.rs b/crates/oa-desktop/src/app/source_symbols/tests.rs
|
||||
index 2c3f342..6168e73 100644
|
||||
--- a/crates/oa-desktop/src/app/source_symbols/tests.rs
|
||||
+++ b/crates/oa-desktop/src/app/source_symbols/tests.rs
|
||||
@@ -461,6 +461,10 @@ fn escape_dismisses_modal_before_underlying_source_results() {
|
||||
app.hosted.visible = true;
|
||||
send(&mut app, Message::Escape);
|
||||
assert!(!app.hosted.visible);
|
||||
+ assert!(
|
||||
+ app.source_reveal_ticket().is_none(),
|
||||
+ "modal dismissal must not focus the editor"
|
||||
+ );
|
||||
assert!(app.source_symbols.open);
|
||||
assert!(Arc::ptr_eq(
|
||||
app.source_symbols.results.as_ref().unwrap(),
|
||||
@@ -473,6 +477,303 @@ fn escape_dismisses_modal_before_underlying_source_results() {
|
||||
assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
}
|
||||
|
||||
+#[test]
|
||||
+fn closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice() {
|
||||
+ for changed in [false, true] {
|
||||
+ let mut app = fixture();
|
||||
+ let accepted = app.document.snapshot();
|
||||
+ let origin = app.source_editor.cursor();
|
||||
+ finish(&mut app, query::Action::Definition);
|
||||
+ reveal(&mut app);
|
||||
+ symbol(&mut app, nav::SymbolEvent::Close);
|
||||
+ assert!(!app.source_symbols.open && app.source_symbols.results.is_none());
|
||||
+ if changed {
|
||||
+ // Use the workspace edit API to give the origin a new generation.
|
||||
+ show(&mut app, USES);
|
||||
+ let text = app.source_workspace.text().to_owned();
|
||||
+ send(
|
||||
+ &mut app,
|
||||
+ Message::SourceEdit(text_editor::Action::SelectAll),
|
||||
+ );
|
||||
+ send(
|
||||
+ &mut app,
|
||||
+ Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Paste(
|
||||
+ Arc::new(format!("{text}// current cursor\r\n")),
|
||||
+ ))),
|
||||
+ );
|
||||
+ show(&mut app, DEFINITIONS);
|
||||
+ }
|
||||
+ let raw = app.source_workspace.bundle();
|
||||
+ symbol(&mut app, nav::SymbolEvent::Back);
|
||||
+ assert_eq!(app.source_workspace.active, USES);
|
||||
+ assert_eq!(app.source_symbols.open, changed);
|
||||
+ assert_eq!(app.source_symbols.notice.is_some(), changed);
|
||||
+ reveal(&mut app);
|
||||
+ if !changed {
|
||||
+ assert_eq!(app.source_editor.cursor(), origin);
|
||||
+ }
|
||||
+ assert_eq!(app.source_workspace.bundle(), raw);
|
||||
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
+ assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
+ }
|
||||
+ let mut app = fixture();
|
||||
+ symbol(&mut app, nav::SymbolEvent::Back);
|
||||
+ assert!(app.source_symbols.open && app.source_symbols.notice.is_some());
|
||||
+}
|
||||
+
|
||||
+#[test]
|
||||
+fn find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection() {
|
||||
+ let mut app = fixture();
|
||||
+ let accepted = app.document.snapshot();
|
||||
+ let raw = app.source_workspace.bundle();
|
||||
+ let cursor = app.source_editor.cursor();
|
||||
+ app.source_navigation.open = true;
|
||||
+ app.source_navigation.query = "送水 🚀".into();
|
||||
+ assert!(!app.source_symbols.open);
|
||||
+ let mut messages = captured_input_subscription_messages(&app);
|
||||
+ assert_eq!(messages.len(), 1);
|
||||
+ assert!(matches!(messages[0], Message::Escape));
|
||||
+ send(&mut app, messages.remove(0));
|
||||
+ assert!(!app.source_navigation.open && !app.source_symbols.open);
|
||||
+ assert_eq!(app.source_navigation.query, "送水 🚀");
|
||||
+ assert!(app.source_reveal_ticket().is_some());
|
||||
+ reveal(&mut app);
|
||||
+ assert_eq!(app.source_editor.cursor(), cursor);
|
||||
+ assert!(captured_input_subscription_messages(&app).is_empty());
|
||||
+ assert_eq!(app.source_workspace.bundle(), raw);
|
||||
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
+ assert!(!app.document.can_undo() && !app.document.can_redo());
|
||||
+}
|
||||
+
|
||||
+#[test]
|
||||
+fn old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent() {
|
||||
+ for replacement in 0..3 {
|
||||
+ let mut app = fixture();
|
||||
+ if replacement == 0 {
|
||||
+ show(&mut app, "project.arcadia");
|
||||
+ }
|
||||
+ send(&mut app, Message::SourceNavigation(nav::Event::Open(false)));
|
||||
+ let old_context = app.source_symbol_context();
|
||||
+ let old_instance = app.source_find_instance;
|
||||
+ let old_close = app.source_find_close_message();
|
||||
+ match replacement {
|
||||
+ 0 => {
|
||||
+ let project = app.project().clone();
|
||||
+ app.reset(project, None);
|
||||
+ app.page = Page::Text;
|
||||
+ assert_eq!(app.source_workspace.active, old_context.path);
|
||||
+ assert_ne!(app.source_workspace.session(), old_context.session);
|
||||
+ }
|
||||
+ 1 => {
|
||||
+ show(&mut app, DEFINITIONS);
|
||||
+ assert_eq!(app.source_workspace.session(), old_context.session);
|
||||
+ assert_ne!(app.source_workspace.active, old_context.path);
|
||||
+ }
|
||||
+ 2 => {
|
||||
+ send(&mut app, old_close.clone());
|
||||
+ assert!(!app.source_navigation.open);
|
||||
+ assert!(app.source_reveal_ticket().is_some());
|
||||
+ assert_eq!(app.source_symbol_context(), old_context);
|
||||
+ }
|
||||
+ _ => unreachable!(),
|
||||
+ }
|
||||
+ send(&mut app, Message::SourceNavigation(nav::Event::Open(false)));
|
||||
+ assert_ne!(app.source_find_instance, old_instance);
|
||||
+ send(
|
||||
+ &mut app,
|
||||
+ Message::SourceNavigation(nav::Event::Query("Current Find 雨".into())),
|
||||
+ );
|
||||
+ let text = app.source_workspace.text();
|
||||
+ let (offset, len) = match replacement {
|
||||
+ 0 => (text.find("project model").unwrap() + "project ".len(), 5),
|
||||
+ 1 => (
|
||||
+ text.find("oa_activity pump").unwrap() + "oa_activity ".len(),
|
||||
+ 4,
|
||||
+ ),
|
||||
+ _ => (text.find("source @pump").unwrap() + "source @".len(), 4),
|
||||
+ };
|
||||
+ let cursor = selection(text, offset, offset + len, true);
|
||||
+ app.source_editor.move_to(cursor);
|
||||
+ let request = begin(&mut app, query::Action::References);
|
||||
+ let ready = completion(&app, request);
|
||||
+ app.queue_source_reveal(cursor, false);
|
||||
+ let find_instance = app.source_find_instance;
|
||||
+ let mut stale = vec![old_close, Message::SourceNavigation(nav::Event::Close)];
|
||||
+ if replacement != 2 {
|
||||
+ // Independently require the session/path guard, even with a current
|
||||
+ // invocation token. A token check alone is not the location contract.
|
||||
+ stale.push(Message::SourceFindClose(old_context, find_instance));
|
||||
+ }
|
||||
+ assert_stale_envelopes_preserve_current(&mut app, &stale);
|
||||
+ assert!(app.source_navigation.open);
|
||||
+ assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
+ assert_eq!(app.source_find_instance, find_instance);
|
||||
+ send(&mut app, Message::SourceSymbolsReady(ready));
|
||||
+ assert_stale_envelopes_preserve_current(&mut app, &stale);
|
||||
+ assert!(app.source_navigation.open);
|
||||
+ assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
+ let current_close = app.source_find_close_message();
|
||||
+ send(&mut app, current_close);
|
||||
+ assert!(!app.source_navigation.open);
|
||||
+ assert_eq!(app.source_navigation.query, "Current Find 雨");
|
||||
+ assert!(app.source_reveal_ticket().is_some());
|
||||
+ reveal(&mut app);
|
||||
+ assert_eq!(app.source_editor.cursor(), cursor);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[tokio::test]
|
||||
+async fn find_close_releases_removed_input_and_guarded_focus_respects_later_input() {
|
||||
+ use iced::advanced::{Layout, layout, widget};
|
||||
+ use iced::futures::StreamExt;
|
||||
+ use iced::widget::{column, text_input};
|
||||
+
|
||||
+ #[derive(Default)]
|
||||
+ struct Focus(Vec<(Option<widget::Id>, bool)>);
|
||||
+ impl widget::Operation for Focus {
|
||||
+ fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
||||
+ visit(self);
|
||||
+ }
|
||||
+ fn focusable(
|
||||
+ &mut self,
|
||||
+ id: Option<&widget::Id>,
|
||||
+ _: iced::Rectangle,
|
||||
+ state: &mut dyn widget::operation::Focusable,
|
||||
+ ) {
|
||||
+ self.0.push((id.cloned(), state.is_focused()));
|
||||
+ }
|
||||
+ }
|
||||
+ fn view(app: &App, later: bool) -> iced::Element<'static, Message, iced::Theme, ()> {
|
||||
+ let mut content = column![
|
||||
+ text_input("Editor", "unchanged source")
|
||||
+ .id(app.source_editor_id())
|
||||
+ .on_input(|s| Message::SourceNavigation(nav::Event::Query(s)))
|
||||
+ ];
|
||||
+ if app.source_navigation.open {
|
||||
+ content = content.push(
|
||||
+ text_input("Find", "送水")
|
||||
+ .id("source-find")
|
||||
+ .on_input(|s| Message::SourceNavigation(nav::Event::Query(s))),
|
||||
+ );
|
||||
+ }
|
||||
+ if later {
|
||||
+ content = content.push(
|
||||
+ text_input("Other", "preserve focus")
|
||||
+ .id("later-input")
|
||||
+ .on_input(|s| Message::SourceNavigation(nav::Event::Query(s))),
|
||||
+ );
|
||||
+ }
|
||||
+ content.into()
|
||||
+ }
|
||||
+ fn run(
|
||||
+ ui: &mut iced::Element<'_, Message, iced::Theme, ()>,
|
||||
+ tree: &mut widget::Tree,
|
||||
+ node: &layout::Node,
|
||||
+ mut op: Box<dyn widget::Operation>,
|
||||
+ ) {
|
||||
+ loop {
|
||||
+ ui.as_widget_mut()
|
||||
+ .operate(tree, Layout::new(node), &(), op.as_mut());
|
||||
+ match op.finish() {
|
||||
+ widget::operation::Outcome::Chain(next) => op = next,
|
||||
+ widget::operation::Outcome::None => break,
|
||||
+ widget::operation::Outcome::Some(()) => panic!("focus must not produce a message"),
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ for escape in [false, true] {
|
||||
+ for later in 0..3 {
|
||||
+ let mut app = fixture();
|
||||
+ app.source_navigation.open = true;
|
||||
+ let accepted = app.document.snapshot();
|
||||
+ let raw = app.source_workspace.bundle();
|
||||
+ let cursor = app.source_editor.cursor();
|
||||
+ let mut ui = view(&app, false);
|
||||
+ let mut tree = widget::Tree::new(ui.as_widget());
|
||||
+ let limits = layout::Limits::new(iced::Size::ZERO, iced::Size::new(500., 250.));
|
||||
+ let node = ui.as_widget_mut().layout(&mut tree, &(), &limits);
|
||||
+ run(
|
||||
+ &mut ui,
|
||||
+ &mut tree,
|
||||
+ &node,
|
||||
+ Box::new(widget::operation::focusable::focus(widget::Id::new(
|
||||
+ "source-find",
|
||||
+ ))),
|
||||
+ );
|
||||
+ let mut focus = Focus::default();
|
||||
+ ui.as_widget_mut()
|
||||
+ .operate(&mut tree, Layout::new(&node), &(), &mut focus);
|
||||
+ assert!(
|
||||
+ focus
|
||||
+ .0
|
||||
+ .contains(&(Some(widget::Id::new("source-find")), true))
|
||||
+ );
|
||||
+
|
||||
+ let close = if escape {
|
||||
+ Message::Escape
|
||||
+ } else {
|
||||
+ app.source_find_close_message()
|
||||
+ };
|
||||
+ send(&mut app, close);
|
||||
+ assert!(!app.source_navigation.open);
|
||||
+ let ticket = app
|
||||
+ .source_reveal_ticket()
|
||||
+ .expect("closing an open Find queues focus");
|
||||
+ ui = view(&app, later != 0);
|
||||
+ tree.diff(ui.as_widget());
|
||||
+ let node = ui.as_widget_mut().layout(&mut tree, &(), &limits);
|
||||
+ let task = app.update(Message::SourceViewport(Some(ticket)));
|
||||
+ if later != 0 {
|
||||
+ run(
|
||||
+ &mut ui,
|
||||
+ &mut tree,
|
||||
+ &node,
|
||||
+ Box::new(widget::operation::focusable::focus(widget::Id::new(
|
||||
+ "later-input",
|
||||
+ ))),
|
||||
+ );
|
||||
+ }
|
||||
+ if later == 2 {
|
||||
+ app.source_symbols
|
||||
+ .raw_interaction
|
||||
+ .fetch_add(1, Ordering::SeqCst);
|
||||
+ }
|
||||
+ let mut actions =
|
||||
+ iced_runtime::task::into_stream(task).expect("guarded widget operation");
|
||||
+ while let Some(action) = actions.next().await {
|
||||
+ let iced_runtime::Action::Widget(op) = action else {
|
||||
+ panic!("focus-only task");
|
||||
+ };
|
||||
+ run(&mut ui, &mut tree, &node, op);
|
||||
+ }
|
||||
+ let mut focus = Focus::default();
|
||||
+ ui.as_widget_mut()
|
||||
+ .operate(&mut tree, Layout::new(&node), &(), &mut focus);
|
||||
+ assert!(
|
||||
+ !focus
|
||||
+ .0
|
||||
+ .iter()
|
||||
+ .any(|(id, _)| id.as_ref() == Some(&widget::Id::new("source-find")))
|
||||
+ );
|
||||
+ assert!(
|
||||
+ focus
|
||||
+ .0
|
||||
+ .contains(&(Some(app.source_editor_id()), later == 0))
|
||||
+ );
|
||||
+ if later != 0 {
|
||||
+ assert!(
|
||||
+ focus
|
||||
+ .0
|
||||
+ .contains(&(Some(widget::Id::new("later-input")), true))
|
||||
+ );
|
||||
+ }
|
||||
+ assert_eq!(app.source_editor.cursor(), cursor);
|
||||
+ assert_eq!(app.source_workspace.bundle(), raw);
|
||||
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/// Feed the real App subscription recipes the status produced by a mounted
|
||||
/// TextInput. Poll only immediately available output: no timer wait, worker,
|
||||
/// file dialog or App task is executed by this helper.
|
||||
diff --git a/crates/oa-desktop/src/views.rs b/crates/oa-desktop/src/views.rs
|
||||
index 1a74bc6..e91892e 100644
|
||||
--- a/crates/oa-desktop/src/views.rs
|
||||
+++ b/crates/oa-desktop/src/views.rs
|
||||
@@ -1020,7 +1020,7 @@ fn source(app: &App) -> View<'_, Message> {
|
||||
),
|
||||
action("Next", Message::SourceNavigation(SourceEvent::Next(false))),
|
||||
label(app.source_navigation.label()),
|
||||
- action("Close", Message::SourceNavigation(SourceEvent::Close)),
|
||||
+ action("Close", app.source_find_close_message()),
|
||||
]
|
||||
.spacing(6)
|
||||
.align_y(iced::Center),
|
||||
@@ -1208,8 +1208,18 @@ fn source(app: &App) -> View<'_, Message> {
|
||||
} else {
|
||||
container(editor).width(Fill).height(Fill).into()
|
||||
};
|
||||
+ source_layout(header.into(), workspace, app.window_size.height)
|
||||
+}
|
||||
+
|
||||
+fn source_layout<'a, Renderer: iced::advanced::text::Renderer + 'a>(
|
||||
+ header: View<'a, Message, iced::Theme, Renderer>,
|
||||
+ workspace: View<'a, Message, iced::Theme, Renderer>,
|
||||
+ window_height: f32,
|
||||
+) -> View<'a, Message, iced::Theme, Renderer> {
|
||||
+ // Lint clears and repopulates diagnostics asynchronously. Reserve the same
|
||||
+ // outer slot so a pointer aimed at the editor cannot land in the header.
|
||||
column![
|
||||
- container(scrollable(header)).max_height((app.window_size.height * 0.42).max(180.)),
|
||||
+ container(scrollable(header).id("source-header")).height((window_height * 0.42).max(180.)),
|
||||
workspace,
|
||||
]
|
||||
.height(Fill)
|
||||
@@ -1222,6 +1232,19 @@ fn source_binding(
|
||||
use crate::source_navigation::{SymbolEvent, query::Action};
|
||||
use iced::keyboard::{Key, key::Named};
|
||||
use widget::text_editor::{Binding, Status};
|
||||
+ // Windows owns Alt+Space for its window menu. Iced's default binding would
|
||||
+ // otherwise insert a supplied space payload, even after that menu closes.
|
||||
+ // Keep Ctrl+Alt (AltGr), shifted input, and ordinary Space unchanged.
|
||||
+ #[cfg(target_os = "windows")]
|
||||
+ if matches!(press.status, Status::Focused { .. })
|
||||
+ && press.modifiers == iced::keyboard::Modifiers::ALT
|
||||
+ && matches!(
|
||||
+ press.key.as_ref(),
|
||||
+ Key::Named(Named::Space) | Key::Character(" ")
|
||||
+ )
|
||||
+ {
|
||||
+ return None;
|
||||
+ }
|
||||
if matches!(press.status, Status::Focused { .. })
|
||||
&& !press.modifiers.control()
|
||||
&& !press.modifiers.logo()
|
||||
@@ -1248,6 +1271,400 @@ fn source_binding(
|
||||
}
|
||||
Binding::from_key_press(press)
|
||||
}
|
||||
+
|
||||
+#[cfg(all(test, debug_assertions))]
|
||||
+mod source_tests {
|
||||
+ use super::*;
|
||||
+ use iced::advanced::{
|
||||
+ Layout, Shell, graphics, layout, renderer, text as shaped, widget as core_widget,
|
||||
+ };
|
||||
+ use iced::widget::column;
|
||||
+ use iced::{Background, Color, Pixels, Point, Rectangle, Size, Transformation, Vector};
|
||||
+
|
||||
+ // Measure real Iced paragraphs/editor text without constructing a window or GPU.
|
||||
+ struct FontLayout;
|
||||
+ impl renderer::Renderer for FontLayout {
|
||||
+ fn start_layer(&mut self, _: Rectangle) {}
|
||||
+ fn end_layer(&mut self) {}
|
||||
+ fn start_transformation(&mut self, _: Transformation) {}
|
||||
+ fn end_transformation(&mut self) {}
|
||||
+ fn reset(&mut self, _: Rectangle) {}
|
||||
+ fn fill_quad(&mut self, _: renderer::Quad, _: impl Into<Background>) {}
|
||||
+ fn allocate_image(
|
||||
+ &mut self,
|
||||
+ handle: &iced::advanced::image::Handle,
|
||||
+ callback: impl FnOnce(
|
||||
+ Result<iced::advanced::image::Allocation, iced::advanced::image::Error>,
|
||||
+ ) + Send
|
||||
+ + 'static,
|
||||
+ ) {
|
||||
+ renderer::Renderer::allocate_image(&mut (), handle, callback);
|
||||
+ }
|
||||
+ }
|
||||
+ impl shaped::Renderer for FontLayout {
|
||||
+ type Font = Font;
|
||||
+ type Paragraph = graphics::text::Paragraph;
|
||||
+ type Editor = graphics::text::Editor;
|
||||
+ const ICON_FONT: Font = Font::DEFAULT;
|
||||
+ const CHECKMARK_ICON: char = '0';
|
||||
+ const ARROW_DOWN_ICON: char = '0';
|
||||
+ const SCROLL_UP_ICON: char = '0';
|
||||
+ const SCROLL_DOWN_ICON: char = '0';
|
||||
+ const SCROLL_LEFT_ICON: char = '0';
|
||||
+ const SCROLL_RIGHT_ICON: char = '0';
|
||||
+ const ICED_LOGO: char = '0';
|
||||
+ fn default_font(&self) -> Font {
|
||||
+ Font::DEFAULT
|
||||
+ }
|
||||
+ fn default_size(&self) -> Pixels {
|
||||
+ Pixels(16.)
|
||||
+ }
|
||||
+ fn fill_paragraph(&mut self, _: &Self::Paragraph, _: Point, _: Color, _: Rectangle) {}
|
||||
+ fn fill_editor(&mut self, _: &Self::Editor, _: Point, _: Color, _: Rectangle) {}
|
||||
+ fn fill_text(&mut self, _: shaped::Text, _: Point, _: Color, _: Rectangle) {}
|
||||
+ }
|
||||
+
|
||||
+ #[derive(Default)]
|
||||
+ struct Geometry {
|
||||
+ header: Option<(Rectangle, Rectangle, Vector)>,
|
||||
+ editor: Option<Rectangle>,
|
||||
+ }
|
||||
+ impl core_widget::Operation for Geometry {
|
||||
+ fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn core_widget::Operation)) {
|
||||
+ visit(self);
|
||||
+ }
|
||||
+ fn container(&mut self, id: Option<&core_widget::Id>, bounds: Rectangle) {
|
||||
+ if id == Some(&core_widget::Id::new("test-source-editor")) {
|
||||
+ self.editor = Some(bounds);
|
||||
+ }
|
||||
+ }
|
||||
+ fn scrollable(
|
||||
+ &mut self,
|
||||
+ id: Option<&core_widget::Id>,
|
||||
+ bounds: Rectangle,
|
||||
+ content: Rectangle,
|
||||
+ offset: Vector,
|
||||
+ _: &mut dyn core_widget::operation::Scrollable,
|
||||
+ ) {
|
||||
+ if id == Some(&core_widget::Id::new("source-header")) {
|
||||
+ self.header = Some((bounds, content, offset));
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ fn mounted_source<'a>(
|
||||
+ content: &'a widget::text_editor::Content<FontLayout>,
|
||||
+ diagnostics: usize,
|
||||
+ find: bool,
|
||||
+ window_height: f32,
|
||||
+ ) -> View<'a, Message, iced::Theme, FontLayout> {
|
||||
+ let mut notices = column![].spacing(5);
|
||||
+ for index in 0..diagnostics {
|
||||
+ notices = notices.push(button(text(format!(
|
||||
+ "救援/assess.arcadia:{}:5 · Warning: verify this participant's engineering allocation", index + 1,
|
||||
+ )).size(12)).padding([4, 6]).width(Fill));
|
||||
+ }
|
||||
+ let find: View<'_, Message, iced::Theme, FontLayout> = if find {
|
||||
+ column![
|
||||
+ text_input("Find literal text", "assess").on_input(|s| Message::SourceNavigation(
|
||||
+ crate::source_navigation::Event::Query(s)
|
||||
+ )),
|
||||
+ text_input("Line", "1").on_input(|s| Message::SourceNavigation(
|
||||
+ crate::source_navigation::Event::Line(s)
|
||||
+ )),
|
||||
+ ]
|
||||
+ .spacing(8)
|
||||
+ .into()
|
||||
+ } else {
|
||||
+ Space::new().into()
|
||||
+ };
|
||||
+ let header = container(
|
||||
+ column![
|
||||
+ text("Textual model").size(24),
|
||||
+ text("Edit the current file. Apply text validates the complete included model.")
|
||||
+ .size(11),
|
||||
+ row![
|
||||
+ button(text("Apply text")),
|
||||
+ button(text("Undo text")),
|
||||
+ button(text("Model Undo"))
|
||||
+ ]
|
||||
+ .spacing(8),
|
||||
+ text("救援/assess.arcadia · All files applied to the accepted model").size(11),
|
||||
+ text("Undo text affects this draft. Model Undo restores accepted files together.")
|
||||
+ .size(11),
|
||||
+ find,
|
||||
+ row![
|
||||
+ button(text("Definition · F12")),
|
||||
+ button(text("References · Shift F12")),
|
||||
+ button(text("Back"))
|
||||
+ ]
|
||||
+ .spacing(7),
|
||||
+ scrollable(notices).height((diagnostics as f32 * 32.).min(132.)),
|
||||
+ ]
|
||||
+ .spacing(12),
|
||||
+ )
|
||||
+ .padding(18);
|
||||
+ let editor = container(
|
||||
+ widget::text_editor(content)
|
||||
+ .on_action(Message::SourceEdit)
|
||||
+ .font(Font::MONOSPACE)
|
||||
+ .size(14)
|
||||
+ .padding(16)
|
||||
+ .height(Fill),
|
||||
+ )
|
||||
+ .id("test-source-editor")
|
||||
+ .height(Fill)
|
||||
+ .width(Fill);
|
||||
+ source_layout(header.into(), editor.into(), window_height)
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds() {
|
||||
+ for (host, window_height) in [
|
||||
+ (Size::new(720., 540.), 700.),
|
||||
+ (Size::new(1180., 890.), 1080.),
|
||||
+ ] {
|
||||
+ let content = widget::text_editor::Content::<FontLayout>::with_text(
|
||||
+ "// is excluded\r\n// 雨\r\n",
|
||||
+ );
|
||||
+ let mut tree = core_widget::Tree::empty();
|
||||
+ let mut baseline = None;
|
||||
+ for (diagnostics, find) in [
|
||||
+ (0, false),
|
||||
+ (6, false),
|
||||
+ (0, false),
|
||||
+ (6, true),
|
||||
+ (1, true),
|
||||
+ (0, false),
|
||||
+ ] {
|
||||
+ let mut ui = mounted_source(&content, diagnostics, find, window_height);
|
||||
+ tree.diff(ui.as_widget());
|
||||
+ let node = ui.as_widget_mut().layout(
|
||||
+ &mut tree,
|
||||
+ &FontLayout,
|
||||
+ &layout::Limits::new(Size::ZERO, host),
|
||||
+ );
|
||||
+ let mut geometry = Geometry::default();
|
||||
+ ui.as_widget_mut().operate(
|
||||
+ &mut tree,
|
||||
+ Layout::new(&node),
|
||||
+ &FontLayout,
|
||||
+ &mut geometry,
|
||||
+ );
|
||||
+ let editor = geometry.editor.unwrap();
|
||||
+ let (header, _, _) = geometry.header.unwrap();
|
||||
+ let slot = node.children()[0].bounds();
|
||||
+ assert_eq!(slot.height, (window_height * 0.42).max(180.));
|
||||
+ assert!(
|
||||
+ header.height <= slot.height,
|
||||
+ "short content may shrink inside the fixed slot"
|
||||
+ );
|
||||
+ assert!(
|
||||
+ editor.height >= 240.,
|
||||
+ "remaining editor must stay usable: {editor:?}"
|
||||
+ );
|
||||
+ assert_eq!(editor.y, slot.y + slot.height);
|
||||
+ if let Some(expected) = baseline {
|
||||
+ assert_eq!(editor, expected, "diagnostics={diagnostics}, find={find}");
|
||||
+ } else {
|
||||
+ baseline = Some(editor);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ #[test]
|
||||
+ fn source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh() {
|
||||
+ let content = widget::text_editor::Content::<FontLayout>::with_text("// 雨\r\n");
|
||||
+ let host = Size::new(720., 540.);
|
||||
+ let mut tree = core_widget::Tree::empty();
|
||||
+ let mut saved = None;
|
||||
+ for diagnostics in [6, 7] {
|
||||
+ let mut ui = mounted_source(&content, diagnostics, true, 700.);
|
||||
+ tree.diff(ui.as_widget());
|
||||
+ let node = ui.as_widget_mut().layout(
|
||||
+ &mut tree,
|
||||
+ &FontLayout,
|
||||
+ &layout::Limits::new(Size::ZERO, host),
|
||||
+ );
|
||||
+ let mut geometry = Geometry::default();
|
||||
+ ui.as_widget_mut()
|
||||
+ .operate(&mut tree, Layout::new(&node), &FontLayout, &mut geometry);
|
||||
+ let (bounds, contents, _) = geometry.header.unwrap();
|
||||
+ assert!(
|
||||
+ contents.height > bounds.height,
|
||||
+ "fixture must actually overflow"
|
||||
+ );
|
||||
+ if saved.is_none() {
|
||||
+ let mut messages = Vec::new();
|
||||
+ let mut shell = Shell::new(&mut messages);
|
||||
+ ui.as_widget_mut().update(
|
||||
+ &mut tree,
|
||||
+ &iced::Event::Mouse(iced::mouse::Event::WheelScrolled {
|
||||
+ delta: iced::mouse::ScrollDelta::Pixels { x: 0., y: -45. },
|
||||
+ }),
|
||||
+ Layout::new(&node),
|
||||
+ iced::mouse::Cursor::Available(Point::new(bounds.x + 8., bounds.y + 8.)),
|
||||
+ &FontLayout,
|
||||
+ &mut iced::advanced::clipboard::Null,
|
||||
+ &mut shell,
|
||||
+ &Rectangle::with_size(host),
|
||||
+ );
|
||||
+ assert!(shell.is_event_captured());
|
||||
+ }
|
||||
+ let mut geometry = Geometry::default();
|
||||
+ ui.as_widget_mut()
|
||||
+ .operate(&mut tree, Layout::new(&node), &FontLayout, &mut geometry);
|
||||
+ let offset = geometry.header.unwrap().2;
|
||||
+ assert!(offset.y > 0., "the actual mounted Scrollable must move");
|
||||
+ if let Some(before) = saved {
|
||||
+ assert_eq!(offset, before);
|
||||
+ } else {
|
||||
+ saved = Some(offset);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ #[cfg(target_os = "windows")]
|
||||
+ #[test]
|
||||
+ fn source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit() {
|
||||
+ use core_widget::operation::Focusable;
|
||||
+ use iced::keyboard::{
|
||||
+ self, Key, Modifiers,
|
||||
+ key::{Code, Named, Physical},
|
||||
+ };
|
||||
+ use widget::text_editor::{Binding, Content, KeyPress, State, Status};
|
||||
+ let baseline = "// is excluded\r\n// 雨\r\n";
|
||||
+ let context = crate::source_navigation::SymbolContext {
|
||||
+ session: Id::new_v4(),
|
||||
+ path: "救援/assess.arcadia".into(),
|
||||
+ };
|
||||
+ for (key, physical_key, modifiers, payload, inserted) in [
|
||||
+ (
|
||||
+ Key::Named(Named::Space),
|
||||
+ Physical::Code(Code::Space),
|
||||
+ Modifiers::ALT,
|
||||
+ " ",
|
||||
+ false,
|
||||
+ ),
|
||||
+ (
|
||||
+ Key::Character(" ".into()),
|
||||
+ Physical::Code(Code::Space),
|
||||
+ Modifiers::ALT,
|
||||
+ " ",
|
||||
+ false,
|
||||
+ ),
|
||||
+ (
|
||||
+ Key::Named(Named::Space),
|
||||
+ Physical::Code(Code::Space),
|
||||
+ Modifiers::empty(),
|
||||
+ " ",
|
||||
+ true,
|
||||
+ ),
|
||||
+ (
|
||||
+ Key::Named(Named::Space),
|
||||
+ Physical::Code(Code::Space),
|
||||
+ Modifiers::CTRL | Modifiers::ALT,
|
||||
+ " ",
|
||||
+ true,
|
||||
+ ),
|
||||
+ (
|
||||
+ Key::Character("e".into()),
|
||||
+ Physical::Code(Code::KeyE),
|
||||
+ Modifiers::CTRL | Modifiers::ALT,
|
||||
+ "€",
|
||||
+ true,
|
||||
+ ),
|
||||
+ (
|
||||
+ Key::Named(Named::Space),
|
||||
+ Physical::Code(Code::Space),
|
||||
+ Modifiers::ALT | Modifiers::SHIFT,
|
||||
+ " ",
|
||||
+ true,
|
||||
+ ),
|
||||
+ ] {
|
||||
+ let mut content = Content::<FontLayout>::with_text(baseline);
|
||||
+ content.move_to(widget::text_editor::Cursor {
|
||||
+ position: widget::text_editor::Position { line: 0, column: 5 },
|
||||
+ selection: None,
|
||||
+ });
|
||||
+ let press = KeyPress {
|
||||
+ key: key.clone(),
|
||||
+ modified_key: key.clone(),
|
||||
+ physical_key,
|
||||
+ modifiers,
|
||||
+ text: Some(payload.into()),
|
||||
+ status: Status::Focused { is_hovered: false },
|
||||
+ };
|
||||
+ assert!(
|
||||
+ matches!(
|
||||
+ Binding::<Message>::from_key_press(press),
|
||||
+ Some(Binding::Insert(_))
|
||||
+ ),
|
||||
+ "the supplied event must expose the default insertion path"
|
||||
+ );
|
||||
+ let mut messages = Vec::new();
|
||||
+ {
|
||||
+ let mut editor: View<'_, Message, iced::Theme, FontLayout> =
|
||||
+ widget::text_editor(&content)
|
||||
+ .on_action(Message::SourceEdit)
|
||||
+ .font(Font::MONOSPACE)
|
||||
+ .size(14)
|
||||
+ .padding(16)
|
||||
+ .height(Fill)
|
||||
+ .key_binding(|press| source_binding(press, &context))
|
||||
+ .into();
|
||||
+ let mut tree = core_widget::Tree::new(editor.as_widget());
|
||||
+ let size = Size::new(600., 240.);
|
||||
+ let node = editor.as_widget_mut().layout(
|
||||
+ &mut tree,
|
||||
+ &FontLayout,
|
||||
+ &layout::Limits::new(Size::ZERO, size),
|
||||
+ );
|
||||
+ tree.state
|
||||
+ .downcast_mut::<State<shaped::highlighter::PlainText>>()
|
||||
+ .focus();
|
||||
+ let event = iced::Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
+ key: key.clone(),
|
||||
+ modified_key: key,
|
||||
+ physical_key,
|
||||
+ location: keyboard::Location::Standard,
|
||||
+ modifiers,
|
||||
+ text: Some(payload.into()),
|
||||
+ repeat: false,
|
||||
+ });
|
||||
+ let mut shell = Shell::new(&mut messages);
|
||||
+ editor.as_widget_mut().update(
|
||||
+ &mut tree,
|
||||
+ &event,
|
||||
+ Layout::new(&node),
|
||||
+ iced::mouse::Cursor::Unavailable,
|
||||
+ &FontLayout,
|
||||
+ &mut iced::advanced::clipboard::Null,
|
||||
+ &mut shell,
|
||||
+ &Rectangle::with_size(size),
|
||||
+ );
|
||||
+ }
|
||||
+ assert_eq!(
|
||||
+ messages.len(),
|
||||
+ usize::from(inserted),
|
||||
+ "modifiers={modifiers:?}"
|
||||
+ );
|
||||
+ for message in messages {
|
||||
+ let Message::SourceEdit(action) = message else {
|
||||
+ panic!("unexpected navigation action");
|
||||
+ };
|
||||
+ content.perform(action);
|
||||
+ }
|
||||
+ let expected = if inserted {
|
||||
+ format!("// is{payload} excluded\r\n// 雨\r\n")
|
||||
+ } else {
|
||||
+ baseline.into()
|
||||
+ };
|
||||
+ assert_eq!(content.text(), expected);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
fn checks(app: &App) -> View<'_, Message> {
|
||||
let issues = &app.model_issues;
|
||||
let mut out = column![heading(
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:41:09.656362+00:00",
|
||||
"scope": "Focused Windows desktop tests and strict all-target Clippy only; no corrected GUI, native OS payload, full desktop suite, Linux, or release acceptance.",
|
||||
"sourceHead": "f4e426ae2a6971395e1ca7e6f094b0c20056cf93",
|
||||
"uncommittedOwnedInputs": [
|
||||
{
|
||||
"path": "crates/oa-desktop/src/views.rs",
|
||||
"bytes": 67434,
|
||||
"sha256": "e5ed7640fbd2653983c418b88fd2e3612b91f8f02fa2f50f1b5dfd31aab773b2"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/app.rs",
|
||||
"bytes": 214840,
|
||||
"sha256": "411007f8c192e0fc581f3c6bcfc49ab46b900a841ee20ae96203d4db64c9280f"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/app/source_symbols.rs",
|
||||
"bytes": 18627,
|
||||
"sha256": "99370865b548b28f32f4c6ce7e9f8eeaa0c80268a644f2ebcc4bd1e11695e59c"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/app/source_symbols/tests.rs",
|
||||
"bytes": 57781,
|
||||
"sha256": "33292a7ce9b61dbc134f74372caa93af9704e2f968cd698da13ea6a1b8cf3ea6"
|
||||
}
|
||||
],
|
||||
"inputs": "inputs4",
|
||||
"inputFiles": 499,
|
||||
"inputBytes": 9470838,
|
||||
"archiveSha256": "7ff9e5703e33bd72e5c2186dd61125e965b82b8dfbf085961d514b005e716798",
|
||||
"sourceZipAndCurrentBytesMatch": true,
|
||||
"gates": {
|
||||
"focused5": {
|
||||
"started": "2026-09-11T19:35:53.3464146Z",
|
||||
"finished": "2026-09-11T19:38:11.7593163Z",
|
||||
"exitCode": 0,
|
||||
"args": [
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"source_",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"buildJobs": 1
|
||||
},
|
||||
"clippy5": {
|
||||
"started": "2026-09-11T19:38:11.8128387Z",
|
||||
"finished": "2026-09-11T19:40:12.0873967Z",
|
||||
"exitCode": 0,
|
||||
"args": [
|
||||
"clippy",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--all-targets",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
},
|
||||
"tests": {
|
||||
"passed": 188,
|
||||
"failed": 0,
|
||||
"ignored": 1,
|
||||
"measured": 0,
|
||||
"filtered": 562
|
||||
},
|
||||
"testExecutionSeconds": 41.73,
|
||||
"requiredRegressionTestsPassed": [
|
||||
"closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice",
|
||||
"find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection",
|
||||
"old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent",
|
||||
"find_close_releases_removed_input_and_guarded_focus_respects_later_input",
|
||||
"source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds",
|
||||
"source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh",
|
||||
"source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Verify frozen gate receipts and source ZIP; never infer a native/full-suite pass."""
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
base = Path(__file__).resolve().parent
|
||||
root = base.parents[1]
|
||||
inputs, focused, clippy = sys.argv[1:]
|
||||
assert re.fullmatch(r"inputs[0-9]+", inputs)
|
||||
assert re.fullmatch(r"focused[0-9]+", focused)
|
||||
assert re.fullmatch(r"clippy[0-9]+", clippy)
|
||||
|
||||
|
||||
def read(name):
|
||||
return json.loads((base / name).read_text(encoding="utf-8-sig"))
|
||||
|
||||
|
||||
def sha(raw):
|
||||
return hashlib.sha256(raw).hexdigest()
|
||||
|
||||
|
||||
manifest = read(inputs + ".json")
|
||||
readback = read(inputs + "-readback.json")
|
||||
assert readback["passed"] and not any(readback[k] for k in ("changed", "added", "removed"))
|
||||
archive = base / (inputs + ".zip")
|
||||
assert sha(archive.read_bytes()) == manifest["archiveSha256"]
|
||||
expected = {item["path"]: item for item in manifest["files"]}
|
||||
with zipfile.ZipFile(archive) as bundle:
|
||||
assert len(bundle.infolist()) == len(expected)
|
||||
assert set(bundle.namelist()) == set(expected)
|
||||
for path, item in expected.items():
|
||||
raw = bundle.read(path)
|
||||
assert len(raw) == item["bytes"] and sha(raw) == item["sha256"], path
|
||||
assert (root / path).read_bytes() == raw, path
|
||||
|
||||
gates = {name: read(name + "-status.json") for name in (focused, clippy)}
|
||||
assert all(gate["exitCode"] == 0 for gate in gates.values())
|
||||
log = (base / (focused + ".log")).read_text(encoding="utf-8-sig")
|
||||
match = re.search(r"test result: ok\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out; finished in ([0-9.]+)s", log)
|
||||
assert match, "No executed successful Rust test summary"
|
||||
counts = dict(zip(("passed", "failed", "ignored", "measured", "filtered"), map(int, match.groups()[:5])))
|
||||
assert counts["failed"] == 0
|
||||
required = [
|
||||
"closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice",
|
||||
"find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection",
|
||||
"old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent",
|
||||
"find_close_releases_removed_input_and_guarded_focus_respects_later_input",
|
||||
"source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds",
|
||||
"source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh",
|
||||
"source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit",
|
||||
]
|
||||
for name in required:
|
||||
assert re.search(r"test [^\r\n]*::" + re.escape(name) + r" \.\.\. ok", log), name
|
||||
|
||||
owned = ["crates/oa-desktop/src/" + path for path in (
|
||||
"views.rs", "app.rs", "app/source_symbols.rs", "app/source_symbols/tests.rs"
|
||||
)]
|
||||
result = {
|
||||
"atUtc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"scope": "Focused Windows desktop tests and strict all-target Clippy only; no corrected GUI, native OS payload, full desktop suite, Linux, or release acceptance.",
|
||||
"sourceHead": manifest["head"],
|
||||
"uncommittedOwnedInputs": [expected[path] for path in owned],
|
||||
"inputs": inputs,
|
||||
"inputFiles": len(expected),
|
||||
"inputBytes": sum(item["bytes"] for item in expected.values()),
|
||||
"archiveSha256": manifest["archiveSha256"],
|
||||
"sourceZipAndCurrentBytesMatch": True,
|
||||
"gates": gates,
|
||||
"tests": counts,
|
||||
"testExecutionSeconds": float(match.group(6)),
|
||||
"requiredRegressionTestsPassed": required,
|
||||
}
|
||||
with (base / "final-gate-result.json").open("x", encoding="utf-8", newline="\n") as stream:
|
||||
json.dump(result, stream, indent=2, ensure_ascii=False)
|
||||
stream.write("\n")
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"started": "2026-09-11T19:41:04.7974359Z",
|
||||
"finished": "2026-09-11T19:41:06.4920379Z",
|
||||
"exitCode": 0,
|
||||
"args": [
|
||||
"--check",
|
||||
"--edition",
|
||||
"2024",
|
||||
"--config",
|
||||
"skip_children=true",
|
||||
"crates/oa-desktop/src/views.rs",
|
||||
"crates/oa-desktop/src/app.rs",
|
||||
"crates/oa-desktop/src/app/source_symbols.rs",
|
||||
"crates/oa-desktop/src/app/source_symbols/tests.rs"
|
||||
]
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"scope": "Transcription of the completed functions exec session response; the failing PowerShell 5 wrapper did not reach its normal status/log writer. This is not a raw OS stderr byte capture.",
|
||||
"sessionId": 39757,
|
||||
"outerExitCode": 1,
|
||||
"output": "cargo.exe : Compiling oa-desktop v0.2.0 (C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-desktop)\r\nAt C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-fixes-20260911\\run-focused1.ps1:14 char:5\r\n+ & cargo @gateArgs 2>&1 | Tee-Object -FilePath $log\r\n+ ~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : NotSpecified: ( Compiling oa...tes\\oa-desktop):String) [], RemoteException\r\n + FullyQualifiedErrorId : NativeCommandError\r\n \r\n"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
2026-09-11: the explicit nested Windows PowerShell invocation terminated at Cargo's first ordinary stderr compilation message, because ErrorActionPreference=Stop treated merged native stderr as NativeCommandError. functions exec session 39757 returned exit 1. No focused test or Clippy terminal result was produced. Subsequent process inventory found no remaining cargo/rustc/clippy process. Production inputs1 remain frozen; the correction is to the private launcher only.
|
||||
|
||||
cargo.exe : Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
At ...\run-focused1.ps1:14 char:5
|
||||
+ & cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
CategoryInfo: NotSpecified / RemoteException
|
||||
FullyQualifiedErrorId: NativeCommandError
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"started": "2026-09-11T19:15:25.1328926Z",
|
||||
"finished": "2026-09-11T19:17:27.0373185Z",
|
||||
"exitCode": 101,
|
||||
"args": [
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"source_",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"ProcessId": 6124,
|
||||
"ParentProcessId": 7424,
|
||||
"Name": "rustc.exe",
|
||||
"CommandLine": "\"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\rustup\\toolchains\\stable-x86_64-pc-windows-gnullvm\\bin\\rustc.exe\" --crate-name arcadia_studio --edition=2024 crates\\oa-desktop\\src\\main.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --emit=dep-info,link -C embed-bitcode=no --test --check-cfg cfg(docsrs,test) --check-cfg \"cfg(feature, values())\" -C metadata=e92d7ab29d07ce2a -C extra-filename=-58e30cee1fd51a8b --out-dir C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps -C linker=clang.exe -C incremental=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\incremental -C strip=debuginfo -L dependency=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps --extern axum=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libaxum-ccc7aea466db9258.rlib --extern iced=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libiced-ee73abd722c72024.rlib --extern iced_runtime=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libiced_runtime-97354eb606c5cbe8.rlib --extern oa_core=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-6c8da57367eec642.rlib --extern oa_server=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_server-69305e51c40cee45.rlib --extern reqwest=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libreqwest-31ac987472fa8038.rlib --extern rfd=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\librfd-17cc7ba2db223d7b.rlib --extern serde=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde-60a32311146a42e4.rlib --extern serde_json=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-da2c4f333fc47818.rlib --extern tempfile=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libtempfile-86aac28d6be16d2f.rlib --extern tokio=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libtokio-5e0ca96735e3073a.rlib -L native=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\ring-5f8d2d20440f09b4\\out -L native=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\windows_x86_64_gnullvm-0.52.6\\lib -C link-arg=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\oa-desktop-664d65ac5124e646\\out\\normal-user-manifest.o"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
error[E0659]: `column` is ambiguous
|
||||
--> crates\oa-desktop\src\views.rs:1360:27
|
||||
|
|
||||
1360 | let mut notices = column![].spacing(5);
|
||||
| ^^^^^^ ambiguous name
|
||||
|
|
||||
= note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution
|
||||
note: `column` could refer to the macro imported here
|
||||
--> crates\oa-desktop\src\views.rs:1277:9
|
||||
|
|
||||
1277 | use super::*;
|
||||
| ^^^^^^^^
|
||||
= help: consider adding an explicit import of `column` to disambiguate
|
||||
= help: or use `self::column` to refer to this macro unambiguously
|
||||
note: `column` could also refer to a macro from prelude
|
||||
--> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\std\src\prelude\mod.rs:162:12
|
||||
|
||||
error[E0659]: `column` is ambiguous
|
||||
--> crates\oa-desktop\src\views.rs:1367:13
|
||||
|
|
||||
1367 | column![
|
||||
| ^^^^^^ ambiguous name
|
||||
|
|
||||
= note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution
|
||||
note: `column` could refer to the macro imported here
|
||||
--> crates\oa-desktop\src\views.rs:1277:9
|
||||
|
|
||||
1277 | use super::*;
|
||||
| ^^^^^^^^
|
||||
= help: consider adding an explicit import of `column` to disambiguate
|
||||
= help: or use `self::column` to refer to this macro unambiguously
|
||||
note: `column` could also refer to a macro from prelude
|
||||
--> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\std\src\prelude\mod.rs:162:12
|
||||
|
||||
error[E0659]: `column` is ambiguous
|
||||
--> crates\oa-desktop\src\views.rs:1381:13
|
||||
|
|
||||
1381 | column![
|
||||
| ^^^^^^ ambiguous name
|
||||
|
|
||||
= note: ambiguous because of a conflict between a name from a glob import and an outer scope during import or macro resolution
|
||||
note: `column` could refer to the macro imported here
|
||||
--> crates\oa-desktop\src\views.rs:1277:9
|
||||
|
|
||||
1277 | use super::*;
|
||||
| ^^^^^^^^
|
||||
= help: consider adding an explicit import of `column` to disambiguate
|
||||
= help: or use `self::column` to refer to this macro unambiguously
|
||||
note: `column` could also refer to a macro from prelude
|
||||
--> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\std\src\prelude\mod.rs:162:12
|
||||
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio" test) due to 3 previous errors
|
||||
|
||||
Caused by:
|
||||
process didn't exit successfully: `C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\rustup\toolchains\stable-x86_64-pc-windows-gnullvm\bin\rustc.exe --crate-name arcadia_studio --edition=2024 crates\oa-desktop\src\main.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --emit=dep-info,link -C embed-bitcode=no --test --check-cfg cfg(docsrs,test) --check-cfg "cfg(feature, values())" -C metadata=e92d7ab29d07ce2a -C extra-filename=-58e30cee1fd51a8b --out-dir C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps -C linker=clang.exe -C incremental=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\incremental -C strip=debuginfo -L dependency=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps --extern axum=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libaxum-ccc7aea466db9258.rlib --extern iced=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libiced-ee73abd722c72024.rlib --extern iced_runtime=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libiced_runtime-97354eb606c5cbe8.rlib --extern oa_core=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\liboa_core-6c8da57367eec642.rlib --extern oa_server=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\liboa_server-69305e51c40cee45.rlib --extern reqwest=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libreqwest-31ac987472fa8038.rlib --extern rfd=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\librfd-17cc7ba2db223d7b.rlib --extern serde=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libserde-60a32311146a42e4.rlib --extern serde_json=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libserde_json-da2c4f333fc47818.rlib --extern tempfile=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libtempfile-86aac28d6be16d2f.rlib --extern tokio=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\libtokio-5e0ca96735e3073a.rlib -L native=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\build\ring-5f8d2d20440f09b4\out -L native=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\cargo\registry\src\index.crates.io-1949cf8c6b5b557f\windows_x86_64_gnullvm-0.52.6\lib -C link-arg=C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\build\oa-desktop-664d65ac5124e646\out\normal-user-manifest.o` (exit code: 0xffffffff)
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"started": "2026-09-11T19:24:58.0862693Z",
|
||||
"finished": "2026-09-11T19:26:31.7992756Z",
|
||||
"exitCode": 101,
|
||||
"args": [
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"source_",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
error[E0277]: the trait bound `Renderer: iced::advanced::text::Renderer` is not satisfied
|
||||
--> crates\oa-desktop\src\views.rs:1222:19
|
||||
|
|
||||
1222 | container(scrollable(header).id("source-header")).height((window_height * 0.42).max(180.)),
|
||||
| ^^^^^^^^^^^^^^^^^^ the trait `iced::advanced::text::Renderer` is not implemented for `Renderer`
|
||||
|
|
||||
= note: `Renderer` implements similarly named trait `iced::advanced::Renderer`, but not `iced::advanced::text::Renderer`
|
||||
note: required by a bound in `iced::widget::scrollable`
|
||||
--> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\cargo\registry\src\index.crates.io-1949cf8c6b5b557f\iced_widget-0.14.2\src\helpers.rs:1051:15
|
||||
|
|
||||
1046 | pub fn scrollable<'a, Message, Theme, Renderer>(
|
||||
| ---------- required by a bound in this function
|
||||
...
|
||||
1051 | Renderer: core::text::Renderer,
|
||||
| ^^^^^^^^^^^^^^^^^^^^ required by this bound in `scrollable`
|
||||
help: consider further restricting type parameter `Renderer` with trait `Renderer`
|
||||
|
|
||||
1214 | fn source_layout<'a, Renderer: iced::advanced::Renderer + 'a + iced::advanced::text::Renderer>(
|
||||
| ++++++++++++++++++++++++++++++++
|
||||
|
||||
error[E0599]: the method `id` exists for struct `iced::widget::Scrollable<'_, app::Message, iced::Theme, Renderer>`, but its trait bounds were not satisfied
|
||||
--> crates\oa-desktop\src\views.rs:1222:38
|
||||
|
|
||||
1222 | container(scrollable(header).id("source-header")).height((window_height * 0.42).max(180.)),
|
||||
| ^^ private field, not a method
|
||||
|
|
||||
= note: the following trait bounds were not satisfied:
|
||||
`Renderer: iced::advanced::text::Renderer`
|
||||
help: consider restricting the type parameter to satisfy the trait bound
|
||||
|
|
||||
1218 | ) -> View<'a, Message, iced::Theme, Renderer> where Renderer: iced::advanced::text::Renderer {
|
||||
| ++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Some errors have detailed explanations: E0277, E0599.
|
||||
For more information about an error, try `rustc --explain E0277`.
|
||||
error: could not compile `oa-desktop` (bin "arcadia-studio" test) due to 2 previous errors
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"started": "2026-09-11T19:27:12.6898804Z",
|
||||
"finished": "2026-09-11T19:35:01.2568189Z",
|
||||
"exitCode": 101,
|
||||
"args": [
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"source_",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 6m 58s
|
||||
Running unittests src\main.rs (.local\target-gnullvm\debug\deps\arcadia_studio-58e30cee1fd51a8b.exe)
|
||||
|
||||
running 189 tests
|
||||
test app::behavior_lifecycle_tests::pending_behavior_blocks_source_creation_and_source_history_replay ... ok
|
||||
test app::behavior_lifecycle_tests::source_writeback_failure_keeps_typed_behavior_form_for_repair_and_one_undo ... ok
|
||||
test app::checkpoints::tests::home_resume_retains_invalid_source_and_property_drafts ... ok
|
||||
test app::checkpoints::tests::walkthrough_request_keeps_unfinished_source_behind_the_normal_pending_guard ... ok
|
||||
test app::direct_scenario_tests::direct_scenario_source_failure_and_navigation_keep_form_until_success ... ok
|
||||
test app::hosted::tests::hosted_invalid_and_detached_source_drafts_never_start_network_writes ... ok
|
||||
test app::hosted::tests::hosted_only_successful_source_save_as_changes_the_active_destination ... ok
|
||||
test app::process_canvas::direct_process_dirty_source_blocks_canvas_creation_without_losing_text ... ok
|
||||
test app::process_canvas::tests::direct_process_app_open_source_and_back_preserve_local_reference_identity ... ok
|
||||
test app::process_canvas::tests::direct_process_app_source_failure_retains_form_then_commits_once_and_selects_occurrence ... ok
|
||||
test app::process_canvas::tests::direct_process_app_stale_context_dirty_source_and_properties_never_mutate ... ok
|
||||
test app::process_canvas::tests::direct_process_append_empty_first_second_exchange_has_exact_source_history ... ok
|
||||
test app::review_navigation_tests::rejected_semantic_source_writeback_retains_review_camera_and_original_navigation ... ok
|
||||
test app::save_as_tests::save_as_late_receipt_preserves_newer_raw_source_draft_and_destination ... ok
|
||||
test app::source_project_tests::closed_detached_file_keeps_first_save_conflict_then_can_be_reloaded_fresh ... ok
|
||||
test app::source_project_tests::inactive_invalid_draft_blocks_graphical_changes_and_diagnostic_switches_files ... ok
|
||||
test app::source_project_tests::json_export_and_rejected_save_as_preserve_source_location_history_and_bytes ... ok
|
||||
test app::source_project_tests::missing_include_open_keeps_old_model_and_can_be_repaired_and_saved ... ok
|
||||
test app::source_project_tests::recovery_after_first_save_failure_retains_target_then_retries_without_reentering_path ... ok
|
||||
test app::source_project_tests::recovery_of_previous_save_as_target_does_not_redirect_current_project ... ok
|
||||
test app::source_project_tests::recovery_open_finds_first_save_journal_without_an_entry_file ... ok
|
||||
test app::source_project_tests::recovery_target_survives_edit_lint_apply_and_rejected_recovery_until_success ... ok
|
||||
test app::source_project_tests::source3_native_open_preserves_report_comments_nesting_and_save_reopen ... ok
|
||||
test app::source_project_tests::two_file_apply_graphical_edit_exact_undo_save_as_and_reopen ... ok
|
||||
test app::source_symbols::tests::back_after_origin_edit_uses_its_current_cursor_instead_of_the_old_range ... ok
|
||||
test app::source_symbols::tests::back_keeps_the_latest_64_actual_locations_without_reusing_dropped_history ... ok
|
||||
test app::source_symbols::tests::back_restores_exact_reversed_utf8_selection_and_crlf_source ... ok
|
||||
test app::source_symbols::tests::captured_find_and_modal_escape_use_one_actual_subscription_and_preserve_pending_work ... ok
|
||||
test app::source_symbols::tests::captured_subscription_does_not_duplicate_existing_behavior_or_architecture_routes ... ok
|
||||
test app::source_symbols::tests::close_during_busy_hides_panel_and_completion_does_not_reopen_it ... ok
|
||||
test app::source_symbols::tests::closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice ... ok
|
||||
test app::source_symbols::tests::completion_rejects_file_edit_session_cursor_interaction_and_raw_input_changes ... ok
|
||||
test app::source_symbols::tests::detached_duplicate_requires_an_explicit_choice_even_when_included_source_is_validated ... ok
|
||||
test app::source_symbols::tests::escape_dismisses_modal_before_underlying_source_results ... ok
|
||||
test app::source_symbols::tests::escape_dismisses_results_and_cancels_busy_navigation_without_changing_sources ... ok
|
||||
test app::source_symbols::tests::find_close_releases_removed_input_and_guarded_focus_respects_later_input ... ok
|
||||
test app::source_symbols::tests::find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection ... ok
|
||||
test app::source_symbols::tests::invalid_unapplied_drafts_navigate_without_apply_or_losing_either_history ... ok
|
||||
test app::source_symbols::tests::matching_reveal_ticket_is_required_and_raw_input_invalidates_pending_reveal ... ok
|
||||
test app::source_symbols::tests::old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent ... ok
|
||||
test app::source_symbols::tests::old_symbol_widget_envelopes_after_file_switch_preserve_same_session_intent ... ok
|
||||
test app::source_symbols::tests::old_symbol_widget_envelopes_after_reset_preserve_new_same_path_intent ... ok
|
||||
test app::source_symbols::tests::reset_retains_worker_slot_until_old_completion_then_admits_new_session_query ... ok
|
||||
test app::source_symbols::tests::saturated_raw_clock_never_authorizes_completion_or_reveal ... ok
|
||||
test app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses ... ignored, manual 100-module latency/process-memory trial; run alone with --ignored --nocapture --test-threads=1
|
||||
test app::source_symbols::tests::unknown_job_cannot_release_current_worker_and_old_duplicate_cannot_replace_new_results ... ok
|
||||
test app::source_symbols::tests::valid_definition_and_reference_choice_keep_whole_model_and_source_unchanged ... ok
|
||||
test app::state_diagram_tests::create_view_places_only_its_machine_and_restores_exact_source_in_one_undo ... ok
|
||||
test app::state_diagram_tests::rejected_creation_keeps_source_and_selection_then_valid_owner_can_retry ... ok
|
||||
test app::state_diagram_tests::state_diagram_routes_constructor_and_source_open_to_the_existing_workspace_without_edits ... ok
|
||||
test app::transition_context_tests::rejected_source_writeback_keeps_review_and_navigation_and_invalid_draft_still_blocks_start ... ok
|
||||
test app::view_copy_tests::copying_the_current_state_view_uses_its_own_identity_and_is_one_source_undo ... ok
|
||||
test app::view_copy_tests::duplicate_rejection_preserves_an_incomplete_source_buffer ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_checkpoint_restores_inner_raw_draft_without_model_adoption ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_empty_actions_and_child_open_refuse_other_raw_editors ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_modal_escape_then_child_back_then_parent_cancel_preserve_accepted_source ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_back_remains_available_after_owner_deletion_and_with_dirty_editors ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_back_retains_parent_and_rejects_late_child_event ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_default_consumer_name_has_stable_alias_and_correlated_selection ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_deleted_owner_view_retains_draft_instead_of_panicking_or_using_fallback ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_empty_history_reports_no_change_and_success_requires_a_real_history_step ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_existing_choice_prunes_staged_dependency ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_failed_source_budget_preserves_both_drafts_and_accepted_bytes ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_incomplete_recovered_kind_reports_error_without_panicking ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_inserts_work_on_the_exact_guarded_link_and_refuses_exchange_identity_conflict ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_lifelines_stage_exact_separate_records_and_one_history_entry ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_machine_before_any_owner_creates_optional_view_atomically ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_mounted_small_editor_keeps_back_and_use_outside_field_scroll ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_nested_semantic_parent_is_explicit_and_revalidated ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_new_callee_lifeline_preserves_caller_bindings_as_an_explicit_warning ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_old_empty_action_cannot_open_a_previous_owner_or_layer ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_original_document_remains_bound_after_back_or_choosing_existing ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_owner_first_can_return_to_an_unnamed_machine_without_losing_source ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_recovery_keeps_inner_raw_fields_and_refuses_deleted_or_changed_owner ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_second_system_and_role_machine_owner_refuse_without_fallback ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_work_uses_reserved_contribution_and_link_ids_and_one_undo ... ok
|
||||
test behavior::creation::app_tests::app_new_never_auto_applies_valid_or_invalid_property_and_source_drafts ... ok
|
||||
test behavior::creation::tests::all_four_viewpoints_are_explicit_and_survive_source_history_reopen ... ok
|
||||
test behavior::creation::tests::rejected_source_transaction_keeps_draft_and_every_accepted_source_byte ... ok
|
||||
test behavior::scenario::direct_tests::direct_clicked_occurrences_use_root_and_operand_slots_with_exact_source_history ... ok
|
||||
test behavior::scenario::direct_tests::direct_middle_pair_insertion_is_one_exact_source_and_model_undo ... ok
|
||||
test behavior::scenario::tests::graphical_call_reply_is_atomic_correlated_and_source_equivalent ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_initial_essentials_fit_short_composed_host_with_real_paragraphs ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_long_parent_wraps_after_visible_name_without_losing_raw_data ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_visible_kind_click_keeps_choice_explicit_and_name_input_works ... ok
|
||||
test behavior::state_edit::tests::view_initializes_only_its_machine_in_one_source_and_model_history_entry ... ok
|
||||
test behavior::topology::contextual::tests::qualified_link_gui_repeated_sources_remain_distinct_and_apply_has_exact_source_history ... ok
|
||||
test behavior::topology::direct_tests::direct_process_nested_source_reopen_and_api_command_use_one_transaction ... ok
|
||||
test behavior::topology::tests::guarded_branch_is_one_transaction_with_distinct_source_identities ... ok
|
||||
test connection_tool::tests::direct_connections_share_exact_source_history_and_model_parity ... ok
|
||||
test diagram::lifecycle_tests::middle_and_arrow_panning_preserve_source_and_history ... ok
|
||||
test diagram::lifecycle_tests::motion_after_source_click_release_must_not_create_connection ... ok
|
||||
test diagram::pointer_tests::coalesced_fe_ports_commit_once_through_app_and_source_history ... ok
|
||||
test diagram::pointer_tests::coalesced_port_drag_commits_once_through_app_and_source_history ... ok
|
||||
test diagram::pointer_tests::drag_from_already_selected_stationary_port_keeps_source_between_batches ... ok
|
||||
test document::save_as_tests::save_as_preserves_explicit_source_entry_changes_in_history ... ok
|
||||
test document::tests::nested_cross_file_reparent_and_source_edit_share_exact_history ... ok
|
||||
test document::tests::no_op_command_or_source_apply_preserves_history_revision_and_redo ... ok
|
||||
test document::tests::omitted_id_stabilization_remains_dirty_until_exact_source_is_saved ... ok
|
||||
test document::tests::recreated_equal_record_does_not_inherit_deleted_source_ownership ... ok
|
||||
test forms::history_fields::tests::history_every_descriptor_fact_choice_matches_actual_closed_types_and_source_contract ... ok
|
||||
test hosted::client::tests::hosted_publication_retains_known_resource_after_failed_followup ... ok
|
||||
test model_menu::tests::add_bend_here_uses_context_point_and_one_source_aware_history_change ... ok
|
||||
test model_menu::tests::pending_source_allows_file_navigation_but_disables_model_actions ... ok
|
||||
test recovery::source7_tests::source7_legacy_profiles_preserve_dirty_source_property_and_behavior_raw_fields ... ok
|
||||
test recovery::tests::exact_source_and_unfinished_invalid_buffers_restore_without_model_overwrite ... ok
|
||||
test semantic_review::artifacts_tests::artifact_creation_and_identified_source_deletion_follow_core_metadata_and_history_boundary_both_directions ... ok
|
||||
test semantic_review::artifacts_tests::artifact_metadata_apply_and_exact_history_reset_are_one_source_preserving_undo_redo ... ok
|
||||
test semantic_review::artifacts_tests::artifact_moved_source_absence_deletes_but_destination_only_take_retains_both_directions ... ok
|
||||
test semantic_review::attachments::tests::attachment_apply_is_one_exact_source_and_history_undo_redo ... ok
|
||||
test semantic_review::attachments::tests::functional_incident_source_requires_explicit_matching_port_review_in_one_atomic_candidate ... ok
|
||||
test semantic_review::attachments::tests::source_only_absence_and_explicit_absent_endpoint_are_different ... ok
|
||||
test semantic_review::batch_tests::batch_apply_is_one_exact_source_and_history_undo_redo_roundtrip ... ok
|
||||
test semantic_review::constructor_tests::scenario_use_group_takes_typed_binding_references_and_preserves_source_text_both_directions ... ok
|
||||
test semantic_review::keyed_rows_tests::keyed_rows_distinguish_unpaired_source_from_explicit_absence_and_require_exact_reset ... ok
|
||||
test semantic_review::keyed_rows_tests::row_review_apply_preserves_exact_source_and_semantic_history_through_undo_redo ... ok
|
||||
test semantic_review::keyed_tests::deleted_member_source_observation_removes_only_mapped_membership_both_directions ... ok
|
||||
test semantic_review::keyed_tests::exact_reset_only_is_one_history_source_transaction_and_undo_redo_both_directions ... ok
|
||||
test semantic_review::keyed_tests::stale_or_source_failed_reset_and_membership_apply_retain_exact_choices ... ok
|
||||
test semantic_review::message_kind_tests::message_group_rejects_protected_edits_stale_plan_and_source_failure_without_losing_timer ... ok
|
||||
test semantic_review::message_kind_tests::message_group_take_full_timer_and_scalar_candidate_are_one_source_history_change_both_directions ... ok
|
||||
test semantic_review::ordered_tests::ordered_review_apply_is_exact_source_history_transaction_with_reopen_and_undo_redo ... ok
|
||||
test semantic_review::ordered_tests::several_incoming_members_follow_source_order_despite_independent_identity_sorting ... ok
|
||||
test semantic_review::tests::scalar_keep_take_candidate_commit_exact_source_and_history_in_both_directions ... ok
|
||||
test semantic_review::tests::stale_plan_and_source_writeback_failure_do_not_lose_decisions_or_history ... ok
|
||||
test source_navigation::interaction::tests::captured_find_click_invalidates_without_app_message_and_preserves_focus ... ok
|
||||
test source_navigation::interaction::tests::focus_rechecks_clock_between_inspection_and_application_and_missing_target_is_inert ... ok
|
||||
test source_navigation::interaction::tests::raw_navigation_inputs_advance_but_passive_geometry_does_not_and_exhaustion_refuses ... ok
|
||||
test source_navigation::query::tests::comments_strings_and_comment_gap_are_not_navigation_hits ... ok
|
||||
test source_navigation::query::tests::detached_alias_and_identity_collisions_never_auto_select ... ok
|
||||
test source_navigation::query::tests::dirty_but_parseable_text_has_current_names_and_no_validated_fallback ... ok
|
||||
test source_navigation::query::tests::exact_multifile_unicode_crlf_definition_and_repeated_lexical_references ... ok
|
||||
test source_navigation::query::tests::exact_snapshot_readback_rejects_same_key_byte_membership_and_evidence_changes ... ok
|
||||
test source_navigation::query::tests::include_cycle_depth_and_aggregate_edges_are_explicit_partial_outcomes ... ok
|
||||
test source_navigation::query::tests::invalid_include_syntax_never_borrows_the_old_reachable_file_set ... ok
|
||||
test source_navigation::query::tests::keyword_alias_and_loaded_path_case_collisions_use_reviewed_index_rules ... ok
|
||||
test source_navigation::query::tests::large_display_name_cannot_escape_the_owned_row_string_budget ... ok
|
||||
test source_navigation::query::tests::missing_and_noncanonical_authored_ids_remain_draft_spans_without_generated_anchors ... ok
|
||||
test source_navigation::query::tests::missing_include_partial_scope_does_not_resurrect_witness_text ... ok
|
||||
test source_navigation::query::tests::omitted_rows_are_counted_before_result_allocation_and_never_confer_uniqueness ... ok
|
||||
test source_navigation::query::tests::owned_request_checks_raw_limits_immediately_and_extracts_only_on_worker ... ok
|
||||
test source_navigation::query::tests::owned_request_moves_exact_text_into_worker_snapshot_and_matches_sync_results ... ok
|
||||
test source_navigation::query::tests::owned_validation_proof_preserves_full_witness_count_and_partial_scope ... ok
|
||||
test source_navigation::query::tests::raw_aggregate_limits_refuse_before_outline_or_copied_snapshot ... ok
|
||||
test source_navigation::query::tests::sole_detached_candidate_and_unapplied_equal_bytes_require_choice ... ok
|
||||
test source_navigation::query::tests::token_declaration_and_live_reference_limits_are_aggregate_not_per_file ... ok
|
||||
test source_navigation::query::tests::validation_witness_must_match_entry_and_complete_included_file_set ... ok
|
||||
test source_navigation::symbols::tests::admitted_unicode_path_budget_keeps_original_policy_after_single_fold ... ok
|
||||
test source_navigation::symbols::tests::comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans ... ok
|
||||
test source_navigation::symbols::tests::declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search ... ok
|
||||
test source_navigation::symbols::tests::detached_uuid_clone_disables_anchor_even_for_included_query ... ok
|
||||
test source_navigation::symbols::tests::duplicate_aliases_are_candidates_without_false_reference_attribution ... ok
|
||||
test source_navigation::symbols::tests::every_budget_admits_boundary_and_returns_no_index_one_below ... ok
|
||||
test source_navigation::symbols::tests::header_metadata_must_match_current_token_boundaries_and_authored_id ... ok
|
||||
test source_navigation::symbols::tests::incomplete_draft_is_lexical_and_error_token_does_not_leak_references ... ok
|
||||
test source_navigation::symbols::tests::legal_keyword_classified_alias_is_not_discarded ... ok
|
||||
test source_navigation::symbols::tests::missing_ids_produce_only_current_snapshot_locations_without_stabilization ... ok
|
||||
test source_navigation::symbols::tests::nested_declaration_extents_do_not_turn_body_text_into_definition_hits ... ok
|
||||
test source_navigation::symbols::tests::noncanonical_header_identity_cannot_enter_through_query_metadata ... ok
|
||||
test source_navigation::symbols::tests::path_identity_rejects_normalization_and_case_collisions ... ok
|
||||
test source_navigation::symbols::tests::query_header_admission_preserves_uppercase_and_json_escape_bytes ... ok
|
||||
test source_navigation::symbols::tests::repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded ... ok
|
||||
test source_navigation::symbols::tests::reused_alias_cannot_steal_old_identity_and_old_locations_are_stale ... ok
|
||||
test source_navigation::symbols::tests::scope_provenance_and_detached_alias_collision_are_explicit ... ok
|
||||
test source_navigation::symbols::tests::stopped_outline_is_not_a_successful_empty_query ... ok
|
||||
test source_navigation::symbols::tests::truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial ... ok
|
||||
test source_navigation::symbols::tests::unicode_path_collisions_match_core_lowercase_policy ... ok
|
||||
test source_navigation::tests::search_uses_utf8_columns_wraps_and_refreshes_after_edits ... ok
|
||||
test source_tree::tests::active_scroll_offset_uses_the_visible_order_and_retains_ancestor_context ... ok
|
||||
test source_tree::tests::collapsed_folders_retain_active_dirty_and_diagnostic_markers ... ok
|
||||
test source_tree::tests::deep_paths_build_expand_and_drop_on_a_small_stack ... ok
|
||||
test source_tree::tests::detached_buffers_keep_paths_and_only_clean_buffers_are_closable ... ok
|
||||
test source_tree::tests::exact_unicode_spaces_and_file_folder_name_collisions_are_not_normalized ... ok
|
||||
test source_tree::tests::folders_are_compact_and_sorted_before_files_without_losing_duplicate_basenames ... ok
|
||||
test source_workspace::tests::draft_history_budget_covers_inactive_files_without_discarding_drafts ... ok
|
||||
test source_workspace::tests::fresh_session_cannot_reuse_a_retained_buffer_generation ... ok
|
||||
test source_workspace::tests::navigation_revision_wrap_cannot_reuse_an_old_workspace_key ... ok
|
||||
test source_workspace::tests::navigation_scope_changes_invalidate_without_changing_unchanged_positions ... ok
|
||||
test source_workspace::tests::navigation_versions_distinguish_reopened_recovered_and_changed_drafts ... ok
|
||||
test source_workspace::tests::switching_retains_invalid_drafts_and_independent_history ... ok
|
||||
test tests::malformed_source_preserves_model_and_is_recoverable ... ok
|
||||
test tests::opening_legacy_source_surfaces_report_without_modifying_original_bytes ... ok
|
||||
test tests::pending_source_prevents_property_draft_collision_and_preserves_both_if_present ... ok
|
||||
test tests::source_find_navigates_an_invalid_draft_without_applying_or_losing_it ... ok
|
||||
test tests::source_navigation_waits_for_editor_layout_and_cancels_on_edit ... ok
|
||||
test views::source_tests::source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit ... ok
|
||||
test views::source_tests::source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds ... FAILED
|
||||
test views::source_tests::source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh ... ok
|
||||
test workflow::transition_tests::basic_transition_explicit_destination_appends_below_existing_bounds_and_is_one_source_edit ... ok
|
||||
|
||||
failures:
|
||||
|
||||
---- views::source_tests::source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds stdout ----
|
||||
|
||||
thread 'views::source_tests::source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds' (6596) panicked at crates\oa-desktop\src\views.rs:1456:17:
|
||||
assertion `left == right` failed
|
||||
left: 243.70001
|
||||
right: 294.0
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
views::source_tests::source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds
|
||||
|
||||
test result: FAILED. 187 passed; 1 failed; 1 ignored; 0 measured; 562 filtered out; finished in 46.57s
|
||||
|
||||
error: test failed, to rerun pass `-p oa-desktop --bin arcadia-studio`
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"started": "2026-09-11T19:35:53.3464146Z",
|
||||
"finished": "2026-09-11T19:38:11.7593163Z",
|
||||
"exitCode": 0,
|
||||
"args": [
|
||||
"test",
|
||||
"--locked",
|
||||
"--offline",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"source_",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"buildJobs": 1
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 1m 35s
|
||||
Running unittests src\main.rs (.local\target-gnullvm\debug\deps\arcadia_studio-58e30cee1fd51a8b.exe)
|
||||
|
||||
running 189 tests
|
||||
test app::behavior_lifecycle_tests::pending_behavior_blocks_source_creation_and_source_history_replay ... ok
|
||||
test app::behavior_lifecycle_tests::source_writeback_failure_keeps_typed_behavior_form_for_repair_and_one_undo ... ok
|
||||
test app::checkpoints::tests::home_resume_retains_invalid_source_and_property_drafts ... ok
|
||||
test app::checkpoints::tests::walkthrough_request_keeps_unfinished_source_behind_the_normal_pending_guard ... ok
|
||||
test app::direct_scenario_tests::direct_scenario_source_failure_and_navigation_keep_form_until_success ... ok
|
||||
test app::hosted::tests::hosted_invalid_and_detached_source_drafts_never_start_network_writes ... ok
|
||||
test app::hosted::tests::hosted_only_successful_source_save_as_changes_the_active_destination ... ok
|
||||
test app::process_canvas::direct_process_dirty_source_blocks_canvas_creation_without_losing_text ... ok
|
||||
test app::process_canvas::tests::direct_process_app_open_source_and_back_preserve_local_reference_identity ... ok
|
||||
test app::process_canvas::tests::direct_process_app_source_failure_retains_form_then_commits_once_and_selects_occurrence ... ok
|
||||
test app::process_canvas::tests::direct_process_app_stale_context_dirty_source_and_properties_never_mutate ... ok
|
||||
test app::process_canvas::tests::direct_process_append_empty_first_second_exchange_has_exact_source_history ... ok
|
||||
test app::review_navigation_tests::rejected_semantic_source_writeback_retains_review_camera_and_original_navigation ... ok
|
||||
test app::save_as_tests::save_as_late_receipt_preserves_newer_raw_source_draft_and_destination ... ok
|
||||
test app::source_project_tests::closed_detached_file_keeps_first_save_conflict_then_can_be_reloaded_fresh ... ok
|
||||
test app::source_project_tests::inactive_invalid_draft_blocks_graphical_changes_and_diagnostic_switches_files ... ok
|
||||
test app::source_project_tests::json_export_and_rejected_save_as_preserve_source_location_history_and_bytes ... ok
|
||||
test app::source_project_tests::missing_include_open_keeps_old_model_and_can_be_repaired_and_saved ... ok
|
||||
test app::source_project_tests::recovery_after_first_save_failure_retains_target_then_retries_without_reentering_path ... ok
|
||||
test app::source_project_tests::recovery_of_previous_save_as_target_does_not_redirect_current_project ... ok
|
||||
test app::source_project_tests::recovery_open_finds_first_save_journal_without_an_entry_file ... ok
|
||||
test app::source_project_tests::recovery_target_survives_edit_lint_apply_and_rejected_recovery_until_success ... ok
|
||||
test app::source_project_tests::source3_native_open_preserves_report_comments_nesting_and_save_reopen ... ok
|
||||
test app::source_project_tests::two_file_apply_graphical_edit_exact_undo_save_as_and_reopen ... ok
|
||||
test app::source_symbols::tests::back_after_origin_edit_uses_its_current_cursor_instead_of_the_old_range ... ok
|
||||
test app::source_symbols::tests::back_keeps_the_latest_64_actual_locations_without_reusing_dropped_history ... ok
|
||||
test app::source_symbols::tests::back_restores_exact_reversed_utf8_selection_and_crlf_source ... ok
|
||||
test app::source_symbols::tests::captured_find_and_modal_escape_use_one_actual_subscription_and_preserve_pending_work ... ok
|
||||
test app::source_symbols::tests::captured_subscription_does_not_duplicate_existing_behavior_or_architecture_routes ... ok
|
||||
test app::source_symbols::tests::close_during_busy_hides_panel_and_completion_does_not_reopen_it ... ok
|
||||
test app::source_symbols::tests::closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice ... ok
|
||||
test app::source_symbols::tests::completion_rejects_file_edit_session_cursor_interaction_and_raw_input_changes ... ok
|
||||
test app::source_symbols::tests::detached_duplicate_requires_an_explicit_choice_even_when_included_source_is_validated ... ok
|
||||
test app::source_symbols::tests::escape_dismisses_modal_before_underlying_source_results ... ok
|
||||
test app::source_symbols::tests::escape_dismisses_results_and_cancels_busy_navigation_without_changing_sources ... ok
|
||||
test app::source_symbols::tests::find_close_releases_removed_input_and_guarded_focus_respects_later_input ... ok
|
||||
test app::source_symbols::tests::find_only_captured_escape_closes_once_and_preserves_raw_query_and_selection ... ok
|
||||
test app::source_symbols::tests::invalid_unapplied_drafts_navigate_without_apply_or_losing_either_history ... ok
|
||||
test app::source_symbols::tests::matching_reveal_ticket_is_required_and_raw_input_invalidates_pending_reveal ... ok
|
||||
test app::source_symbols::tests::old_find_close_cannot_cancel_a_new_file_session_or_reopened_find_intent ... ok
|
||||
test app::source_symbols::tests::old_symbol_widget_envelopes_after_file_switch_preserve_same_session_intent ... ok
|
||||
test app::source_symbols::tests::old_symbol_widget_envelopes_after_reset_preserve_new_same_path_intent ... ok
|
||||
test app::source_symbols::tests::reset_retains_worker_slot_until_old_completion_then_admits_new_session_query ... ok
|
||||
test app::source_symbols::tests::saturated_raw_clock_never_authorizes_completion_or_reveal ... ok
|
||||
test app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses ... ignored, manual 100-module latency/process-memory trial; run alone with --ignored --nocapture --test-threads=1
|
||||
test app::source_symbols::tests::unknown_job_cannot_release_current_worker_and_old_duplicate_cannot_replace_new_results ... ok
|
||||
test app::source_symbols::tests::valid_definition_and_reference_choice_keep_whole_model_and_source_unchanged ... ok
|
||||
test app::state_diagram_tests::create_view_places_only_its_machine_and_restores_exact_source_in_one_undo ... ok
|
||||
test app::state_diagram_tests::rejected_creation_keeps_source_and_selection_then_valid_owner_can_retry ... ok
|
||||
test app::state_diagram_tests::state_diagram_routes_constructor_and_source_open_to_the_existing_workspace_without_edits ... ok
|
||||
test app::transition_context_tests::rejected_source_writeback_keeps_review_and_navigation_and_invalid_draft_still_blocks_start ... ok
|
||||
test app::view_copy_tests::copying_the_current_state_view_uses_its_own_identity_and_is_one_source_undo ... ok
|
||||
test app::view_copy_tests::duplicate_rejection_preserves_an_incomplete_source_buffer ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_checkpoint_restores_inner_raw_draft_without_model_adoption ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_empty_actions_and_child_open_refuse_other_raw_editors ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_app_modal_escape_then_child_back_then_parent_cancel_preserve_accepted_source ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_back_remains_available_after_owner_deletion_and_with_dirty_editors ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_back_retains_parent_and_rejects_late_child_event ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_default_consumer_name_has_stable_alias_and_correlated_selection ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_deleted_owner_view_retains_draft_instead_of_panicking_or_using_fallback ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_empty_history_reports_no_change_and_success_requires_a_real_history_step ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_existing_choice_prunes_staged_dependency ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_failed_source_budget_preserves_both_drafts_and_accepted_bytes ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_incomplete_recovered_kind_reports_error_without_panicking ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_inserts_work_on_the_exact_guarded_link_and_refuses_exchange_identity_conflict ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_lifelines_stage_exact_separate_records_and_one_history_entry ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_machine_before_any_owner_creates_optional_view_atomically ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_mounted_small_editor_keeps_back_and_use_outside_field_scroll ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_nested_semantic_parent_is_explicit_and_revalidated ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_new_callee_lifeline_preserves_caller_bindings_as_an_explicit_warning ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_old_empty_action_cannot_open_a_previous_owner_or_layer ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_original_document_remains_bound_after_back_or_choosing_existing ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_owner_first_can_return_to_an_unnamed_machine_without_losing_source ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_recovery_keeps_inner_raw_fields_and_refuses_deleted_or_changed_owner ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_second_system_and_role_machine_owner_refuse_without_fallback ... ok
|
||||
test behavior::contextual_source_tests::contextual_source_work_uses_reserved_contribution_and_link_ids_and_one_undo ... ok
|
||||
test behavior::creation::app_tests::app_new_never_auto_applies_valid_or_invalid_property_and_source_drafts ... ok
|
||||
test behavior::creation::tests::all_four_viewpoints_are_explicit_and_survive_source_history_reopen ... ok
|
||||
test behavior::creation::tests::rejected_source_transaction_keeps_draft_and_every_accepted_source_byte ... ok
|
||||
test behavior::scenario::direct_tests::direct_clicked_occurrences_use_root_and_operand_slots_with_exact_source_history ... ok
|
||||
test behavior::scenario::direct_tests::direct_middle_pair_insertion_is_one_exact_source_and_model_undo ... ok
|
||||
test behavior::scenario::tests::graphical_call_reply_is_atomic_correlated_and_source_equivalent ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_initial_essentials_fit_short_composed_host_with_real_paragraphs ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_long_parent_wraps_after_visible_name_without_losing_raw_data ... ok
|
||||
test behavior::source_draft::layout_tests::contextual_source_layout_visible_kind_click_keeps_choice_explicit_and_name_input_works ... ok
|
||||
test behavior::state_edit::tests::view_initializes_only_its_machine_in_one_source_and_model_history_entry ... ok
|
||||
test behavior::topology::contextual::tests::qualified_link_gui_repeated_sources_remain_distinct_and_apply_has_exact_source_history ... ok
|
||||
test behavior::topology::direct_tests::direct_process_nested_source_reopen_and_api_command_use_one_transaction ... ok
|
||||
test behavior::topology::tests::guarded_branch_is_one_transaction_with_distinct_source_identities ... ok
|
||||
test connection_tool::tests::direct_connections_share_exact_source_history_and_model_parity ... ok
|
||||
test diagram::lifecycle_tests::middle_and_arrow_panning_preserve_source_and_history ... ok
|
||||
test diagram::lifecycle_tests::motion_after_source_click_release_must_not_create_connection ... ok
|
||||
test diagram::pointer_tests::coalesced_fe_ports_commit_once_through_app_and_source_history ... ok
|
||||
test diagram::pointer_tests::coalesced_port_drag_commits_once_through_app_and_source_history ... ok
|
||||
test diagram::pointer_tests::drag_from_already_selected_stationary_port_keeps_source_between_batches ... ok
|
||||
test document::save_as_tests::save_as_preserves_explicit_source_entry_changes_in_history ... ok
|
||||
test document::tests::nested_cross_file_reparent_and_source_edit_share_exact_history ... ok
|
||||
test document::tests::no_op_command_or_source_apply_preserves_history_revision_and_redo ... ok
|
||||
test document::tests::omitted_id_stabilization_remains_dirty_until_exact_source_is_saved ... ok
|
||||
test document::tests::recreated_equal_record_does_not_inherit_deleted_source_ownership ... ok
|
||||
test forms::history_fields::tests::history_every_descriptor_fact_choice_matches_actual_closed_types_and_source_contract ... ok
|
||||
test hosted::client::tests::hosted_publication_retains_known_resource_after_failed_followup ... ok
|
||||
test model_menu::tests::add_bend_here_uses_context_point_and_one_source_aware_history_change ... ok
|
||||
test model_menu::tests::pending_source_allows_file_navigation_but_disables_model_actions ... ok
|
||||
test recovery::source7_tests::source7_legacy_profiles_preserve_dirty_source_property_and_behavior_raw_fields ... ok
|
||||
test recovery::tests::exact_source_and_unfinished_invalid_buffers_restore_without_model_overwrite ... ok
|
||||
test semantic_review::artifacts_tests::artifact_creation_and_identified_source_deletion_follow_core_metadata_and_history_boundary_both_directions ... ok
|
||||
test semantic_review::artifacts_tests::artifact_metadata_apply_and_exact_history_reset_are_one_source_preserving_undo_redo ... ok
|
||||
test semantic_review::artifacts_tests::artifact_moved_source_absence_deletes_but_destination_only_take_retains_both_directions ... ok
|
||||
test semantic_review::attachments::tests::attachment_apply_is_one_exact_source_and_history_undo_redo ... ok
|
||||
test semantic_review::attachments::tests::functional_incident_source_requires_explicit_matching_port_review_in_one_atomic_candidate ... ok
|
||||
test semantic_review::attachments::tests::source_only_absence_and_explicit_absent_endpoint_are_different ... ok
|
||||
test semantic_review::batch_tests::batch_apply_is_one_exact_source_and_history_undo_redo_roundtrip ... ok
|
||||
test semantic_review::constructor_tests::scenario_use_group_takes_typed_binding_references_and_preserves_source_text_both_directions ... ok
|
||||
test semantic_review::keyed_rows_tests::keyed_rows_distinguish_unpaired_source_from_explicit_absence_and_require_exact_reset ... ok
|
||||
test semantic_review::keyed_rows_tests::row_review_apply_preserves_exact_source_and_semantic_history_through_undo_redo ... ok
|
||||
test semantic_review::keyed_tests::deleted_member_source_observation_removes_only_mapped_membership_both_directions ... ok
|
||||
test semantic_review::keyed_tests::exact_reset_only_is_one_history_source_transaction_and_undo_redo_both_directions ... ok
|
||||
test semantic_review::keyed_tests::stale_or_source_failed_reset_and_membership_apply_retain_exact_choices ... ok
|
||||
test semantic_review::message_kind_tests::message_group_rejects_protected_edits_stale_plan_and_source_failure_without_losing_timer ... ok
|
||||
test semantic_review::message_kind_tests::message_group_take_full_timer_and_scalar_candidate_are_one_source_history_change_both_directions ... ok
|
||||
test semantic_review::ordered_tests::ordered_review_apply_is_exact_source_history_transaction_with_reopen_and_undo_redo ... ok
|
||||
test semantic_review::ordered_tests::several_incoming_members_follow_source_order_despite_independent_identity_sorting ... ok
|
||||
test semantic_review::tests::scalar_keep_take_candidate_commit_exact_source_and_history_in_both_directions ... ok
|
||||
test semantic_review::tests::stale_plan_and_source_writeback_failure_do_not_lose_decisions_or_history ... ok
|
||||
test source_navigation::interaction::tests::captured_find_click_invalidates_without_app_message_and_preserves_focus ... ok
|
||||
test source_navigation::interaction::tests::focus_rechecks_clock_between_inspection_and_application_and_missing_target_is_inert ... ok
|
||||
test source_navigation::interaction::tests::raw_navigation_inputs_advance_but_passive_geometry_does_not_and_exhaustion_refuses ... ok
|
||||
test source_navigation::query::tests::comments_strings_and_comment_gap_are_not_navigation_hits ... ok
|
||||
test source_navigation::query::tests::detached_alias_and_identity_collisions_never_auto_select ... ok
|
||||
test source_navigation::query::tests::dirty_but_parseable_text_has_current_names_and_no_validated_fallback ... ok
|
||||
test source_navigation::query::tests::exact_multifile_unicode_crlf_definition_and_repeated_lexical_references ... ok
|
||||
test source_navigation::query::tests::exact_snapshot_readback_rejects_same_key_byte_membership_and_evidence_changes ... ok
|
||||
test source_navigation::query::tests::include_cycle_depth_and_aggregate_edges_are_explicit_partial_outcomes ... ok
|
||||
test source_navigation::query::tests::invalid_include_syntax_never_borrows_the_old_reachable_file_set ... ok
|
||||
test source_navigation::query::tests::keyword_alias_and_loaded_path_case_collisions_use_reviewed_index_rules ... ok
|
||||
test source_navigation::query::tests::large_display_name_cannot_escape_the_owned_row_string_budget ... ok
|
||||
test source_navigation::query::tests::missing_and_noncanonical_authored_ids_remain_draft_spans_without_generated_anchors ... ok
|
||||
test source_navigation::query::tests::missing_include_partial_scope_does_not_resurrect_witness_text ... ok
|
||||
test source_navigation::query::tests::omitted_rows_are_counted_before_result_allocation_and_never_confer_uniqueness ... ok
|
||||
test source_navigation::query::tests::owned_request_checks_raw_limits_immediately_and_extracts_only_on_worker ... ok
|
||||
test source_navigation::query::tests::owned_request_moves_exact_text_into_worker_snapshot_and_matches_sync_results ... ok
|
||||
test source_navigation::query::tests::owned_validation_proof_preserves_full_witness_count_and_partial_scope ... ok
|
||||
test source_navigation::query::tests::raw_aggregate_limits_refuse_before_outline_or_copied_snapshot ... ok
|
||||
test source_navigation::query::tests::sole_detached_candidate_and_unapplied_equal_bytes_require_choice ... ok
|
||||
test source_navigation::query::tests::token_declaration_and_live_reference_limits_are_aggregate_not_per_file ... ok
|
||||
test source_navigation::query::tests::validation_witness_must_match_entry_and_complete_included_file_set ... ok
|
||||
test source_navigation::symbols::tests::admitted_unicode_path_budget_keeps_original_policy_after_single_fold ... ok
|
||||
test source_navigation::symbols::tests::comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans ... ok
|
||||
test source_navigation::symbols::tests::declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search ... ok
|
||||
test source_navigation::symbols::tests::detached_uuid_clone_disables_anchor_even_for_included_query ... ok
|
||||
test source_navigation::symbols::tests::duplicate_aliases_are_candidates_without_false_reference_attribution ... ok
|
||||
test source_navigation::symbols::tests::every_budget_admits_boundary_and_returns_no_index_one_below ... ok
|
||||
test source_navigation::symbols::tests::header_metadata_must_match_current_token_boundaries_and_authored_id ... ok
|
||||
test source_navigation::symbols::tests::incomplete_draft_is_lexical_and_error_token_does_not_leak_references ... ok
|
||||
test source_navigation::symbols::tests::legal_keyword_classified_alias_is_not_discarded ... ok
|
||||
test source_navigation::symbols::tests::missing_ids_produce_only_current_snapshot_locations_without_stabilization ... ok
|
||||
test source_navigation::symbols::tests::nested_declaration_extents_do_not_turn_body_text_into_definition_hits ... ok
|
||||
test source_navigation::symbols::tests::noncanonical_header_identity_cannot_enter_through_query_metadata ... ok
|
||||
test source_navigation::symbols::tests::path_identity_rejects_normalization_and_case_collisions ... ok
|
||||
test source_navigation::symbols::tests::query_header_admission_preserves_uppercase_and_json_escape_bytes ... ok
|
||||
test source_navigation::symbols::tests::repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded ... ok
|
||||
test source_navigation::symbols::tests::reused_alias_cannot_steal_old_identity_and_old_locations_are_stale ... ok
|
||||
test source_navigation::symbols::tests::scope_provenance_and_detached_alias_collision_are_explicit ... ok
|
||||
test source_navigation::symbols::tests::stopped_outline_is_not_a_successful_empty_query ... ok
|
||||
test source_navigation::symbols::tests::truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial ... ok
|
||||
test source_navigation::symbols::tests::unicode_path_collisions_match_core_lowercase_policy ... ok
|
||||
test source_navigation::tests::search_uses_utf8_columns_wraps_and_refreshes_after_edits ... ok
|
||||
test source_tree::tests::active_scroll_offset_uses_the_visible_order_and_retains_ancestor_context ... ok
|
||||
test source_tree::tests::collapsed_folders_retain_active_dirty_and_diagnostic_markers ... ok
|
||||
test source_tree::tests::deep_paths_build_expand_and_drop_on_a_small_stack ... ok
|
||||
test source_tree::tests::detached_buffers_keep_paths_and_only_clean_buffers_are_closable ... ok
|
||||
test source_tree::tests::exact_unicode_spaces_and_file_folder_name_collisions_are_not_normalized ... ok
|
||||
test source_tree::tests::folders_are_compact_and_sorted_before_files_without_losing_duplicate_basenames ... ok
|
||||
test source_workspace::tests::draft_history_budget_covers_inactive_files_without_discarding_drafts ... ok
|
||||
test source_workspace::tests::fresh_session_cannot_reuse_a_retained_buffer_generation ... ok
|
||||
test source_workspace::tests::navigation_revision_wrap_cannot_reuse_an_old_workspace_key ... ok
|
||||
test source_workspace::tests::navigation_scope_changes_invalidate_without_changing_unchanged_positions ... ok
|
||||
test source_workspace::tests::navigation_versions_distinguish_reopened_recovered_and_changed_drafts ... ok
|
||||
test source_workspace::tests::switching_retains_invalid_drafts_and_independent_history ... ok
|
||||
test tests::malformed_source_preserves_model_and_is_recoverable ... ok
|
||||
test tests::opening_legacy_source_surfaces_report_without_modifying_original_bytes ... ok
|
||||
test tests::pending_source_prevents_property_draft_collision_and_preserves_both_if_present ... ok
|
||||
test tests::source_find_navigates_an_invalid_draft_without_applying_or_losing_it ... ok
|
||||
test tests::source_navigation_waits_for_editor_layout_and_cancels_on_edit ... ok
|
||||
test views::source_tests::source_editor_windows_menu_payload_is_inert_but_space_and_altgr_still_edit ... ok
|
||||
test views::source_tests::source_header_keeps_editor_bounds_stable_through_lint_and_find_rebuilds ... ok
|
||||
test views::source_tests::source_header_remains_scrollable_and_preserves_offset_across_diagnostic_refresh ... ok
|
||||
test workflow::transition_tests::basic_transition_explicit_destination_appends_below_existing_bounds_and_is_one_source_edit ... ok
|
||||
|
||||
test result: ok. 188 passed; 0 failed; 1 ignored; 0 measured; 562 filtered out; finished in 41.73s
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# Independent review of Text native corrections, inputs1
|
||||
|
||||
Codex-9e8r6t /root/release_audit, 2026-09-11. Read-only source review against HEAD fb16c420 and the frozen inputs1 archive SHA256 026cb5d112cbcb70e4690ab831a77e17906db329267df21423b7973f71774c36. Read Gitea #6 through comment 484 and the four-file diff. All four inspected raw hashes matched inputs1.json. No compiler, test execution, native input or production edit by this reviewer. Root reports the initial wrapper failed on PowerShell native stderr handling; this review claims no corrected gate pass.
|
||||
|
||||
## Actionable finding: stale Find Close gains current-context focus
|
||||
|
||||
At views.rs:1023, Find Close still emits the unscoped Message::SourceNavigation(Event::Close). At app.rs:3035-3040 it closes whichever Find is now open and calls restore_source_editor_focus. Unlike SourceSymbols, it bypasses the emitting-session/path check at app.rs:1109-1113. The helper (app/source_symbols.rs:137-144) captures the current context and raw clock, so its later guards cannot recognize that the initiating Close came from an older view.
|
||||
|
||||
Counterexample: retain A's Close event; switch/reset to B, open Find and enter a new query; deliver A's event. It closes B's Find and queues a B focus ticket. The same path also needs a Close/reopen instance boundary. This is a verified source path, not a new native reproduction. Existing unscoped dismissal predates this patch; the additional editor-focus effect is new.
|
||||
|
||||
Recommended narrow correction: stamp only the Find Close UI action with emitting workspace session, path and Find-open instance, reject mismatches before source_interaction, and retain the ordinary current Close path. Root accepted this finding and requested implementation by behavior_audit; no corrected version is included in this review. Regression controls should preserve the newly opened Find/query, existing reveal and interaction token, cursor, exact raw files and accepted snapshot on wrong workspace/path or old same-path instance. Current Close must still work.
|
||||
|
||||
## Inspected corrections and evaluator limits
|
||||
|
||||
* **Stable editor boundary:** source_layout at views.rs:1214 reserves the existing bounded header height and leaves overflow scrollable. The actual source view calls this helper, so this is a small, maintainable correction to the native diagnostic jump without retaining misleading stale diagnostics. The test at :1421 uses that production layout helper with real Iced Paragraph/Editor geometry and repeated tree diffs. It meaningfully verifies the boundary invariant; the header contents in mounted_source (:1354) are a separate simplified fixture, not the actual App header. It therefore does not prove real minimum-window toolbar reachability, full App editor height, diagnostic button hit routing or font readability. The scroll test (:1471) proves actual overflow and retained offset for six to seven diagnostics, both at the same capped inner height; it does not claim offset preservation through zero diagnostics or a shrinking scroll range. Native minimum-height lint-clear/refill and next-click confirmation remain required.
|
||||
|
||||
* **Back:** app/source_symbols.rs:189-195 retains closed results after an exact successful return and opens only for a notice. The test at app/source_symbols/tests.rs:481 covers exact cursor return, changed-origin notice and empty history, preserving accepted/raw/model history. No additional finding in this path.
|
||||
|
||||
* **Removed Find focus:** app.rs:1057-1064 extends the existing single captured bridge, retains architecture exclusion and ordinary modal priority. Close/Escape first removes Find, queues a reveal, and the changed sensor ticket causes a viewport message after rebuilding. Thus the originating focused Find is absent when guarded_focus scans the current tree; it does not silently veto normal restoration merely because it was focused before Close. The test at app/source_symbols/tests.rs:549 explicitly removes and diffs the old focused input before running the real viewport task/operation chain. Its target is a TextInput substitute with the actual editor ID, so this is operation/tree-lifetime evidence, not a full App/editor native test. A later focused widget vetoes the operation even without a clock change, and raw input is checked again at operation execution. Modal dismissal exits before requesting editor focus. No new unconditional focus operation was introduced.
|
||||
|
||||
* **Raw-input controls:** the new mounted focus test combines later focus with the incremented-clock variant; that variant alone does not isolate the clock from the foreign-focus veto. Existing interaction tests are the separate primitive coverage. Retain a no-foreign-focus, clock-advanced control when claiming this specific end-to-end race. This is evaluator scope, not an established production defect.
|
||||
|
||||
* **Windows Alt+Space:** views.rs:1237-1249 excludes only focused Windows events whose modifiers are exactly ALT and whose logical key is named/character Space. Ordinary Space, Ctrl+Alt input, shifted input and non-Windows defaults remain delegated. The mounted real-editor test (:1524) verifies supplied key payloads produce no SourceEdit for the two guarded variants and exact insertion for ordinary/international controls. It also establishes that the default binding would insert those payloads. It does not observe Windows event delivery and cannot establish that the delayed post-menu insertion uses those modifiers/logical keys. Native Alt+Space, Escape, subsequent neutral click, settled frames and exact raw-file checks are still mandatory; preserve the earlier immediate-clean counterevidence.
|
||||
|
||||
## Frozen raw identities
|
||||
|
||||
| Path under crates/oa-desktop/src | SHA256 |
|
||||
| --- | --- |
|
||||
| app.rs | 280b7080b5f940feb5d31d8b453168930e6f34c400dfc37f473756388b93d448 |
|
||||
| views.rs | 1dae1d05a1b93cbb33f7d4fd941f01ff971b9b2f1c3eaecfd3e885c871168a42 |
|
||||
| app/source_symbols.rs | 4766bc70ef2e163497fc1f48180d2598e77d85b5331617777b5afa93da3939e8 |
|
||||
| app/source_symbols/tests.rs | 8babf11d897053eba7db7c4a2342aade8ac41b3a0cdd8b96f17b7537c9c067da |
|
||||
|
||||
Disposition: correct the scoped Find Close replay before acceptance. No other actionable production defect established within this four-file review. All synthetic/inspection evidence remains distinct from a corrected Windows native pass.
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# Find Close replay correction: narrow closure review
|
||||
|
||||
Codex-9e8r6t /root/release_audit, 2026-09-11. READY for the producer's next frozen gate. This is source inspection, not a test/build/native pass. No production edits or compiler/native actions by this reviewer. Review1 remains the historical inputs1 finding and broader evaluator limitations.
|
||||
|
||||
The four current raw hashes were measured and match the supplied freeze:
|
||||
|
||||
| Path under crates/oa-desktop/src | SHA256 |
|
||||
| --- | --- |
|
||||
| app.rs | 411007F8C192E0FC581F3C6BCFC49AB46B900A841EE20AE96203D4DB64C9280F |
|
||||
| views.rs | 32A4176305052CAEB3520C647EB9E981EBE702EE3099B2F1036037136FCD58BB |
|
||||
| app/source_symbols.rs | 99370865B548B28F32F4C6CE7E9F8EEAA0C80268A644F2EBCC4BD1E11695E59C |
|
||||
| app/source_symbols/tests.rs | 33292A7CE9B61DBC134F74372CAA93AF9704E2F968CD698DA13EA6A1B8CF3EA6 |
|
||||
|
||||
## Closure
|
||||
|
||||
At app.rs:1111-1128, SourceFindClose validates emitting workspace session/path, per-Open instance, currently open Find and source_navigation_available before source_interaction. Only a valid envelope is translated into the existing Close handler. An incoming bare SourceNavigation(Event::Close) returns immediately. The private handler also defensively refuses an untranslated SourceFindClose at :2999.
|
||||
|
||||
The instance is initialized independently at :608, renewed through reset's fresh App construction (:938), and regenerated on every handled Find/Go-to Open (:3048). Thus an old same-path Close cannot dismiss a later opening. The helper at app/source_symbols.rs:45 captures the current session/path/instance; the actual Close button at views.rs:1023 uses it. A repository Rust search found no other live emitter of bare Find Close: remaining occurrences are the checked translation, refusal branch and negative test. Other legacy Find actions are intentionally unchanged.
|
||||
|
||||
The unavailable/modal boundary also returns before changing interaction, query, reveal or focus state. This follows directly from the early source_navigation_available check, which includes page, busy, pending/file/delete/workflow/hosted/model-menu/architecture/behavior guards. Ordinary Escape retains its existing modal-first path. No new asynchronous unconditional focus operation is introduced.
|
||||
|
||||
## Regression assessment
|
||||
|
||||
The new test at app/source_symbols/tests.rs:549 exercises actual reset, active-file change, and same-path Close/reopen. For reset/file change, it additionally combines the *current* instance with the *old* context; therefore token mismatch alone cannot satisfy the session/path expectations. Same-path reopening isolates the instance boundary. Bare legacy Close is explicitly included among rejected inputs. The existing stale-state assertion snapshots interaction/raw clock, request, worker slot, reveal, cursor, raw bundle, accepted snapshot and history before dispatch; the new test additionally asserts Find open/query/instance. It checks both pending and completed query states, then demonstrates current Close still queues a reveal and restores the unchanged cursor.
|
||||
|
||||
The mounted removed-input test at :626 now emits the valid envelope for Close. It retains the independent widget-tree focus observation: the originating Find is actually removed before running the real viewport focus task, and later foreign focus wins. No test was executed by this review. The new stale regression does not itself enumerate every modal/unavailable flag; those refusals are verified source logic, and existing modal Escape tests remain separate evidence.
|
||||
|
||||
No remaining actionable defect established within this closure. The earlier geometry-fixture and Windows OS-event limitations remain: these source changes do not substitute for corrected native minimum-window lint/click and delayed Alt+Space trials. Ready was sent directly to root and behavior_audit before final capture/gates.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:19:01.914478+00:00",
|
||||
"changed": [],
|
||||
"added": [],
|
||||
"removed": [],
|
||||
"passed": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:26:35.492216+00:00",
|
||||
"changed": [],
|
||||
"added": [],
|
||||
"removed": [],
|
||||
"passed": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:35:48.305081+00:00",
|
||||
"changed": [],
|
||||
"added": [],
|
||||
"removed": [],
|
||||
"passed": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:41:08.550906+00:00",
|
||||
"changed": [],
|
||||
"added": [],
|
||||
"removed": [],
|
||||
"passed": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"atUtc": "2026-09-11T19:42:17.551496+00:00",
|
||||
"baseCommit": "f4e426ae2a6971395e1ca7e6f094b0c20056cf93",
|
||||
"scope": "Four corrected desktop files with exact input ZIPs, failed runs, independent review, focused tests and strict Clippy. Corrected full-suite, GUI and native acceptance are separate.",
|
||||
"files": [
|
||||
{
|
||||
"path": "capture.py",
|
||||
"bytes": 2789,
|
||||
"sha256": "c8ba5b85829c0d1b704ea984eb83a2830e66cffcb86736da32d8cd0c09fe6183"
|
||||
},
|
||||
{
|
||||
"path": "clippy5-status.json",
|
||||
"bytes": 294,
|
||||
"sha256": "57c535b7d5dc81466f9aea20ddb0d7a05428371a367f87ec901f59b90b411159"
|
||||
},
|
||||
{
|
||||
"path": "clippy5.log",
|
||||
"bytes": 157,
|
||||
"sha256": "66690537d431b19659d6d2c932275605cde048ae43d45f2eca00cd4d2b23c34b"
|
||||
},
|
||||
{
|
||||
"path": "final-gate-result.json",
|
||||
"bytes": 2722,
|
||||
"sha256": "d65edfbeb1ac210832974d5d78a964af00eef8e67ca342b103e7a9f39b00c1c2"
|
||||
},
|
||||
{
|
||||
"path": "finalize.py",
|
||||
"bytes": 3571,
|
||||
"sha256": "4658124e48215cd1e32662fcedcd22554cf979759f1ddd840f6125d0a9c830c6"
|
||||
},
|
||||
{
|
||||
"path": "fmt-final-status.json",
|
||||
"bytes": 411,
|
||||
"sha256": "deeb1c3fcfd130891a5f836bfc16d4becbea43890dedb3d689c72c89193d7c9f"
|
||||
},
|
||||
{
|
||||
"path": "focused1-tool-result.json",
|
||||
"bytes": 765,
|
||||
"sha256": "b9cd259f5d2dd0915da4202f8187db239b333aad981f2b6f3708e06819b814d8"
|
||||
},
|
||||
{
|
||||
"path": "focused1-wrapper-failure.txt",
|
||||
"bytes": 759,
|
||||
"sha256": "79d1b7f38bee64841f726224977ac7c1d1a60a2cddd0dff94f735c6e0144c702"
|
||||
},
|
||||
{
|
||||
"path": "focused2-status.json",
|
||||
"bytes": 322,
|
||||
"sha256": "e57ca06ce248d86bb02a48e5067cb1a8d22d557db7c20aa41be40908bbd8f72c"
|
||||
},
|
||||
{
|
||||
"path": "focused2-stop-request.json",
|
||||
"bytes": 2855,
|
||||
"sha256": "d9b71ceea78580c5783591901643a37badd62f59b9c03477fb73a2a4f8a627a6"
|
||||
},
|
||||
{
|
||||
"path": "focused2.log",
|
||||
"bytes": 5176,
|
||||
"sha256": "41cfd3637799aac0d6edfffe7662362e5722a455640dd2c846c6adf5b7a34857"
|
||||
},
|
||||
{
|
||||
"path": "focused3-status.json",
|
||||
"bytes": 322,
|
||||
"sha256": "41836702a94284aacade462a2d4b9449825145dc77b43b8fbb64adf2c1c7b655"
|
||||
},
|
||||
{
|
||||
"path": "focused3.log",
|
||||
"bytes": 2412,
|
||||
"sha256": "746ce65623b2bc88154295c50d259adba92e18fb50b8d1862d36176ba8d1f7f6"
|
||||
},
|
||||
{
|
||||
"path": "focused4-status.json",
|
||||
"bytes": 322,
|
||||
"sha256": "db512cf983989afa7d2103832917596f984550edf96e18ccc7f61dc31928e518"
|
||||
},
|
||||
{
|
||||
"path": "focused4.log",
|
||||
"bytes": 22811,
|
||||
"sha256": "572ae28ba9b722694b1f70320012a4037c8939df4def7e66d06a1bfbcfb80950"
|
||||
},
|
||||
{
|
||||
"path": "focused5-status.json",
|
||||
"bytes": 320,
|
||||
"sha256": "282b0c49e162660e4677598ef140d24a7ada9769543f1b332a27b54db9a7405c"
|
||||
},
|
||||
{
|
||||
"path": "focused5.log",
|
||||
"bytes": 22181,
|
||||
"sha256": "3be2383aa7bf25a92b55b7b6e178c05819559248a88f3ecf245e78266e63d311"
|
||||
},
|
||||
{
|
||||
"path": "independent-review1.md",
|
||||
"bytes": 6301,
|
||||
"sha256": "0fa5a19ffe8db0b4cb0a41ac48672f52a76818cbb3c0311649a480a3ade9f37b"
|
||||
},
|
||||
{
|
||||
"path": "independent-review2.md",
|
||||
"bytes": 3822,
|
||||
"sha256": "6be168f48475c000262326af2a56e3f606c60ec365129a21d535c86e17a38035"
|
||||
},
|
||||
{
|
||||
"path": "inputs1-readback.json",
|
||||
"bytes": 117,
|
||||
"sha256": "8c862514228baa8fd34da0d4457b8f5dafe3b87eb54eeabd3fca828fff183216"
|
||||
},
|
||||
{
|
||||
"path": "inputs1.json",
|
||||
"bytes": 93343,
|
||||
"sha256": "1c328363248f4f7867d0faa2aeabb114fcc918c5bcd022324e03d89d50c9ae40"
|
||||
},
|
||||
{
|
||||
"path": "inputs1.zip",
|
||||
"bytes": 2083588,
|
||||
"sha256": "026cb5d112cbcb70e4690ab831a77e17906db329267df21423b7973f71774c36"
|
||||
},
|
||||
{
|
||||
"path": "inputs2-readback.json",
|
||||
"bytes": 117,
|
||||
"sha256": "d53f076feae1487af7e3d2172f4930e929817573077da0d955f298ad11b50109"
|
||||
},
|
||||
{
|
||||
"path": "inputs2.json",
|
||||
"bytes": 93343,
|
||||
"sha256": "b2c2b102fc7bdeff15114940d236290a893457c6abf853f1c3710d61aa8fe833"
|
||||
},
|
||||
{
|
||||
"path": "inputs2.zip",
|
||||
"bytes": 2084416,
|
||||
"sha256": "fa2fa9dfe4ae329abb9d01bd200a74fc829233824a896b4276942f4eb0f01cbc"
|
||||
},
|
||||
{
|
||||
"path": "inputs3-readback.json",
|
||||
"bytes": 117,
|
||||
"sha256": "939e2c541167068d72981b784c4207824bb6ae6685033d2d956f2678f241dbdf"
|
||||
},
|
||||
{
|
||||
"path": "inputs3.json",
|
||||
"bytes": 93343,
|
||||
"sha256": "eb2a202cd5988bc0ea51a09507525459f76b9db6c391a08a3a26a1725bb3d900"
|
||||
},
|
||||
{
|
||||
"path": "inputs3.zip",
|
||||
"bytes": 2084416,
|
||||
"sha256": "7dcbf323348f214bbd6052f253831810af1562cc5ec4eb8aa46f9c212559b2b3"
|
||||
},
|
||||
{
|
||||
"path": "inputs4-readback.json",
|
||||
"bytes": 117,
|
||||
"sha256": "d153dda1959fcc65e613770f8cb45a253f6c20d1d73b5aad97078e5dba6a9752"
|
||||
},
|
||||
{
|
||||
"path": "inputs4.json",
|
||||
"bytes": 93343,
|
||||
"sha256": "72c2e8154f9945aa87d0cbf9cc148934a84614e15576673496aa5a5907c12154"
|
||||
},
|
||||
{
|
||||
"path": "inputs4.zip",
|
||||
"bytes": 2084486,
|
||||
"sha256": "7ff9e5703e33bd72e5c2186dd61125e965b82b8dfbf085961d514b005e716798"
|
||||
},
|
||||
{
|
||||
"path": "original-hashes.json",
|
||||
"bytes": 910,
|
||||
"sha256": "927aed406f454b0fb2cde8dd3a717e337a620fc6e3c4a0160ba3c7ee3d419ed3"
|
||||
},
|
||||
{
|
||||
"path": "originals/crates/oa-desktop/src/app/source_symbols/tests.rs",
|
||||
"bytes": 45457,
|
||||
"sha256": "ed82648dc6e7a69eb55be6134cbd78452addd250aaeb759f94530c95601d840d"
|
||||
},
|
||||
{
|
||||
"path": "originals/crates/oa-desktop/src/app/source_symbols.rs",
|
||||
"bytes": 17979,
|
||||
"sha256": "00d21572b36d15908e75f0bc954951229fb42c640c6b81b7c424e6b931407d20"
|
||||
},
|
||||
{
|
||||
"path": "originals/crates/oa-desktop/src/app.rs",
|
||||
"bytes": 213219,
|
||||
"sha256": "370c2ce1b58f5aad4cfbbbae4a774670a20c6f4f5f1a77305daf8ce221f80dbe"
|
||||
},
|
||||
{
|
||||
"path": "originals/crates/oa-desktop/src/views.rs",
|
||||
"bytes": 51197,
|
||||
"sha256": "3d57ff54d159d4bbb4fe0b6a368f8dad0c70b5a4d40a6c19fd0f2197cb06624e"
|
||||
},
|
||||
{
|
||||
"path": "README.md",
|
||||
"bytes": 5226,
|
||||
"sha256": "e97cfac491c3545d05e4143a0153b91e3970c339fba8be4bdb71d1a151cdeb9d"
|
||||
},
|
||||
{
|
||||
"path": "run-focused1.ps1",
|
||||
"bytes": 1034,
|
||||
"sha256": "7f64f42e5a45a4780f5487902ac5d201015a10435b1a57c3adc8053c3b21f440"
|
||||
},
|
||||
{
|
||||
"path": "run-focused2.ps1",
|
||||
"bytes": 1034,
|
||||
"sha256": "a45331c664756652f3b52d4c2ffe1dca0942861cd0d9d74645b293570bbe386b"
|
||||
},
|
||||
{
|
||||
"path": "run-focused3.ps1",
|
||||
"bytes": 1034,
|
||||
"sha256": "5b187d24778bbafd31edae96267ff4e773082751a9a5ca35b7cc12a0a72d5374"
|
||||
},
|
||||
{
|
||||
"path": "run-focused4.ps1",
|
||||
"bytes": 1034,
|
||||
"sha256": "54b4232f4df45bb2ae76597aca6060b106c1cf194196b3bcface2d8f14f60084"
|
||||
},
|
||||
{
|
||||
"path": "run-focused5.ps1",
|
||||
"bytes": 1034,
|
||||
"sha256": "bd14bb9955d15d33fdab771c481a899014eb1abfa5061b25f8b0fa10bf79c671"
|
||||
},
|
||||
{
|
||||
"path": "test-binary.json",
|
||||
"bytes": 343,
|
||||
"sha256": "af2ebfa351a216f1a7be96c1de983abbcc346ef845138e87c0f16225b11e15ec"
|
||||
},
|
||||
{
|
||||
"path": "corrections.patch",
|
||||
"bytes": 39037,
|
||||
"sha256": "037581f957f619728bffbf024e9b99cbc78d7a03994108c4e08bbfcb9a49f81e"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"Algorithm": "SHA256",
|
||||
"Hash": "3D57FF54D159D4BBB4FE0B6A368F8DAD0C70B5A4D40A6C19FD0F2197CB06624E",
|
||||
"Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-desktop\\src\\views.rs"
|
||||
},
|
||||
{
|
||||
"Algorithm": "SHA256",
|
||||
"Hash": "370C2CE1B58F5AAD4CFBBBAE4A774670A20C6F4F5F1A77305DAF8CE221F80DBE",
|
||||
"Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-desktop\\src\\app.rs"
|
||||
},
|
||||
{
|
||||
"Algorithm": "SHA256",
|
||||
"Hash": "00D21572B36D15908E75F0BC954951229FB42C640C6B81B7C424E6B931407D20",
|
||||
"Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-desktop\\src\\app\\source_symbols.rs"
|
||||
},
|
||||
{
|
||||
"Algorithm": "SHA256",
|
||||
"Hash": "ED82648DC6E7A69EB55BE6134CBD78452ADDD250AAEB759F94530C95601D840D",
|
||||
"Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-desktop\\src\\app\\source_symbols\\tests.rs"
|
||||
}
|
||||
]
|
||||
+5106
File diff suppressed because it is too large
Load Diff
+448
@@ -0,0 +1,448 @@
|
||||
//! Current draft navigation, isolated from model application and disk discovery.
|
||||
use super::*;
|
||||
use crate::source_navigation::{self as nav, query};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
fn admission_notice(error: query::AdmissionError) -> String {
|
||||
use nav::symbols::{BuildError, InputProblem, Resource};
|
||||
let (limit, resource) = match error {
|
||||
query::AdmissionError::Index(BuildError::Limit { limit, resource, .. }) => (limit, resource),
|
||||
query::AdmissionError::Outline { error, .. } => (error.limit, match error.resource {
|
||||
language::OutlineResource::SourceBytes => Resource::SourceBytes,
|
||||
language::OutlineResource::Tokens => Resource::Tokens,
|
||||
language::OutlineResource::Declarations => Resource::Records,
|
||||
}),
|
||||
query::AdmissionError::Index(BuildError::Input { problem, .. }) => return match problem {
|
||||
InputProblem::NonCanonicalPath | InputProblem::DuplicateOrCaseCollidingPath =>
|
||||
"Navigation needs distinct relative source paths. Check file names and include paths.".into(),
|
||||
_ => "Navigation could not verify the current source locations. Correct source errors and try again.".into(),
|
||||
},
|
||||
};
|
||||
let unit = match resource {
|
||||
Resource::Files => "loaded files",
|
||||
Resource::SourceBytes => "source bytes",
|
||||
Resource::PathBytes => "bytes in file paths",
|
||||
Resource::Tokens => "source tokens",
|
||||
Resource::Records => "declarations and references",
|
||||
};
|
||||
format!(
|
||||
"Navigation reached its limit of {limit} {unit}. Reduce the loaded source size and try again."
|
||||
)
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn source_symbol_context(&self) -> nav::SymbolContext {
|
||||
nav::SymbolContext {
|
||||
session: self.source_workspace.session(),
|
||||
path: self.source_workspace.active.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_symbol_message(&self, event: nav::SymbolEvent) -> Message {
|
||||
Message::SourceSymbols(self.source_symbol_context(), event)
|
||||
}
|
||||
pub fn source_key(&self) -> query::SnapshotKey {
|
||||
query::SnapshotKey {
|
||||
workspace: self.source_workspace.session(),
|
||||
revision: self.source_workspace.revision(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_source_input<T>(&self, use_input: impl FnOnce(query::Input<'_>) -> T) -> T {
|
||||
let files: Vec<_> = self
|
||||
.source_workspace
|
||||
.files
|
||||
.iter()
|
||||
.map(|(path, buffer)| query::DraftFile {
|
||||
path,
|
||||
text: &buffer.text,
|
||||
})
|
||||
.collect();
|
||||
let accepted = self.document.bundle();
|
||||
let validated: Vec<_> = accepted
|
||||
.files
|
||||
.iter()
|
||||
.map(|(path, text)| query::DraftFile { path, text })
|
||||
.collect();
|
||||
use_input(query::Input {
|
||||
key: self.source_key(),
|
||||
entry: &self.source_workspace.entry,
|
||||
files: &files,
|
||||
applied: self.source_workspace.applied,
|
||||
validation: self
|
||||
.source_workspace
|
||||
.applied
|
||||
.then_some(query::ValidatedSources {
|
||||
entry: &accepted.entry,
|
||||
files: &validated,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn source_snapshot_matches(&self, snapshot: &query::Snapshot) -> bool {
|
||||
snapshot.key() == self.source_key()
|
||||
&& self.with_source_input(|input| snapshot.matches(input))
|
||||
}
|
||||
|
||||
pub fn source_navigation_available(&self) -> bool {
|
||||
self.page == Page::Text
|
||||
&& !self.busy
|
||||
&& self.pending.is_none()
|
||||
&& self.file_prompt.is_none()
|
||||
&& !self.delete_pending
|
||||
&& self.workflow.is_none()
|
||||
&& !self.hosted.visible
|
||||
&& self.model_menu.is_none()
|
||||
&& self.architecture.is_none()
|
||||
&& !self.behavior.has_pending()
|
||||
}
|
||||
|
||||
/// Mounted widget IDs change when another interaction supersedes a reveal.
|
||||
/// A queued focus operation therefore cannot find a subsequently reused editor.
|
||||
pub fn source_editor_id(&self) -> iced::widget::Id {
|
||||
iced::widget::Id::from(format!(
|
||||
"source-editor-{}-{}-{}",
|
||||
self.source_workspace.session(),
|
||||
self.source_workspace.active,
|
||||
self.source_symbols.interaction
|
||||
))
|
||||
}
|
||||
|
||||
pub fn source_reveal_ticket(&self) -> Option<Id> {
|
||||
self.source_symbols
|
||||
.reveal
|
||||
.as_ref()
|
||||
.map(|reveal| reveal.ticket)
|
||||
}
|
||||
|
||||
pub(super) fn source_interaction(&mut self) {
|
||||
self.source_symbols.interaction = Id::new_v4();
|
||||
self.source_symbols.request = None;
|
||||
self.source_symbols.reveal = None;
|
||||
self.source_jump = None;
|
||||
}
|
||||
|
||||
pub(super) fn queue_source_reveal(&mut self, cursor: text_editor::Cursor, select_line: bool) {
|
||||
self.source_jump = Some(cursor);
|
||||
self.source_select_line = select_line;
|
||||
self.source_symbols.reveal = Some(nav::Reveal {
|
||||
ticket: Id::new_v4(),
|
||||
interaction: self.source_symbols.interaction,
|
||||
raw_interaction: self.source_symbols.raw_interaction.load(Ordering::SeqCst),
|
||||
snapshot: self.source_key(),
|
||||
path: self.source_workspace.active.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn source_viewport(&mut self, ticket: Option<Id>) -> Task<Message> {
|
||||
let Some(reveal) = &self.source_symbols.reveal else {
|
||||
return Task::none();
|
||||
};
|
||||
if Some(reveal.ticket) != ticket
|
||||
|| reveal.interaction != self.source_symbols.interaction
|
||||
|| !nav::interaction::is_current(
|
||||
&self.source_symbols.raw_interaction,
|
||||
reveal.raw_interaction,
|
||||
)
|
||||
|| reveal.snapshot != self.source_key()
|
||||
|| reveal.path != self.source_workspace.active
|
||||
|| !self.source_navigation_available()
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
let raw_interaction = reveal.raw_interaction;
|
||||
self.source_symbols.reveal = None;
|
||||
if let Some(cursor) = self.source_jump.take() {
|
||||
if !nav::exact_cursor(self.source_workspace.text(), cursor) {
|
||||
return Task::none();
|
||||
}
|
||||
self.source_editor.move_to(cursor);
|
||||
if self.source_select_line && cursor.selection.is_none() {
|
||||
self.source_editor.perform(text_editor::Action::SelectLine);
|
||||
}
|
||||
return nav::interaction::guarded_focus(
|
||||
self.source_editor_id(),
|
||||
self.source_symbols.raw_interaction.clone(),
|
||||
raw_interaction,
|
||||
);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
pub(super) fn source_symbol_event(&mut self, event: nav::SymbolEvent) -> Task<Message> {
|
||||
if !self.source_navigation_available() {
|
||||
return Task::none();
|
||||
}
|
||||
match event {
|
||||
nav::SymbolEvent::Close => {
|
||||
self.source_symbols.open = false;
|
||||
self.source_symbols.results = None;
|
||||
self.source_symbols.notice = None;
|
||||
}
|
||||
nav::SymbolEvent::Back => {
|
||||
self.source_symbols.open = true;
|
||||
self.source_symbol_back();
|
||||
}
|
||||
nav::SymbolEvent::Choose {
|
||||
stamp,
|
||||
reference,
|
||||
index,
|
||||
} => {
|
||||
let Some(results) = self.source_symbols.results.clone() else {
|
||||
return Task::none();
|
||||
};
|
||||
if results.stamp != stamp
|
||||
|| stamp.snapshot != self.source_key()
|
||||
|| self
|
||||
.source_symbols
|
||||
.cache
|
||||
.as_ref()
|
||||
.is_none_or(|cache| !self.source_snapshot_matches(cache))
|
||||
{
|
||||
self.source_symbols.notice =
|
||||
Some("Source changed. Run navigation again for current locations.".into());
|
||||
return Task::none();
|
||||
}
|
||||
let destination = if reference {
|
||||
results
|
||||
.references
|
||||
.get(index)
|
||||
.map(|row| (&row.path, row.position, row.end_position))
|
||||
} else {
|
||||
results
|
||||
.definitions
|
||||
.get(index)
|
||||
.map(|row| (&row.path, row.position, row.end_position))
|
||||
};
|
||||
if let Some((path, start, end)) = destination {
|
||||
self.source_symbol_jump(path, start, end);
|
||||
}
|
||||
}
|
||||
nav::SymbolEvent::Query(action) => return self.start_source_query(action),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn start_source_query(&mut self, action: query::Action) -> Task<Message> {
|
||||
self.source_symbols.open = true;
|
||||
if !nav::interaction::is_current(
|
||||
&self.source_symbols.raw_interaction,
|
||||
self.source_symbols.raw_interaction.load(Ordering::SeqCst),
|
||||
) {
|
||||
self.source_symbols.notice = Some("Reopen this workspace to reset navigation.".into());
|
||||
return Task::none();
|
||||
}
|
||||
if self.source_symbols.in_flight.is_some() {
|
||||
self.source_symbols.notice = Some("Navigation is finishing. Try again shortly.".into());
|
||||
return Task::none();
|
||||
}
|
||||
let cursor = self.source_editor.cursor();
|
||||
let Some(offset) = nav::cursor_offset(self.source_workspace.text(), cursor) else {
|
||||
self.source_symbols.notice =
|
||||
Some("Place the cursor on an alias or its @ reference.".into());
|
||||
return Task::none();
|
||||
};
|
||||
self.source_symbols.serial = match self.source_symbols.serial.checked_add(1) {
|
||||
Some(serial) => serial,
|
||||
None => {
|
||||
self.source_workspace.reset_navigation_session();
|
||||
self.source_symbols.back.clear();
|
||||
self.source_symbols.cache = None;
|
||||
0
|
||||
}
|
||||
};
|
||||
let request = nav::Request {
|
||||
job: Id::new_v4(),
|
||||
interaction: self.source_symbols.interaction,
|
||||
raw_interaction: self.source_symbols.raw_interaction.load(Ordering::SeqCst),
|
||||
stamp: query::QueryStamp {
|
||||
snapshot: self.source_key(),
|
||||
serial: self.source_symbols.serial,
|
||||
},
|
||||
path: self.source_workspace.active.clone(),
|
||||
offset,
|
||||
cursor,
|
||||
action,
|
||||
};
|
||||
let cache = self
|
||||
.source_symbols
|
||||
.cache
|
||||
.as_ref()
|
||||
.filter(|cache| self.source_snapshot_matches(cache))
|
||||
.cloned();
|
||||
let input = if cache.is_none() {
|
||||
match self
|
||||
.with_source_input(|input| query::admit_owned(input, query::Limits::default()))
|
||||
{
|
||||
Ok(input) => Some(input),
|
||||
Err(error) => {
|
||||
self.source_symbols.notice = Some(admission_notice(error));
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.source_symbols.in_flight = Some(request.job);
|
||||
self.source_symbols.request = Some(request.clone());
|
||||
self.source_symbols.results = None;
|
||||
self.source_symbols.notice = None;
|
||||
Task::perform(
|
||||
async move {
|
||||
let worker_request = request.clone();
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
let snapshot = match cache {
|
||||
Some(cache) => cache,
|
||||
None => Arc::new(
|
||||
input
|
||||
.expect("admitted input for uncached request")
|
||||
.capture()
|
||||
.map_err(admission_notice)?,
|
||||
),
|
||||
};
|
||||
let results = snapshot
|
||||
.query(query::Query {
|
||||
stamp: worker_request.stamp,
|
||||
path: &worker_request.path,
|
||||
byte_offset: worker_request.offset,
|
||||
action: worker_request.action,
|
||||
})
|
||||
.map_err(|error| match error {
|
||||
query::QueryError::ResultEnvelopeLimit => "These result details exceed the display limit. Try literal Find for this alias.".to_owned(),
|
||||
_ => "Navigation could not verify this location. Place the cursor on an alias and try again.".to_owned(),
|
||||
})?;
|
||||
Ok((snapshot, Arc::new(results)))
|
||||
})
|
||||
.await
|
||||
.unwrap_or_else(|error| Err(format!("Navigation task failed: {error}")));
|
||||
nav::Completion { request, result }
|
||||
},
|
||||
Message::SourceSymbolsReady,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn source_symbols_ready(&mut self, completion: nav::Completion) -> Task<Message> {
|
||||
let request = completion.request;
|
||||
if self.source_symbols.in_flight != Some(request.job) {
|
||||
return Task::none();
|
||||
}
|
||||
self.source_symbols.in_flight = None;
|
||||
let wanted = self
|
||||
.source_symbols
|
||||
.request
|
||||
.take()
|
||||
.is_some_and(|current| current.job == request.job)
|
||||
&& request.interaction == self.source_symbols.interaction
|
||||
&& request.stamp.snapshot == self.source_key()
|
||||
&& nav::interaction::is_current(
|
||||
&self.source_symbols.raw_interaction,
|
||||
request.raw_interaction,
|
||||
)
|
||||
&& request.path == self.source_workspace.active
|
||||
&& request.cursor == self.source_editor.cursor()
|
||||
&& self.source_navigation_available();
|
||||
if !wanted {
|
||||
return Task::none();
|
||||
}
|
||||
match completion.result {
|
||||
Ok((snapshot, results)) if self.source_snapshot_matches(&snapshot) => {
|
||||
self.source_symbols.cache = Some(snapshot);
|
||||
self.source_symbols.results = Some(results.clone());
|
||||
if results.disposition == query::Disposition::Direct
|
||||
&& let Some(row) = results.definitions.first()
|
||||
{
|
||||
self.source_symbol_jump(&row.path, row.position, row.end_position);
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
self.source_symbols.notice = Some("Source changed. Run navigation again.".into())
|
||||
}
|
||||
Err(error) => self.source_symbols.notice = Some(error),
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn source_symbol_jump(&mut self, path: &str, start: query::Position, end: query::Position) {
|
||||
let cursor = text_editor::Cursor {
|
||||
position: text_editor::Position {
|
||||
line: start.line,
|
||||
column: start.byte_column,
|
||||
},
|
||||
selection: Some(text_editor::Position {
|
||||
line: end.line,
|
||||
column: end.byte_column,
|
||||
}),
|
||||
};
|
||||
if self
|
||||
.source_workspace
|
||||
.files
|
||||
.get(path)
|
||||
.is_none_or(|buffer| !nav::exact_cursor(&buffer.text, cursor))
|
||||
{
|
||||
self.source_symbols.notice =
|
||||
Some("That source location is no longer available. Run navigation again.".into());
|
||||
return;
|
||||
}
|
||||
if let Some(buffer) = self
|
||||
.source_workspace
|
||||
.files
|
||||
.get(&self.source_workspace.active)
|
||||
{
|
||||
let origin = nav::HistoryEntry {
|
||||
session: self.source_workspace.session(),
|
||||
path: self.source_workspace.active.clone(),
|
||||
generation: buffer.generation(),
|
||||
cursor: self.source_editor.cursor(),
|
||||
};
|
||||
self.source_symbols
|
||||
.back
|
||||
.retain(|entry| entry.session == origin.session);
|
||||
if self.source_symbols.back.len() == 64 {
|
||||
self.source_symbols.back.remove(0);
|
||||
}
|
||||
self.source_symbols.back.push(origin);
|
||||
}
|
||||
self.source_workspace
|
||||
.switch(path, self.source_editor.cursor());
|
||||
self.show_source_file(false);
|
||||
self.queue_source_reveal(cursor, false);
|
||||
}
|
||||
|
||||
fn source_symbol_back(&mut self) {
|
||||
while let Some(origin) = self.source_symbols.back.pop() {
|
||||
if origin.session != self.source_workspace.session() {
|
||||
continue;
|
||||
}
|
||||
let Some(buffer) = self.source_workspace.files.get(&origin.path) else {
|
||||
self.source_symbols.notice = Some("The previous file is no longer loaded.".into());
|
||||
return;
|
||||
};
|
||||
let unchanged = origin.generation == buffer.generation();
|
||||
let cursor = if unchanged {
|
||||
origin.cursor
|
||||
} else if origin.path == self.source_workspace.active {
|
||||
self.source_editor.cursor()
|
||||
} else {
|
||||
buffer.cursor
|
||||
};
|
||||
if !nav::exact_cursor(&buffer.text, cursor) {
|
||||
self.source_symbols.notice = Some(
|
||||
"The previous location changed. Open the file to choose a current position."
|
||||
.into(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.source_workspace
|
||||
.switch(&origin.path, self.source_editor.cursor());
|
||||
self.show_source_file(false);
|
||||
self.queue_source_reveal(cursor, false);
|
||||
self.source_symbols.notice =
|
||||
(!unchanged).then(|| "This draft changed; its current cursor was restored.".into());
|
||||
return;
|
||||
}
|
||||
self.source_symbols.notice = Some("No previous source location in this workspace.".into());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
+1201
File diff suppressed because it is too large
Load Diff
+1362
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. ./.local/toolchain/env.ps1
|
||||
$env:CARGO_BUILD_JOBS = '1'
|
||||
$gateRoot = Join-Path (Get-Location) '.local/text-navigation-native-fixes-20260911'
|
||||
$gates = @(
|
||||
@{name='focused1'; args=@('test','--locked','--offline','-p','oa-desktop','--bin','arcadia-studio','source_','--','--test-threads=1')},
|
||||
@{name='clippy1'; args=@('clippy','--locked','--offline','-p','oa-desktop','--all-targets','--','-D','warnings')}
|
||||
)
|
||||
foreach ($gate in $gates) {
|
||||
$log = Join-Path $gateRoot ($gate.name + '.log')
|
||||
if (Test-Path -LiteralPath $log) { throw "Refusing to overwrite $($gate.name)" }
|
||||
$started = [DateTime]::UtcNow.ToString('o')
|
||||
$gateArgs = $gate.args
|
||||
& cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
$gateExit = $LASTEXITCODE
|
||||
[ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $gateRoot ($gate.name + '-status.json'))
|
||||
if ($gateExit -ne 0) { exit $gateExit }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. ./.local/toolchain/env.ps1
|
||||
$env:CARGO_BUILD_JOBS = '1'
|
||||
$gateRoot = Join-Path (Get-Location) '.local/text-navigation-native-fixes-20260911'
|
||||
$gates = @(
|
||||
@{name='focused2'; args=@('test','--locked','--offline','-p','oa-desktop','--bin','arcadia-studio','source_','--','--test-threads=1')},
|
||||
@{name='clippy2'; args=@('clippy','--locked','--offline','-p','oa-desktop','--all-targets','--','-D','warnings')}
|
||||
)
|
||||
foreach ($gate in $gates) {
|
||||
$log = Join-Path $gateRoot ($gate.name + '.log')
|
||||
if (Test-Path -LiteralPath $log) { throw "Refusing to overwrite $($gate.name)" }
|
||||
$started = [DateTime]::UtcNow.ToString('o')
|
||||
$gateArgs = $gate.args
|
||||
& cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
$gateExit = $LASTEXITCODE
|
||||
[ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $gateRoot ($gate.name + '-status.json'))
|
||||
if ($gateExit -ne 0) { exit $gateExit }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. ./.local/toolchain/env.ps1
|
||||
$env:CARGO_BUILD_JOBS = '1'
|
||||
$gateRoot = Join-Path (Get-Location) '.local/text-navigation-native-fixes-20260911'
|
||||
$gates = @(
|
||||
@{name='focused3'; args=@('test','--locked','--offline','-p','oa-desktop','--bin','arcadia-studio','source_','--','--test-threads=1')},
|
||||
@{name='clippy3'; args=@('clippy','--locked','--offline','-p','oa-desktop','--all-targets','--','-D','warnings')}
|
||||
)
|
||||
foreach ($gate in $gates) {
|
||||
$log = Join-Path $gateRoot ($gate.name + '.log')
|
||||
if (Test-Path -LiteralPath $log) { throw "Refusing to overwrite $($gate.name)" }
|
||||
$started = [DateTime]::UtcNow.ToString('o')
|
||||
$gateArgs = $gate.args
|
||||
& cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
$gateExit = $LASTEXITCODE
|
||||
[ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $gateRoot ($gate.name + '-status.json'))
|
||||
if ($gateExit -ne 0) { exit $gateExit }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. ./.local/toolchain/env.ps1
|
||||
$env:CARGO_BUILD_JOBS = '1'
|
||||
$gateRoot = Join-Path (Get-Location) '.local/text-navigation-native-fixes-20260911'
|
||||
$gates = @(
|
||||
@{name='focused4'; args=@('test','--locked','--offline','-p','oa-desktop','--bin','arcadia-studio','source_','--','--test-threads=1')},
|
||||
@{name='clippy4'; args=@('clippy','--locked','--offline','-p','oa-desktop','--all-targets','--','-D','warnings')}
|
||||
)
|
||||
foreach ($gate in $gates) {
|
||||
$log = Join-Path $gateRoot ($gate.name + '.log')
|
||||
if (Test-Path -LiteralPath $log) { throw "Refusing to overwrite $($gate.name)" }
|
||||
$started = [DateTime]::UtcNow.ToString('o')
|
||||
$gateArgs = $gate.args
|
||||
& cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
$gateExit = $LASTEXITCODE
|
||||
[ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $gateRoot ($gate.name + '-status.json'))
|
||||
if ($gateExit -ne 0) { exit $gateExit }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. ./.local/toolchain/env.ps1
|
||||
$env:CARGO_BUILD_JOBS = '1'
|
||||
$gateRoot = Join-Path (Get-Location) '.local/text-navigation-native-fixes-20260911'
|
||||
$gates = @(
|
||||
@{name='focused5'; args=@('test','--locked','--offline','-p','oa-desktop','--bin','arcadia-studio','source_','--','--test-threads=1')},
|
||||
@{name='clippy5'; args=@('clippy','--locked','--offline','-p','oa-desktop','--all-targets','--','-D','warnings')}
|
||||
)
|
||||
foreach ($gate in $gates) {
|
||||
$log = Join-Path $gateRoot ($gate.name + '.log')
|
||||
if (Test-Path -LiteralPath $log) { throw "Refusing to overwrite $($gate.name)" }
|
||||
$started = [DateTime]::UtcNow.ToString('o')
|
||||
$gateArgs = $gate.args
|
||||
& cargo @gateArgs 2>&1 | Tee-Object -FilePath $log
|
||||
$gateExit = $LASTEXITCODE
|
||||
[ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $gateRoot ($gate.name + '-status.json'))
|
||||
if ($gateExit -ne 0) { exit $gateExit }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\arcadia_studio-58e30cee1fd51a8b.exe",
|
||||
"bytes": 110330368,
|
||||
"sha256": "6FCB1E9D264A709297DEA9856E5927DCEEDEB39C84938D4EEB46B320A3514D7D",
|
||||
"scope": "Executed focused5 binary identity; binary not copied into this evidence directory."
|
||||
}
|
||||
@@ -113,6 +113,10 @@ The subsequent [initial native trial](release-text-navigation-native-initial-202
|
||||
verified useful navigation and exact saved-source/model equality, but found four
|
||||
interaction defects. It is not accepted for release. Corrections and a matching
|
||||
new native evaluation are required; earlier build/test results keep their scope.
|
||||
The subsequent [four-file correction](release-text-navigation-native-corrections-20260911.md)
|
||||
passed 188 focused tests and strict Clippy after independent review. Its raw
|
||||
inputs and failed attempts are preserved separately; corrected native acceptance
|
||||
is still pending.
|
||||
|
||||
- [Component and prepared fixture archive](experiments/text-navigation-components-20260911/README.md)
|
||||
- [Desktop gate archive](experiments/text-navigation-desktop-20260911/README.md)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
# Native navigation corrections — 2026-09-11
|
||||
|
||||
Codex-9e8r6t `/root`; [issue 6](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/6).
|
||||
The four-file correction above `f4e426ae2a6971395e1ca7e6f094b0c20056cf93`
|
||||
passes its focused Windows gates and independent source review. A corrected
|
||||
full desktop run, GUI and native acceptance remain separate requirements.
|
||||
|
||||
## Changes and review
|
||||
|
||||
The [initial DF71 native trial](release-text-navigation-native-initial-20260911.md)
|
||||
found four interaction defects. The correction reserves the existing bounded
|
||||
header's outer height so lint cannot move the editor; keeps results closed on a
|
||||
successful Back without a notice; dismisses Find alone through the existing
|
||||
captured-Escape bridge; and suppresses exactly focused Windows Alt-only Space
|
||||
in the source editor. Ordinary Space, shifted input and Ctrl+Alt/AltGr remain
|
||||
on their existing paths. The header still scrolls when its contents overflow.
|
||||
|
||||
Find Close and ordinary dismissing Escape restore the existing source selection
|
||||
through the guarded viewport/focus ticket. Modal dismissal retains priority.
|
||||
Independent review found that the previously unscoped Find Close could now
|
||||
restore focus in a newer workspace or Find invocation. Its actual button message
|
||||
now carries session/path plus an invocation token renewed on every Find Open.
|
||||
Stale envelopes and bare legacy Close are rejected before invalidating current
|
||||
navigation; current Close still works. Review2 accepted this closure. Root also
|
||||
reviewed the final renderer-bound and evaluator-only changes.
|
||||
|
||||
## Verified scope
|
||||
|
||||
- `cargo test --locked --offline -p oa-desktop --bin arcadia-studio source_ -- --test-threads=1`:
|
||||
**188 passed, 0 failed, 1 ignored, 562 filtered**, 41.73 s execution,
|
||||
finished 19:38:11 UTC.
|
||||
- `cargo clippy --locked --offline -p oa-desktop --all-targets -- -D warnings`:
|
||||
**passed**, finished 19:40:12 UTC. Scoped formatting and diff checks passed.
|
||||
- All **499 inputs / 9,470,838 bytes** were unchanged at 19:41:08 UTC.
|
||||
ZIP members were also compared with the manifest and current source bytes.
|
||||
Final correction `inputs4` archive SHA256:
|
||||
`7ff9e5703e33bd72e5c2186dd61125e965b82b8dfbf085961d514b005e716798`.
|
||||
|
||||
Seven added regressions exercise Back visibility, actual captured Find Escape,
|
||||
stale Find Close across reset/file switch/reopen, mounted guarded focus, stable
|
||||
editor geometry, real Scrollable wheel/offset behavior, and text-bearing Windows
|
||||
key events with ordinary/international input controls. Affected App assertions
|
||||
compare accepted Project, raw SourceBundle, selection and history.
|
||||
|
||||
Geometry uses the shared production layout helper with real paragraph/editor
|
||||
metrics and representative dynamic header content, not the whole App view.
|
||||
The focus tree verifies removal of Find and later foreign focus; its raw-clock
|
||||
case also changes focus, so it does not independently isolate that race end to
|
||||
end. Existing primitive guards provide narrower separate coverage. The supplied
|
||||
Alt+Space payload test is not instrumentation of the native Windows event.
|
||||
Actual settled menu/neutral-click behavior must still be repeated on a new GUI.
|
||||
|
||||
## Preserved failures
|
||||
|
||||
The first PowerShell 5 wrapper stopped on ordinary Cargo stderr. The corrected
|
||||
PowerShell 7 run reached three real E0659 macro-import errors and was stopped
|
||||
after the independent stale-Close finding; both diagnostics and interruption
|
||||
are retained. Inputs2 then failed compilation because the generic helper needed
|
||||
`text::Renderer`. Inputs3 fixed only that bound; 187 tests passed, but its geometry
|
||||
test incorrectly equated the short inner Scrollable with the fixed outer slot
|
||||
(243.70001 versus 294 px). Inputs4 corrected only the measurement to the actual
|
||||
outer Container/editor boundary. Exact editor rectangle stability and the
|
||||
separate overflow/offset assertions then passed. No production layout change
|
||||
was made merely to satisfy that assertion. Failed runs are not Clippy or native
|
||||
passes; every earlier input snapshot retains its own readback.
|
||||
|
||||
## Evidence and continuation
|
||||
|
||||
[Raw correction archive](experiments/text-navigation-native-20260911/corrections/manifest.json):
|
||||
44 payloads, 9,167,596 bytes; manifest SHA256
|
||||
`25a210de4a65b2221007e077fe72dfe0bb68483a068127e122839798aa4da332`.
|
||||
It preserves original files, exact source ZIPs, helpers, patch, actual logs and
|
||||
receipts, independent reviews and final gate result. The test executable has an
|
||||
identity receipt; its large binary is not included.
|
||||
|
||||
The next native evaluation must repeat the four failures, existing navigation
|
||||
and draft/Undo behavior, invalid-source repair and the realistic 30-file
|
||||
engineering walkthrough, followed by explicit Save and complete saved equality.
|
||||
The existing contextual release remains unchanged. Linux/WSL remains paused.
|
||||
Reference in New Issue
Block a user