From 84f95855887c9fae1ca43079bf3c823a4c54350a Mon Sep 17 00:00:00 2001 From: WHC Date: Sun, 11 Oct 2026 15:26:32 +0800 Subject: [PATCH] v0.9.1: the window opens on the session the engine already has open A workspace is never empty on the second run: cst_agent_open() resumes the last session of the store, so the engine holds a transcript before the window exists. The window only filled its view on a session *switch*, and a start is not a switch -- the pane came up empty next to a sidebar pointing at the very session it belonged to (on the phone: "hello, 6 messages" highlighted, no transcript under it). - Main.qml: Component.onCompleted reloads the session list and replays the open session, the same call the sessionChanged handler makes. - AgentCore::switchSession(): a click on the row of the session that is already open emits sessionChanged instead of returning quietly, so the row redraws its transcript rather than being a dead spot. - --uitest: the window is now created over a seeded session (a fixture is written and switched to before the QML loads, which is what a restart looks like) and the check "the open session is on screen at startup" pins it -- it failed with 0 rows before the fix. Two qWaits in the settings-dialog section, whose clicks were racing the dialog's layout: the extra startup work made that visible (5 failures that had nothing to do with sessions). Verified: --uitest 79/79 (the new check among them), cst-app-selftest OK, and on the phone -- the APK rebuilt and installed, a restart of the app draws the whole stored session (messages, tool cards, thinking blocks, copy icons). Version 0.9.1. mode = coding --- app/qml/Main.qml | 9 ++++++++- app/src/core/AgentCore.cpp | 7 ++++++- app/src/ui/UiTest.cpp | 20 ++++++++++++++++++++ include/cst_version.h | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/qml/Main.qml b/app/qml/Main.qml index ab26331..24fb203 100644 --- a/app/qml/Main.qml +++ b/app/qml/Main.qml @@ -191,7 +191,14 @@ ApplicationWindow { settings.syncPick() } - Component.onCompleted: win.reloadSessions() + /* The engine opens the workspace's last session (cst_agent_open()), so the + * window is created with a transcript already in it: draw it, or the pane + * starts empty while the sidebar points at the very session it belongs to. + * A switch draws it the same way (the sessionChanged handler below). */ + Component.onCompleted: { + win.reloadSessions() + win.replay() + } // ---- the shell ------------------------------------------------------- header: ToolBar { diff --git a/app/src/core/AgentCore.cpp b/app/src/core/AgentCore.cpp index 118b5f8..07f9293 100644 --- a/app/src/core/AgentCore.cpp +++ b/app/src/core/AgentCore.cpp @@ -551,8 +551,13 @@ bool AgentCore::newSession() { bool AgentCore::switchSession(const QString &id) { if (!m_agent || m_running || id.isEmpty()) return false; - if (id == sessionId()) + if (id == sessionId()) { + /* A click on the row of the session that is already open: nothing to + * switch, but the window still wants the transcript (the row is not a + * dead spot), and sessionChanged is what draws it. */ + emit sessionChanged(); return true; + } if (cst_agent_switch_session(m_agent, id.toUtf8().constData()) != 0) return false; afterSessionChange(); diff --git a/app/src/ui/UiTest.cpp b/app/src/ui/UiTest.cpp index c855fe5..16a21db 100644 --- a/app/src/ui/UiTest.cpp +++ b/app/src/ui/UiTest.cpp @@ -196,6 +196,15 @@ int runUiTest(AgentCore &core, const QString &size) { } } + /* A workspace is never empty on a second run: the engine opens the last + * session of the store (cst_agent_open()), so the window is created with a + * transcript already in the engine. Put one there before the QML loads, + * which is exactly what a restart looks like. */ + const QString resumed = + writeFixtureSession(core.workspace(), QStringLiteral("20260101-115959-on")); + check(!resumed.isEmpty() && core.switchSession(resumed), + QStringLiteral("the engine has a session open before the window exists")); + QQmlApplicationEngine engine; installThemeIcons(&engine); /* the window's icons, same as main() */ /* image://cstattach/: the session's stored images, pointed at the session @@ -219,6 +228,15 @@ int runUiTest(AgentCore &core, const QString &size) { } QTest::qWait(250); + /* --- the window opens on the session the engine already had ----------- */ + { + QQuickItem *tr = item(win, QStringLiteral("transcript")); + const int rows = tr ? tr->property("count").toInt() : -1; + check(rows == 5, + QStringLiteral("the open session is on screen at startup (%1 rows)") + .arg(rows)); + } + QQuickItem *docked = item(win, QStringLiteral("dockedSidebar")); QQuickItem *navButton = item(win, QStringLiteral("navButton")); QQuickItem *navPanel = item(win, QStringLiteral("navPanel")); @@ -464,8 +482,10 @@ int runUiTest(AgentCore &core, const QString &size) { /* The dialog drives the same two engine calls: open it, fold the advanced * section out and put a provider in through the form. */ clickMouse(win, QStringLiteral("settingsButton")); + QTest::qWait(150); /* the dialog's layout settles before it is clicked into */ if (panel && mfield) { clickMouse(win, QStringLiteral("settingsAdvanced")); + QTest::qWait(150); /* the fold-out re-lays the body out */ QQuickItem *nfield = item(win, QStringLiteral("advName")); QQuickItem *bfield = item(win, QStringLiteral("advBase")); check(nfield && nfield->isVisible(), QStringLiteral("Advanced options opens in the dialog")); diff --git a/include/cst_version.h b/include/cst_version.h index e694393..7fd7986 100644 --- a/include/cst_version.h +++ b/include/cst_version.h @@ -3,7 +3,7 @@ #define CST_VERSION_MAJOR 0 #define CST_VERSION_MINOR 9 -#define CST_VERSION_PATCH 0 +#define CST_VERSION_PATCH 1 #define CST_VERSION_STR_(x) #x #define CST_VERSION_STR(x) CST_VERSION_STR_(x)