fix: align NomadNet cache response policy

This commit is contained in:
torlando-agent[bot]
2026-08-17 14:50:00 +00:00
parent 2f0f7900ca
commit 5b76abfdbe
7 changed files with 77 additions and 13 deletions
+19
View File
@@ -124,6 +124,7 @@ NomadNetScreen::NomadNetScreen() {
set_status("Enter a NomadNet address");show_start();hide();
}
NomadNetScreen::~NomadNetScreen(){
cancel_status_timer();
finish_field_edit(false);
if(_field_editor)lv_textarea_set_text(_field_editor,"");
if(_screen)lv_obj_del(_screen);
@@ -382,11 +383,29 @@ void NomadNetScreen::set_address_editing(bool editing){
else{lv_group_add_obj(group,_edit_button);lv_group_focus_obj(_edit_button);}
}
}
void NomadNetScreen::cancel_status_timer(){
if(!_status_timer)return;
lv_timer_del(_status_timer);
_status_timer=nullptr;
}
void NomadNetScreen::status_timer_cb(lv_timer_t* timer){
auto* screen=static_cast<NomadNetScreen*>(timer->user_data);
if(!screen||screen->_status_timer!=timer)return;
screen->_status_timer=nullptr;
screen->apply_browser_layout(false);
}
void NomadNetScreen::set_status(const char* value){
cancel_status_timer();
const char* status=value?value:"";
lv_label_set_text(_status,status);
const bool loaded_ack=_page_loaded&&std::strncmp(status,"Page loaded",11)==0;
const bool cached_ack=_page_loaded&&std::strncmp(status,"Cached page",11)==0;
apply_browser_layout(!loaded_ack);
if(cached_ack){
_status_timer=lv_timer_create(status_timer_cb,1500,this);
if(_status_timer)lv_timer_set_repeat_count(_status_timer,1);
else apply_browser_layout(false);
}
}
bool NomadNetScreen::set_page(const NomadNet::Document& document) {
// Prepare every heap-backed model before touching the published page. The
+3
View File
@@ -124,6 +124,7 @@ private:
lv_obj_t* _reload_button=nullptr; lv_obj_t* _save_button=nullptr; lv_obj_t* _identify_button=nullptr; lv_obj_t* _address_row=nullptr; lv_obj_t* _address=nullptr;
lv_obj_t* _go_button=nullptr; lv_obj_t* _address_summary=nullptr; lv_obj_t* _edit_button=nullptr;
lv_obj_t* _status=nullptr; lv_obj_t* _content=nullptr; lv_obj_t* _field_editor=nullptr;
lv_timer_t* _status_timer=nullptr;
lv_obj_t* _directory=nullptr;
NomadNet::CompactPage _page;
NomadNet::FormState _form_state;
@@ -159,6 +160,8 @@ private:
Callback _back,_home; OpenCallback _reload,_open; LinkCallback _link;
SubmitCallback _submit; SaveCallback _save; IdentifyCallback _identify;
void set_address_editing(bool editing);
static void status_timer_cb(lv_timer_t* timer);
void cancel_status_timer();
void apply_browser_layout(bool show_status);
void render_directory(View view);
void show_browser(bool editing);
+2 -6
View File
@@ -2691,10 +2691,6 @@ void UIManager::nomad_update() {
nomad_stop_transport();
break;
}
const bool has_password = std::any_of(document.fields.begin(), document.fields.end(),
[](const NomadNet::FormField& field) {
return field.type == NomadNet::FormFieldType::PASSWORD;
});
const bool ordinary_nil =
_nomad_request_data_class == NomadNet::RequestDataClass::NIL;
const auto directive = NomadNet::parse_cache_directive(bytes.data(), bytes.size());
@@ -2704,7 +2700,7 @@ void UIManager::nomad_update() {
const NomadNet::CacheKey cache_key{
_nomad_url.destination_hex, _nomad_url.path,
_nomad_request_data_class};
if (ordinary_nil && valid_document && !has_password && cache_now &&
if (ordinary_nil && valid_document && cache_now &&
directive.valid && directive.ttl) {
_nomad_cache_pending_key = cache_key;
_nomad_cache_pending_body = _nomad_response.take();
@@ -2713,7 +2709,7 @@ void UIManager::nomad_update() {
_nomad_cache_pending_generation = event.generation;
} else {
_nomad_response.release();
if (ordinary_nil && (!directive.valid || directive.ttl == 0 || has_password))
if (ordinary_nil && (!directive.valid || directive.ttl == 0))
{ _nomad_cache_pending_key = cache_key;
_nomad_cache_pending_generation = event.generation;
_nomad_cache_pending_invalidate = true; }
@@ -34,5 +34,6 @@ target_include_directories(nomadnet_lvgl_acceptance PRIVATE
target_compile_definitions(nomadnet_lvgl_acceptance PRIVATE ARDUINO=1)
target_compile_options(nomadnet_lvgl_acceptance PRIVATE -Wall -Wextra
-fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(nomadnet_lvgl_acceptance PRIVATE -fsanitize=address,undefined)
target_link_options(nomadnet_lvgl_acceptance PRIVATE -fsanitize=address,undefined
-Wl,--wrap=lv_timer_create)
target_link_libraries(nomadnet_lvgl_acceptance PRIVATE lvgl)
@@ -23,6 +23,17 @@ lv_group_t* group = nullptr;
lv_disp_t* display = nullptr;
lv_indev_t* keyboard = nullptr;
std::vector<lv_color_t> framebuffer(320 * 240);
bool fail_next_timer_create = false;
extern "C" lv_timer_t* __real_lv_timer_create(lv_timer_cb_t, uint32_t, void*);
extern "C" lv_timer_t* __wrap_lv_timer_create(lv_timer_cb_t callback, uint32_t period,
void* user_data) {
if (fail_next_timer_create) {
fail_next_timer_create = false;
return nullptr;
}
return __real_lv_timer_create(callback, period, user_data);
}
struct KeyFeed { uint32_t key = 0; uint8_t phase = 0; } key_feed;
@@ -187,7 +198,8 @@ int main() {
bool focus_events = false, edge_scroll = false;
bool ready = false, cancel = false, enter = false, escape = false, focus_restore = false;
bool table_pixels = false, form_pixels = false, focus_pixels = false, glyph_pixels = false;
bool background_pixels = false, teardown = false;
bool background_pixels = false, teardown = false, cached_status_transient = false;
bool cached_status_oom_collapses = false;
int delete_events = 0;
UI::LXMF::NomadNet::DocumentParser parser;
@@ -200,6 +212,24 @@ int main() {
"---\n`[Next`:/page/next.mu]\n`[Submit`:/page/form.mu`username]");
assert(screen.set_page(fit_doc));
lv_obj_update_layout(screen._screen);
screen.set_status("Cached page; current reachability not checked");
lv_obj_update_layout(screen._screen);
const int16_t content_with_cached_notice = lv_obj_get_height(screen._content);
const bool cached_notice_visible =
!lv_obj_has_flag(screen._status, LV_OBJ_FLAG_HIDDEN);
for (int i = 0; i < 12; ++i) pump();
lv_obj_update_layout(screen._screen);
cached_status_transient = cached_notice_visible &&
lv_obj_has_flag(screen._status, LV_OBJ_FLAG_HIDDEN) &&
lv_obj_get_height(screen._content) > content_with_cached_notice;
screen.set_status("Checking page cache...");
fail_next_timer_create = true;
screen.set_status("Cached page; current reachability not checked");
lv_obj_update_layout(screen._screen);
cached_status_oom_collapses = !fail_next_timer_create &&
screen._status_timer == nullptr &&
lv_obj_has_flag(screen._status, LV_OBJ_FLAG_HIDDEN) &&
lv_obj_get_height(screen._content) > content_with_cached_notice;
const auto fit = screen._table_layout;
fit_tier = fit.valid && fit.tier == UI::LXMF::NomadNet::TableLayoutTier::FIT;
fit_columns = fit.columns == 2 && fit.cards == 0 && fit.x >= 0 && fit.width > 0 &&
@@ -338,15 +368,17 @@ int main() {
"LVGL ACCEPT 320x240 fit_tier=%d fit_columns=%d reflow_tier=%d reflow_cards=%d "
"stacked_tier=%d stacked_cards=%d stacked_pixels=%d stacked_scroll=%d stacked_objects=%d "
"focus_events=%d edge_scroll=%d ready=%d cancel=%d enter=%d escape=%d focus_restore=%d "
"teardown=%d stale_group=%d background_pixels=%d table_pixels=%d form_pixels=%d "
"teardown=%d cached_status_transient=%d cached_status_oom_collapses=%d stale_group=%d background_pixels=%d table_pixels=%d form_pixels=%d "
"focus_pixels=%d glyph_pixels=%d exact_fonts=1 objects=%u\n",
fit_tier, fit_columns, reflow_tier, reflow_cards, stacked_tier, stacked_cards,
stacked_pixels, stacked_scroll, stacked_objects, focus_events, edge_scroll,
ready, cancel, enter, escape, focus_restore, teardown, 0, background_pixels,
ready, cancel, enter, escape, focus_restore, teardown, cached_status_transient,
cached_status_oom_collapses, 0, background_pixels,
table_pixels, form_pixels, focus_pixels, glyph_pixels, remaining);
return fit_tier && fit_columns && reflow_tier && reflow_cards && stacked_tier &&
stacked_cards && stacked_pixels && stacked_scroll && stacked_objects &&
focus_events && edge_scroll &&
ready && cancel && enter && escape && focus_restore && teardown && background_pixels &&
ready && cancel && enter && escape && focus_restore && teardown && cached_status_transient &&
cached_status_oom_collapses && background_pixels &&
table_pixels && form_pixels && focus_pixels && glyph_pixels && remaining == 0 ? 0 : 1;
}
+14 -1
View File
@@ -1057,13 +1057,26 @@ def test_nomadnet_same_destination_navigation_reuses_active_link():
"nomad_finish_request_keep_link();"
)
application_failure = response[response.index("if (!page_applied)"):
response.index("const bool has_password")]
response.index("const bool ordinary_nil")]
assert "nomad_stop_transport();" in application_failure
retained = response[response.index("_nomad_link.status() == Type::Link::ACTIVE"):]
assert "_nomad_destination_hash.toHex() == _nomad_url.destination_hex" in retained
assert "nomad_stop_transport();" in retained
def test_nomadnet_cache_persists_only_unsubmitted_server_response_bodies():
manager_cpp = (INCLUDE / "UIManager.cpp").read_text()
response = manager_cpp[manager_cpp.index("case NomadNet::AsyncMailbox::Kind::RESPONSE:"):
manager_cpp.index("case NomadNet::AsyncMailbox::Kind::NONE:")]
# A password descriptor in server markup does not contain mutable editor state.
# Cache admission is based on the request-data class, not field descriptors.
assert "has_password" not in response
assert "_nomad_request_data_class == NomadNet::RequestDataClass::NIL" in response
assert "ordinary_nil && valid_document && cache_now" in response
assert "_nomad_cache_pending_body = _nomad_response.take();" in response
def test_nomadnet_owner_routes_back_reload_and_table_observation_is_private():
screen_h = (INCLUDE / "NomadNetScreen.h").read_text()
manager_h = (INCLUDE / "UIManager.h").read_text()
@@ -41,7 +41,7 @@ def test_actual_nomadnet_screen_320x240_acceptance(tmp_path):
assert "stacked_tier=1 stacked_cards=1 stacked_pixels=1 stacked_scroll=1 stacked_objects=1" in output
assert "focus_events=1 edge_scroll=1" in output
assert "ready=1 cancel=1 enter=1 escape=1 focus_restore=1" in output
assert "teardown=1 stale_group=0" in output
assert "teardown=1 cached_status_transient=1 cached_status_oom_collapses=1 stale_group=0" in output
assert "background_pixels=1 table_pixels=1 form_pixels=1 focus_pixels=1 glyph_pixels=1" in output
assert "exact_fonts=1" in output
assert output.endswith("objects=0")