feat(web): show which plugins use each font; warn before deleting one (#619)

* feat(web): show which plugins use each font, warn before deleting one

The Fonts tab lists font files from the web process's own scan, and the
plugins that register fonts run in the display process, so the tab had no
way to say whether a font was in use before deleting it.

The display service now publishes {catalog key: [plugin ids]} to the
shared cache (font_usage_snapshot, src/font_usage.py), built from the
loaded plugins' FontManager.register_manager_font() registrations. A
daemon thread checks every 10 s and writes only when the usage changed
(plus a daily refresh so cache cleanup cannot expire it); it never raises.
Families, aliases (press_start, four_by_six, ...) and paths are resolved
through FontManager's catalog to the file stem the Fonts tab keys rows by;
fonts outside assets/fonts are left out. Unloading a plugin drops its
registrations (new FontManager.forget_manager_fonts).

GET /api/v3/fonts/catalog merges used_by into each row per request (the
5-minute scan cache is copied, never edited): a list of plugin ids, or
null when the display service has not reported. The tab shows a Used by
column ("unknown" / "-" / ids, rendered as text) and deleting an in-use
font names the plugins in the confirmation, from a fresh read. The server
still refuses only system fonts. Catalog fetches bypass the browser's
5-second API cache, which otherwise served the pre-delete list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix: call forget_manager_fonts through a hasattr check pylint can follow

getattr(..., None) then callable() is fine at runtime, but pylint's E1102
("not callable") can't see through it, and Codacy fails the check on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-23 16:50:42 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 9a1f94f793
commit f3894916a9
10 changed files with 995 additions and 22 deletions
+7
View File
@@ -406,6 +406,11 @@ class DisplayController:
logger.exception("Plugin system initialization failed")
self.plugin_manager = None
# The web UI's Fonts tab ("Used by") reads what this publishes.
from src.font_usage import start_font_usage_publisher
self._font_usage_publisher = start_font_usage_publisher(
self.cache_manager, self.font_manager, self.plugin_manager)
# Display rotation state
self.current_mode_index = 0
self.current_display_mode = None
@@ -3341,6 +3346,8 @@ class DisplayController:
self.config_service.shutdown()
except Exception as e:
logger.warning("Error shutting down config service: %s", e)
if getattr(self, '_font_usage_publisher', None) is not None:
self._font_usage_publisher.stop()
logger.info("Cleaning up display controller...")
if hasattr(self, 'display_manager'):
self.display_manager.cleanup()