From af458b4c6346f5b28666357346bf8d5ba2148c0a Mon Sep 17 00:00:00 2001 From: jbenedictocenteno Date: Fri, 13 Nov 2026 17:04:39 +0100 Subject: [PATCH] fix(kgui): Keep a write card's face through reports that name nobody An app's report that named no project dropped the face of the card it arrived on. A card can carry two transfers: the fetch of an app's data folder and the install it announced (kodediy/kode-sdk#123), or an install that starts again once its copies are on the card. The later one names nobody until its image is read, so the face went for the time a download takes to open and came back at the first chunk. The card now keeps its face through such a stretch and asks its folder again, once: the transfer before may be the one that wrote it, and the folder was last asked when the card rose. That is also what puts a freshly fetched icon on the card from the install's first report. --- components/kgui/kgui_ao.c | 17 +++++- components/kgui/kgui_wface.c | 20 +++++-- components/kgui/kgui_wface.h | 19 +++++-- components/kgui/test/test_kgui_wface.c | 72 ++++++++++++++++++++++++-- 4 files changed, 115 insertions(+), 13 deletions(-) diff --git a/components/kgui/kgui_ao.c b/components/kgui/kgui_ao.c index c093ac5..1512953 100644 --- a/components/kgui/kgui_ao.c +++ b/components/kgui/kgui_ao.c @@ -15014,7 +15014,11 @@ static void gui_write_face_take(Gui * const me) } /** - * @brief A report of a write: keep, drop or take the face its card wears. + * @brief A report of a write: keep, drop or take the face its card wears, or + * ask its folder again. + * + * Asked again, the face stays on the card as it is: the folder's answer is + * written over it and drawn when it has all arrived (gui_write_face_settle()). * * @param me The AO. * @param p The report. @@ -15028,11 +15032,20 @@ static void gui_write_face_report(Gui * const me, const AppProgressEvt *p) if ( KGUI_WFACE_TAKE == act ) { gui_write_face_take(me); + gui_write_face_show(me); } - if ( KGUI_WFACE_KEEP != act ) + else if ( KGUI_WFACE_DROP == act ) { gui_write_face_show(me); } + else if ( KGUI_WFACE_AGAIN == act ) + { + gui_ask_next_appdata(me); + } + else + { + /* The same app, or still nobody. */ + } } /** diff --git a/components/kgui/kgui_wface.c b/components/kgui/kgui_wface.c index b0c3f38..e71fa49 100644 --- a/components/kgui/kgui_wface.c +++ b/components/kgui/kgui_wface.c @@ -12,7 +12,7 @@ kgui_wface_act_t kgui_wface_report(kgui_wface_t *w, bool app, const char *projec { kgui_wface_act_t act = KGUI_WFACE_KEEP; - if ( !app || ('\0' == project[0]) ) + if ( !app ) { if ( kgui_wface_has(w) ) { @@ -20,15 +20,28 @@ kgui_wface_act_t kgui_wface_report(kgui_wface_t *w, bool app, const char *projec act = KGUI_WFACE_DROP; } } + else if ( '\0' == project[0] ) + { + /* The next transfer on the card, its image still unread: the face + * stays, and its folder is asked again, once for the stretch. */ + if ( kgui_wface_has(w) && !w->unnamed ) + { + w->unnamed = true; + w->owed = true; + act = KGUI_WFACE_AGAIN; + } + } else if ( strncmp(w->project, project, sizeof(w->project) - 1U) != 0 ) { (void)snprintf(w->project, sizeof(w->project), "%s", project); - w->owed = true; - act = KGUI_WFACE_TAKE; + w->owed = true; + w->unnamed = false; + act = KGUI_WFACE_TAKE; } else { /* The app the card already wears. */ + w->unnamed = false; } return act; } @@ -37,6 +50,7 @@ void kgui_wface_end(kgui_wface_t *w) { w->project[0] = '\0'; w->owed = false; + w->unnamed = false; } bool kgui_wface_has(const kgui_wface_t *w) diff --git a/components/kgui/kgui_wface.h b/components/kgui/kgui_wface.h index 1742a7a..b7a4ea6 100644 --- a/components/kgui/kgui_wface.h +++ b/components/kgui/kgui_wface.h @@ -9,8 +9,14 @@ * is named for its project and may be on the board before the app is: the * card wears that folder's name, icon and colour. This is the part that * decides, from each report, whether the card keeps the face it has, drops - * it, or takes a new one; the face itself and its folder's question are the - * launcher's. + * it, takes a new one, or asks the folder behind it again; the face itself and + * its folder's question are the launcher's. + * + * A card can carry more than one transfer: the fetch of an app's data folder + * and the install it announced, or an install that starts again once its + * copies are on the card. The reports of the later one name no project until + * its image is read. The card keeps its face through them, and the folder is + * asked again, once: the transfer before may be the one that wrote it. */ #ifndef KGUI_WFACE_H #define KGUI_WFACE_H @@ -28,13 +34,16 @@ extern "C" { typedef enum { KGUI_WFACE_KEEP = 0, /**< Nothing: the same app, or still nobody. */ KGUI_WFACE_DROP, /**< The card is about no app any more: the face goes. */ - KGUI_WFACE_TAKE /**< Another app: its face is to be taken. */ + KGUI_WFACE_TAKE, /**< Another app: its face is to be taken. */ + KGUI_WFACE_AGAIN /**< The same face: its folder is to be asked again. */ } kgui_wface_act_t; /** @brief Whose face the card wears. */ typedef struct { char project[KGUI_WFACE_PROJECT_MAX]; /**< The app's project name; "" for nobody. */ bool owed; /**< Its folder is still to be asked. */ + bool unnamed; /**< The last report named nobody, and the + * folder was asked again for it. */ } kgui_wface_t; /** @@ -45,7 +54,9 @@ typedef struct { * @param project The project the report names; "" when it names none. * @return KGUI_WFACE_TAKE for an app other than the one the card wears, with * @c owed set; KGUI_WFACE_DROP when the card wore a face and the - * report is no app's or names none; KGUI_WFACE_KEEP otherwise. + * report is no app's; KGUI_WFACE_AGAIN, with @c owed set, for the first + * of an app's reports that name nobody on a card that wears a face; + * KGUI_WFACE_KEEP otherwise. */ kgui_wface_act_t kgui_wface_report(kgui_wface_t *w, bool app, const char *project); diff --git a/components/kgui/test/test_kgui_wface.c b/components/kgui/test/test_kgui_wface.c index 668c241..0f58fb9 100644 --- a/components/kgui/test/test_kgui_wface.c +++ b/components/kgui/test/test_kgui_wface.c @@ -65,14 +65,74 @@ void test_a_write_that_is_no_app_drops_the_face(void) TEST_ASSERT_EQUAL_INT(KGUI_WFACE_KEEP, kgui_wface_report(&w, false, "")); } -void test_an_app_report_with_no_name_drops_a_face_of_another_write(void) +void test_an_app_report_with_no_name_keeps_the_face_and_asks_its_folder_again(void) { kgui_wface_t w = { 0 }; (void)kgui_wface_report(&w, true, "snake"); - /* A write whose project is not known yet: it is not the one before. */ - TEST_ASSERT_EQUAL_INT(KGUI_WFACE_DROP, kgui_wface_report(&w, true, "")); + w.owed = false; /* its folder was asked */ + /* The next transfer on the card, its image still unread: the install a + * fetch announced, or an install that starts again after its copies. */ + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_AGAIN, kgui_wface_report(&w, true, "")); + TEST_ASSERT_TRUE_MESSAGE(kgui_wface_is(&w, "snake"), + "the card lost its face between a fetch and the install it announced"); + TEST_ASSERT_TRUE_MESSAGE(w.owed, + "the folder the transfer before may have written was not asked again"); +} + +void test_a_nameless_stretch_asks_the_folder_once(void) +{ + kgui_wface_t w = { 0 }; + + (void)kgui_wface_report(&w, true, "snake"); + (void)kgui_wface_report(&w, true, ""); + w.owed = false; /* asked again */ + /* The same report every second while a download opens. */ + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_KEEP, kgui_wface_report(&w, true, "")); + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_KEEP, kgui_wface_report(&w, true, "")); + TEST_ASSERT_FALSE_MESSAGE(w.owed, "every second would ask the folder again"); + TEST_ASSERT_TRUE(kgui_wface_is(&w, "snake")); +} + +void test_the_image_naming_the_same_app_changes_nothing(void) +{ + kgui_wface_t w = { 0 }; + + (void)kgui_wface_report(&w, true, "snake"); + (void)kgui_wface_report(&w, true, ""); + w.owed = false; + /* The first chunk is read and names the app the card already wears. */ + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_KEEP, kgui_wface_report(&w, true, "snake")); + TEST_ASSERT_FALSE(w.owed); + /* A second nameless stretch (the install starts again) asks once more. */ + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_AGAIN, kgui_wface_report(&w, true, "")); + TEST_ASSERT_TRUE(w.owed); +} + +void test_the_image_naming_another_app_takes_its_face(void) +{ + kgui_wface_t w = { 0 }; + + (void)kgui_wface_report(&w, true, "ladder"); /* what the fetch was for */ + (void)kgui_wface_report(&w, true, ""); + w.owed = false; + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_TAKE, kgui_wface_report(&w, true, "snake")); + TEST_ASSERT_TRUE(kgui_wface_is(&w, "snake")); + TEST_ASSERT_TRUE(w.owed); +} + +void test_a_write_that_is_no_app_drops_a_face_kept_through_a_nameless_stretch(void) +{ + kgui_wface_t w = { 0 }; + + (void)kgui_wface_report(&w, true, "snake"); + (void)kgui_wface_report(&w, true, ""); + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_DROP, kgui_wface_report(&w, false, "")); TEST_ASSERT_FALSE(kgui_wface_has(&w)); + TEST_ASSERT_FALSE(w.owed); + /* And the next card starts clean: a nameless report asks nothing. */ + TEST_ASSERT_EQUAL_INT(KGUI_WFACE_KEEP, kgui_wface_report(&w, true, "")); + TEST_ASSERT_FALSE(w.owed); } void test_the_end_of_a_write_forgets_the_face(void) @@ -119,7 +179,11 @@ int main(void) RUN_TEST(test_a_report_that_names_nobody_keeps_a_card_with_no_face); RUN_TEST(test_another_app_takes_the_face_over); RUN_TEST(test_a_write_that_is_no_app_drops_the_face); - RUN_TEST(test_an_app_report_with_no_name_drops_a_face_of_another_write); + RUN_TEST(test_an_app_report_with_no_name_keeps_the_face_and_asks_its_folder_again); + RUN_TEST(test_a_nameless_stretch_asks_the_folder_once); + RUN_TEST(test_the_image_naming_the_same_app_changes_nothing); + RUN_TEST(test_the_image_naming_another_app_takes_its_face); + RUN_TEST(test_a_write_that_is_no_app_drops_a_face_kept_through_a_nameless_stretch); RUN_TEST(test_the_end_of_a_write_forgets_the_face); RUN_TEST(test_an_answer_is_the_cards_only_by_its_project); RUN_TEST(test_a_name_longer_than_a_project_is_cut_not_overrun);