What changed, and why it matters
This commit is a routine UI enhancement for a Bitcoin hardware wallet project. It renames an internal method, adds a helper that finds the most recently modified source file, and displays that timestamp on the version/about screen. There is no security-relevant change.
No security action required. Review as normal UI/feature code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch renames Controller.get_display_version() to Controller.get_version(), improves its detection of the .git/HEAD path depending on whether the code runs from src/ or the project root, and adds Controller.get_last_src_edit() which walks src/ and returns the latest mtime of any .py file. The VersionScreen dataclass gains a last_edit field and renders it below the version string. The screenshot generator now includes VersionView. No input handling, cryptography, permissions, or network code is modified.
Changed components
src/seedsigner/controller.pysrc/seedsigner/gui/screens/settings_screens.pysrc/seedsigner/views/screensaver.pysrc/seedsigner/views/settings_views.pytests/screenshot_generator/generator.pyInspect captured patch +54 / −8
diff --git a/src/seedsigner/controller.py b/src/seedsigner/controller.py
index a151d3f..6284a46 100644
--- a/src/seedsigner/controller.py
+++ b/src/seedsigner/controller.py
@@ -1,3 +1,4 @@
+from datetime import datetime
import logging
import os
import time
@@ -479,16 +480,23 @@ class Controller(Singleton):
return Destination(UnhandledExceptionView, view_args={"error": error}, clear_history=True)
- def get_display_version(self) -> str:
+ def get_version(self) -> str:
"""
- Returns a user-friendly version string for display in the UI.
+ Returns either the hard-coded Controller.VERSION or a dynamically detected
+ version string for display in the UI. In local dev will return either the
+ current git branch name or commit hash.
"""
name = f"v{VERSION}"
if Settings.HOSTNAME == Settings.SEEDSIGNER_OS:
return name
-
+
# We're in local dev; pull info from .git/HEAD if we can
- git_HEAD_path = os.path.join(os.getcwd(), "..", ".git", "HEAD")
+ git_HEAD_dir = os.getcwd()
+
+ # Main app runs from src/ dir, tests and screenshot generator from project root
+ if "src" in git_HEAD_dir:
+ git_HEAD_dir = os.path.join(git_HEAD_dir, "..")
+ git_HEAD_path = os.path.join(git_HEAD_dir, ".git", "HEAD")
if os.path.exists(git_HEAD_path):
with open(git_HEAD_path, "r") as f:
@@ -501,3 +509,26 @@ class Controller(Singleton):
name = git_ref[:7]
return name
+
+
+ def get_last_src_edit(self) -> datetime:
+ """
+ Recursively scan the src/ directory for the most recent python file edit time and
+ return it.
+ """
+ src_path = os.getcwd()
+ print(f"{src_path=}")
+ if "src" not in src_path:
+ # Screenshot generator runs from the project root
+ src_path = os.path.join(src_path, "src")
+
+ print(f"{src_path=}")
+ latest_mtime = 0
+ for dirpath, dirnames, filenames in os.walk(src_path):
+ for filename in filenames:
+ if filename.endswith(".py"):
+ filepath = os.path.join(dirpath, filename)
+ mtime = os.path.getmtime(filepath)
+ if mtime > latest_mtime:
+ latest_mtime = mtime
+ return datetime.fromtimestamp(latest_mtime)
\ No newline at end of file
diff --git a/src/seedsigner/gui/screens/settings_screens.py b/src/seedsigner/gui/screens/settings_screens.py
index 9d26cc0..14e27d2 100644
--- a/src/seedsigner/gui/screens/settings_screens.py
+++ b/src/seedsigner/gui/screens/settings_screens.py
@@ -1,3 +1,4 @@
+import datetime
import time
from dataclasses import dataclass
@@ -313,6 +314,7 @@ class DonateScreen(BaseTopNavScreen):
@dataclass
class VersionScreen(BaseTopNavScreen):
version: str = None
+ last_edit: datetime = None
def __post_init__(self):
self.title = _("Version")
@@ -323,6 +325,7 @@ class VersionScreen(BaseTopNavScreen):
font = Fonts.get_font(font_name, font_size)
(left, char_height, char_width, bottom) = font.getbbox("X", anchor="ls")
+ num_version_lines = 1
if len(self.version) * char_width > self.canvas_width - 2*GUIConstants.EDGE_PADDING:
max_chars_width = int((self.canvas_width - 2*GUIConstants.EDGE_PADDING) / char_width)
# Add as many line breaks as needed for the version string to fit
@@ -333,10 +336,14 @@ class VersionScreen(BaseTopNavScreen):
else:
wrapped_version.append(self.version[i:])
self.version = "\n".join(wrapped_version)
+ num_version_lines = len(wrapped_version)
# Center the version vertically
char_height *= -1 # due to the "ls" (baseline) anchor, height is negative
- screen_y = int((self.canvas_height - self.top_nav.height) / 2) + self.top_nav.height - char_height*len(wrapped_version)
+
+ # Note: we roughly account for the last edit display's vertical space by adding to
+ # num_version_lines.
+ screen_y = int((self.canvas_height - self.top_nav.height) / 2) + self.top_nav.height - char_height*(num_version_lines + 1)
self.components.append(TextArea(
text=self.version,
@@ -346,6 +353,13 @@ class VersionScreen(BaseTopNavScreen):
screen_y=screen_y,
))
+ last_edit_str = self.last_edit.strftime("%Y-%m-%d\n%H:%M:%S UTC")
+ self.components.append(TextArea(
+ text=last_edit_str,
+ is_text_centered=True,
+ screen_y=self.components[-1].screen_y + self.components[-1].height + 2*GUIConstants.COMPONENT_PADDING,
+ ))
+
@dataclass
diff --git a/src/seedsigner/views/screensaver.py b/src/seedsigner/views/screensaver.py
index c8c51c7..25be5cc 100644
--- a/src/seedsigner/views/screensaver.py
+++ b/src/seedsigner/views/screensaver.py
@@ -97,7 +97,7 @@ class OpeningSplashScreen(LogoScreen):
# Display version num below SeedSigner logo
font = Fonts.get_font(GUIConstants.get_body_font_name(), GUIConstants.get_top_nav_title_font_size())
- version = controller.get_display_version()
+ version = controller.get_version()
# The logo png is 240x240, but the actual logo is 70px tall, vertically centered
logo_height = 70
diff --git a/src/seedsigner/views/settings_views.py b/src/seedsigner/views/settings_views.py
index df03e43..9bc2ff6 100644
--- a/src/seedsigner/views/settings_views.py
+++ b/src/seedsigner/views/settings_views.py
@@ -327,11 +327,11 @@ class VersionView(View):
def run(self):
from seedsigner.controller import Controller
controller = Controller.get_instance()
- version = controller.get_display_version()
self.run_screen(
settings_screens.VersionScreen,
- version=version
+ version=controller.get_version(),
+ last_edit=controller.get_last_src_edit(),
)
return Destination(SettingsMenuView)
\ No newline at end of file
diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py
index e45373f..3d32678 100644
--- a/tests/screenshot_generator/generator.py
+++ b/tests/screenshot_generator/generator.py
@@ -425,6 +425,7 @@ def generate_screenshots(locale):
"Settings Views": settings_views_list + [
ScreenshotConfig(settings_views.IOTestView),
ScreenshotConfig(settings_views.DonateView),
+ ScreenshotConfig(settings_views.VersionView),
ScreenshotConfig(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_persistent), screenshot_name="SettingsIngestSettingsQRView_persistent"),
ScreenshotConfig(settings_views.SettingsIngestSettingsQRView, dict(data=settingsqr_data_not_persistent), screenshot_name="SettingsIngestSettingsQRView_not_persistent"),
],
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.