Restore inline naming focus after recovery
This commit is contained in:
@@ -202,7 +202,16 @@ impl App {
|
||||
} else {
|
||||
"Recovered an unsaved local copy. Choose Save as to save it; the original file and hosted model are unchanged.".into()
|
||||
};
|
||||
self.request_lint()
|
||||
let lint = self.request_lint();
|
||||
if self
|
||||
.architecture
|
||||
.as_ref()
|
||||
.is_some_and(architecture_authoring::State::inline)
|
||||
{
|
||||
Task::batch([lint, iced::widget::operation::focus("architecture-name")])
|
||||
} else {
|
||||
lint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +340,177 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn run_recovery_tasks(app: &mut App, task: Task<Message>) -> (usize, bool, bool) {
|
||||
use iced::advanced::{Layout, layout, widget};
|
||||
use iced::futures::StreamExt;
|
||||
// Mount real inputs, with focus initially in another control. This
|
||||
// verifies the returned operation targets the naming field by its ID.
|
||||
let mut fields: iced::Element<'_, (), iced::Theme, ()> = iced::widget::column![
|
||||
iced::widget::text_input("Engineering name", "Retained name")
|
||||
.id("architecture-name")
|
||||
.on_input(|_| ()),
|
||||
iced::widget::text_input("Other control", "Other value")
|
||||
.id("unrelated-field")
|
||||
.on_input(|_| ())
|
||||
]
|
||||
.into();
|
||||
let mut tree = widget::Tree::new(fields.as_widget());
|
||||
let node = fields.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
&(),
|
||||
&layout::Limits::new(iced::Size::ZERO, iced::Size::new(320., 100.)),
|
||||
);
|
||||
tree.children[1]
|
||||
.state
|
||||
.downcast_mut::<iced::widget::text_input::State<()>>()
|
||||
.focus();
|
||||
let mut operations = 0;
|
||||
let mut lint_completions = 0;
|
||||
let mut stream = iced_runtime::task::into_stream(task).expect("Recovery must still lint");
|
||||
while let Some(action) = stream.next().await {
|
||||
match action {
|
||||
iced_runtime::Action::Widget(mut operation) => {
|
||||
operations += 1;
|
||||
fields.as_widget_mut().operate(
|
||||
&mut tree,
|
||||
Layout::new(&node),
|
||||
&(),
|
||||
operation.as_mut(),
|
||||
);
|
||||
}
|
||||
iced_runtime::Action::Output(message @ Message::Linted(_, _)) => {
|
||||
lint_completions += 1;
|
||||
let _ = app.update(message);
|
||||
}
|
||||
_ => panic!("Unexpected recovery task action"),
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
lint_completions, 1,
|
||||
"Autofocus must not replace request_lint"
|
||||
);
|
||||
assert!(app.lint_running.is_none());
|
||||
let focused = |index: usize| {
|
||||
tree.children[index]
|
||||
.state
|
||||
.downcast_ref::<iced::widget::text_input::State<()>>()
|
||||
.is_focused()
|
||||
};
|
||||
(operations, focused(0), focused(1))
|
||||
}
|
||||
|
||||
fn architecture_recovery_app() -> App {
|
||||
let mut app = App::with_project(Project::empty());
|
||||
let _ = app.update(Message::AddDiagram);
|
||||
let _ = app.update(Message::Architecture(architecture_authoring::Event::Start(
|
||||
Kind::Entity,
|
||||
Some(Position { x: 80., y: 90. }),
|
||||
)));
|
||||
let _ = app.update(Message::Architecture(architecture_authoring::Event::Name(
|
||||
"Accepted team".into(),
|
||||
)));
|
||||
let _ = app.update(Message::Architecture(architecture_authoring::Event::Submit));
|
||||
assert!(app.architecture.is_none());
|
||||
app
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn checkpoint_inline_create_and_rename_restore_focus_without_changing_drafts_or_history()
|
||||
{
|
||||
use architecture_authoring::Event;
|
||||
for rename in [false, true] {
|
||||
let mut app = architecture_recovery_app();
|
||||
let point = Position { x: 439., y: 328.2 };
|
||||
let event = if rename {
|
||||
Event::Rename(app.route_context().unwrap(), app.selected, point)
|
||||
} else {
|
||||
Event::Start(Kind::Actor, Some(point))
|
||||
};
|
||||
let _ = app.update(Message::Architecture(event));
|
||||
let _ = app.update(Message::Architecture(Event::Name(
|
||||
" Recovered engineering name 雨 ".into(),
|
||||
)));
|
||||
let saved = serde_json::to_value(&app.architecture.as_ref().unwrap().saved).unwrap();
|
||||
let snapshot = retained_snapshot(&app).with_architecture(app.architecture.as_ref());
|
||||
let expected = snapshot.clone().restore().unwrap();
|
||||
let checkpoint = snapshot.id;
|
||||
let mut recovered = App::with_project(Project::empty());
|
||||
let task = recovered.checkpoint_loaded(Ok(Arc::new(snapshot)));
|
||||
assert_eq!(
|
||||
run_recovery_tasks(&mut recovered, task).await,
|
||||
(1, true, false)
|
||||
);
|
||||
assert_eq!(recovered.page, Page::Diagram);
|
||||
assert!(recovered.architecture.as_ref().unwrap().inline());
|
||||
assert_eq!(
|
||||
serde_json::to_value(&recovered.architecture.as_ref().unwrap().saved).unwrap(),
|
||||
saved
|
||||
);
|
||||
assert_eq!(recovered.project(), expected.document.project());
|
||||
assert_eq!(recovered.document.bundle(), expected.document.bundle());
|
||||
assert_eq!(recovered.document.revision(), expected.document.revision());
|
||||
assert_eq!(recovered.document.can_undo(), expected.document.can_undo());
|
||||
assert_eq!(recovered.document.can_redo(), expected.document.can_redo());
|
||||
assert_eq!(
|
||||
recovered.source_workspace.bundle(),
|
||||
expected.workspace.bundle()
|
||||
);
|
||||
assert_eq!(recovered.recovery.restored, Some(checkpoint));
|
||||
assert!(recovered.path.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn checkpoint_noninline_contexts_keep_other_focus_and_still_lint() {
|
||||
use architecture_authoring::Event;
|
||||
for context in ["accepted", "armed", "picker", "property", "source"] {
|
||||
let mut app = architecture_recovery_app();
|
||||
match context {
|
||||
"armed" => {
|
||||
let _ = app.update(Message::Architecture(Event::Start(Kind::Actor, None)));
|
||||
}
|
||||
"picker" => {
|
||||
let _ = app.update(Message::Architecture(Event::Existing(None)));
|
||||
}
|
||||
"property" => {
|
||||
app.draft
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.input("/name", "Unapplied property".into());
|
||||
assert!(app.draft_dirty());
|
||||
}
|
||||
"source" => {
|
||||
app.source_workspace
|
||||
.edit("incomplete source 雨".into(), app.source_editor.cursor());
|
||||
assert!(app.source_dirty());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let snapshot = retained_snapshot(&app).with_architecture(app.architecture.as_ref());
|
||||
let expected = snapshot.clone().restore().unwrap();
|
||||
let mut recovered = App::with_project(Project::empty());
|
||||
let task = recovered.checkpoint_loaded(Ok(Arc::new(snapshot)));
|
||||
assert_eq!(
|
||||
run_recovery_tasks(&mut recovered, task).await,
|
||||
(0, false, true),
|
||||
"{context}"
|
||||
);
|
||||
assert!(
|
||||
!recovered
|
||||
.architecture
|
||||
.as_ref()
|
||||
.is_some_and(architecture_authoring::State::inline)
|
||||
);
|
||||
assert_eq!(recovered.project(), expected.document.project());
|
||||
assert_eq!(recovered.document.bundle(), expected.document.bundle());
|
||||
assert_eq!(recovered.document.revision(), expected.document.revision());
|
||||
assert_eq!(
|
||||
recovered.source_workspace.bundle(),
|
||||
expected.workspace.bundle()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saving_an_older_snapshot_keeps_recovery_for_newer_unsaved_work() {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
# First release execution plan
|
||||
|
||||
**Current next steps, 18:17 UTC:** integrate verified bundle `01bb52a` with our
|
||||
**Current next steps, 2026-09-10:** integrate verified bundle `01bb52a` with our
|
||||
published palette; accept the separate typed-history constructor correction;
|
||||
then compile and test process/scenario/history together. Implement the measured
|
||||
remaining-height scenario workspace after preserving the new navigation and
|
||||
clicked-slot contracts. Close packaging review, current shared core/server and
|
||||
specification gates, then optimized package/runtime and full engineering-story
|
||||
acceptance. Native control remains paused after user Escape; Linux runtime needs
|
||||
a viable environment. Neither condition narrows the original release objective.
|
||||
acceptance. The user has reauthorized native control; Linux runtime still needs
|
||||
a viable environment. The original release objective remains unchanged.
|
||||
|
||||
**Active execution, 2026-09-10:** finish the already-started architecture palette
|
||||
and inline naming with focused correctness checks, then perform actual native
|
||||
|
||||
+3
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Active continuation — 2026-09-10
|
||||
|
||||
Integration update (18:17 UTC): local branch is `codex/release1-9e8r6t`.
|
||||
Integration update (2026-09-10): local branch is `codex/release1-9e8r6t`.
|
||||
Palette `7d1df4f` is published separately. Received Codex-956atf's exact Git
|
||||
bundle (57,658 bytes, SHA256 `85A38B00207036DCB8DB24ED4A7E80B2BE670275EEB7B00413D36153322ADA5D`),
|
||||
verified its parent `1797516`, and published its original `01bb52a` commit on
|
||||
@@ -13,6 +13,8 @@ its cold toolchain/process claims cannot be mistaken for this host's state.
|
||||
Its optional history-value constructor cache defect has a follow-up pending.
|
||||
No integrated compile/native pass is claimed. Scenario layout follows the merge;
|
||||
the checkpoint worker independently restores inline name focus after recovery.
|
||||
The user subsequently reauthorized Computer Use explicitly. Root will resume
|
||||
native checks against separately staged current binaries after compilation.
|
||||
|
||||
Latest checkpoint (18:10 UTC, Codex-9e8r6t): palette compile repairs, bounded
|
||||
inline naming, stale-submit retention and focused first-Escape cancellation are
|
||||
|
||||
@@ -138,3 +138,27 @@ root again. No worker-owned Cargo process remains.
|
||||
Root's existing native stage remains the earlier `1B47C096…` binary and is not
|
||||
overwritten. The full integrated suite is deferred until the other-host behavior
|
||||
and forms changes are integrated; the earlier 588-test scope is preserved above.
|
||||
|
||||
## Recovery autofocus follow-up on integrated source
|
||||
|
||||
Root's native candidate 2 confirmed that recovered inline naming was visible but
|
||||
needed a click to focus. In `app/checkpoints.rs`, recovery now batches the
|
||||
unchanged lint task with focus of `architecture-name` only when the restored
|
||||
architecture state is inline. Armed tools, existing-element pickers and other
|
||||
recovery contexts retain their existing focus behavior.
|
||||
|
||||
Two tests consume the actual returned Task stream, operate on mounted Iced name
|
||||
and unrelated TextInputs, require lint completion and compare exact accepted
|
||||
source/model, raw draft, revision and history. Seven recovery contexts include
|
||||
Create/Rename and the non-inline/property/source controls. The integrated source
|
||||
at `057eddd` plus this checkpoint-only patch compiled in 4m 42s; all 13 checkpoint
|
||||
tests passed in 4.84s, with 605 filtered. Scoped rustfmt and diff check passed.
|
||||
Log: `.local/checkpoint-autofocus-tests-20260910.log`.
|
||||
|
||||
Test artifact SHA256:
|
||||
`D504C59BC2A5853F4FBE54A9DFA841DA0AC8DD2475342E45A91DBAA6533372B4`.
|
||||
Root copied this immutable artifact into
|
||||
`.local/integration-20260910/tests-057eddd/` and is executing its full 618-test
|
||||
inventory separately while later scenario edits proceed. That result is pending;
|
||||
it does not include the later F1 nullable-constructor correction or workspace
|
||||
layout. No new GUI executable or native autofocus pass is claimed.
|
||||
|
||||
@@ -116,3 +116,26 @@ No rename action was performed by that stopped call. Native control is paused;
|
||||
F2 rename, the remaining complete basic-editing sequence, transitions and final
|
||||
reopen/runtime acceptance remain pending. Source integration and packaging
|
||||
continue. Recovery autofocus remains a small confirmed usability follow-up.
|
||||
|
||||
## Resumed native F2 check
|
||||
|
||||
The user subsequently explicitly reauthorized Computer Use. Continued on the
|
||||
same separately staged candidate 2 (`E6D6B1D9…`), not the newly integrated source.
|
||||
Selecting Rescue team and pressing F2 opened the anchored, focused name editor.
|
||||
Before applying, an independent expected bundle was copied from the already
|
||||
oracle-verified Entity snapshot and changed only the unique quoted name in
|
||||
`oa/participants.arcadia` to `Rescue coordination team`; all other bytes and the
|
||||
complete file set were retained. Enter accepted the visible name, retained the
|
||||
same alias/UUID and geometry, and first-submit Ctrl+S saved it. All three saved
|
||||
files matched that expectation byte-for-byte. One Undo followed by Save passed
|
||||
the complete Entity model/three-source oracle. One Redo followed by Save matched
|
||||
the same name-only expectation exactly. Evidence is retained as
|
||||
`expected-entity-rename`, `checkpoint-entity-rename` and `rename-source-result.json`.
|
||||
The last Undo/Save returned the working fixture to the original Entity baseline.
|
||||
|
||||
Input limitation observed: after Ctrl+A selected the original name, the first
|
||||
Computer Use type_text call left an empty field. A fresh observation confirmed
|
||||
that state; explicit refocus and a second type_text produced the intended name.
|
||||
The cause is not established, so this is not recorded as a proven application
|
||||
typing defect or as uninterrupted first-attempt typing. Applying, history and
|
||||
source persistence were tested only after the correct text was visible.
|
||||
|
||||
Reference in New Issue
Block a user