From 819a108451a8e6785b4397ab2dab48473334a4b9 Mon Sep 17 00:00:00 2001 From: hz Date: Sat, 18 Jul 2026 22:25:08 -0500 Subject: [PATCH] Make note save status transient --- scripts/autoload/notes_service.gd | 5 ++--- scripts/ui/notes_editor.gd | 24 ++++++++++++++++++++++-- tests/notes_ui_test.gd | 9 +++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/scripts/autoload/notes_service.gd b/scripts/autoload/notes_service.gd index d091064..d7735c1 100644 --- a/scripts/autoload/notes_service.gd +++ b/scripts/autoload/notes_service.gd @@ -10,6 +10,7 @@ const MAX_CONTENT_LENGTH := 50000 signal content_changed(content: String) signal state_changed(state: String) signal conflict_detected(server_content: String) +signal save_succeeded var api_url := DEFAULT_API_URL var content := "" @@ -113,10 +114,7 @@ func resolve_conflict_use_synced() -> void: func status_text() -> String: match state: "loading": return "Loading..." - "dirty": return "Unsaved changes" "saving": return "Saving..." - "saved": return "Saved" - "offline": return "Saved locally - waiting for connection" "conflict": return "Changes found on another device" _: return "" @@ -271,6 +269,7 @@ func _apply_remote_save(server: Dictionary) -> void: if content == sent_content: dirty = false _set_state("saved") + save_succeeded.emit() else: dirty = true _set_state("dirty") diff --git a/scripts/ui/notes_editor.gd b/scripts/ui/notes_editor.gd index 25826c9..a836481 100644 --- a/scripts/ui/notes_editor.gd +++ b/scripts/ui/notes_editor.gd @@ -9,6 +9,7 @@ var conflict_panel: PanelContainer var applying_content := false var notes_service: Node var theme_service: Node +var saved_status_timer: Timer func _ready() -> void: @@ -28,6 +29,11 @@ func _ready() -> void: count_label = Label.new() Brand.apply_font(count_label) metadata_row.add_child(count_label) + saved_status_timer = Timer.new() + saved_status_timer.one_shot = true + saved_status_timer.wait_time = 2.0 + saved_status_timer.timeout.connect(_clear_saved_status) + add_child(saved_status_timer) editor = TextEdit.new() editor.placeholder_text = "Write notes..." @@ -65,6 +71,7 @@ func _ready() -> void: notes_service.content_changed.connect(_on_service_content_changed) notes_service.state_changed.connect(_on_service_state_changed) + notes_service.save_succeeded.connect(_on_save_succeeded) theme_service.theme_changed.connect(_on_theme_changed) _refresh_from_service() _apply_theme() @@ -104,6 +111,8 @@ func _on_service_content_changed(value: String) -> void: func _on_service_state_changed(_new_state: String) -> void: _refresh_metadata() + if notes_service.state != "saved": + saved_status_timer.stop() func _refresh_from_service() -> void: @@ -111,7 +120,8 @@ func _refresh_from_service() -> void: func _refresh_metadata() -> void: - status_label.text = notes_service.status_text() + if status_label.text != "Saved" or saved_status_timer.time_left <= 0.0: + status_label.text = notes_service.status_text() count_label.text = "%s / %s" % [ _format_count(editor.text.length()), _format_count(notes_service.MAX_CONTENT_LENGTH) @@ -119,7 +129,17 @@ func _refresh_metadata() -> void: conflict_panel.visible = notes_service.conflict_active status_label.add_theme_color_override( "font_color", - Brand.AMBER if notes_service.state in ["dirty", "offline", "conflict"] else Brand.muted_color(theme_service.is_dark_mode())) + Brand.AMBER if notes_service.state == "conflict" else Brand.muted_color(theme_service.is_dark_mode())) + + +func _on_save_succeeded() -> void: + status_label.text = "Saved" + saved_status_timer.start() + + +func _clear_saved_status() -> void: + if status_label.text == "Saved": + status_label.text = "" func _format_count(value: int) -> String: diff --git a/tests/notes_ui_test.gd b/tests/notes_ui_test.gd index 7b6349f..6e31ff7 100644 --- a/tests/notes_ui_test.gd +++ b/tests/notes_ui_test.gd @@ -18,6 +18,15 @@ func _run() -> void: _assert(editor.editor != null, "notes editor should create a text editor") _assert(editor.editor.wrap_mode == TextEdit.LINE_WRAPPING_BOUNDARY, "notes should wrap plain text") _assert(NavigationRoutes.key_for_screen("notes") == "notes", "notes should have a navigation route") + var notes_service = root.get_node("NotesService") + var prior_state: String = notes_service.state + notes_service.state = "offline" + _assert(notes_service.status_text().is_empty(), "offline note retries should not leave a persistent status message") + editor._on_save_succeeded() + _assert(editor.status_label.text == "Saved", "successful remote saves should show a Saved confirmation") + editor._clear_saved_status() + _assert(editor.status_label.text.is_empty(), "the Saved confirmation should clear after its display window") + notes_service.state = prior_state var dialog := NotesDialog.new() root.add_child(dialog)