Restore inline naming focus after recovery

This commit is contained in:
2026-09-10 11:25:06 -07:00
parent 057edddef8
commit 31b7dc3e15
5 changed files with 234 additions and 5 deletions
+181 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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.