-
Notifications
You must be signed in to change notification settings - Fork 154
feat: add variety to loading messages with shimmer animation #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nhicks00
wants to merge
12
commits into
mpfaffenberger:main
Choose a base branch
from
nhicks00:feature/248-loading-message-variety-clean
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 10 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
874f9da
feat: add variety to loading messages with shimmer animation
09a1d4e
feat: add register_loading_messages callback hook
dad2260
fix: address CodeRabbit review feedback
4010e89
fix: exclude plugin 'standalone' category from spinner rotation
cda2d80
refactor: Move loading_messages.py to messaging module to reduce root…
423e72e
fix(threading): Add lock around _plugin_categories mutations
98900f1
fix(threading): Lock _plugin_categories read in get_all_messages
a144e0a
fix: address CodeRabbit review on PR #208
32760ba
chore: restore uv.lock from main (no new deps)
c1dfafc
fix: allow 'standalone' as plugin category (not reserved)
f7d71b2
fix: address CodeRabbit round 3 feedback
d8c9aab
fix: address remaining CodeRabbit review feedback
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,351 @@ | ||
| """Loading messages for spinner and status display. | ||
|
|
||
| Provides a shuffled deck of fun/silly loading messages that rotate | ||
| during spinner cycles. Messages are drawn from several categories | ||
| and shuffled so no message repeats until the entire deck is exhausted. | ||
|
|
||
| Plugins can register additional message categories via | ||
| ``register_messages(category, messages)``. | ||
| """ | ||
|
|
||
| import random | ||
| import threading | ||
| from typing import Dict, List | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 🐶 Puppy / Dog Themed | ||
| # --------------------------------------------------------------------------- | ||
| _PUPPY_SPINNER: List[str] = [ | ||
| "sniffing around...", | ||
| "wagging tail...", | ||
| "pawsing for a moment...", | ||
| "chasing tail...", | ||
| "digging up results...", | ||
| "barking at the data...", | ||
| "rolling over...", | ||
| "panting with excitement...", | ||
| "chewing on it...", | ||
| "prancing along...", | ||
| "howling at the code...", | ||
| "snuggling up to the task...", | ||
| "bounding through data...", | ||
| "fetching results...", | ||
| "playing fetch with ideas...", | ||
| "doing a happy dance...", | ||
| "perking up ears...", | ||
| "tilting head curiously...", | ||
| "burying a bone of knowledge...", | ||
| "shaking off the cobwebs...", | ||
| "begging for treats...", | ||
| "chasing the mailman...", | ||
| "guarding the codebase...", | ||
| "herding the functions...", | ||
| "learning new tricks...", | ||
| "napping on the keyboard...", | ||
| "pawing at the screen...", | ||
| "sniffing out bugs...", | ||
| "doing zoomies...", | ||
| "gnawing on a problem...", | ||
| "licking the screen...", | ||
| "being a good boy...", | ||
| "drooling over clean code...", | ||
| "marking territory...", | ||
| "digging a hole in the stack...", | ||
| "catching a frisbee...", | ||
| "whimpering at legacy code...", | ||
| "yipping with excitement...", | ||
| "rolling in the data...", | ||
| "scratching behind ear...", | ||
| "snoozing then coding...", | ||
| "burying the lede...", | ||
| "unleashing the code...", | ||
| ] | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 💻 Dev / Coding | ||
| # --------------------------------------------------------------------------- | ||
| _DEV_SPINNER: List[str] = [ | ||
| "refactoring reality...", | ||
| "compiling thoughts...", | ||
| "parsing the universe...", | ||
| "rebasing on reality...", | ||
| "merging timelines...", | ||
| "deploying neurons...", | ||
| "linting the cosmos...", | ||
| "cherry-picking ideas...", | ||
| "stashing thoughts...", | ||
| "hashing it out...", | ||
| "vibe-coding...", | ||
| ] | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # 🎲 Random Fun / Silly | ||
| # --------------------------------------------------------------------------- | ||
| _FUN_SPINNER: List[str] = [ | ||
| "consulting the oracle...", | ||
| "asking the rubber duck...", | ||
| "reading tea leaves...", | ||
| "shaking the magic 8-ball...", | ||
| "channeling the force...", | ||
| "summoning the kraken...", | ||
| "calibrating the vibe...", | ||
| "vibing...", | ||
| "manifesting...", | ||
| "reticulating splines...", | ||
| "reversing the polarity...", | ||
| "rerouting the dilithium...", | ||
| "consulting the ancient texts...", | ||
| "interrogating the void...", | ||
| "pondering the orb...", | ||
| "adjusting the timeline...", | ||
| "wrangling the chaos...", | ||
| "herding cats...", | ||
| "spinning up hamster wheels...", | ||
| "brewing coffee...", | ||
| "microwaving leftovers...", | ||
| "ordering pizza...", | ||
| "updating the spreadsheet...", | ||
| "googling the answer...", | ||
| "Stack Overflowing...", | ||
| "reading the docs...", | ||
| "RTFM-ing...", | ||
| "blaming the intern...", | ||
| "turning it off and on...", | ||
| "wiggling the cables...", | ||
| "downloading more RAM...", | ||
| "feeding the hamsters...", | ||
| "dusting off the cobwebs...", | ||
| "polishing the pixels...", | ||
| "aligning the chakras...", | ||
| "balancing the universe...", | ||
| "crunching the numbers...", | ||
| "doing the math...", | ||
| "phoning a friend...", | ||
| "asking the audience...", | ||
| "inserting coin...", | ||
| "doing your work for you...", | ||
| ] | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # ⚡ Action Verbs (short & punchy) | ||
| # --------------------------------------------------------------------------- | ||
| _ACTION_SPINNER: List[str] = [ | ||
| "deliberating...", | ||
| "contemplating...", | ||
| "hypothesizing...", | ||
| "brainstorming...", | ||
| "strategizing...", | ||
| "orchestrating...", | ||
| "crafting...", | ||
| "sculpting...", | ||
| "weaving...", | ||
| "assembling...", | ||
| "constructing...", | ||
| "investigating...", | ||
| "researching...", | ||
| "exploring...", | ||
| "discovering...", | ||
| "transforming...", | ||
| "transmuting...", | ||
| "conjuring...", | ||
| "invoking...", | ||
| "materializing...", | ||
| "crystallizing...", | ||
| "distilling...", | ||
| "curating...", | ||
| "polishing...", | ||
| "refining...", | ||
| ] | ||
|
|
||
|
|
||
| # =========================================================================== | ||
| # STANDALONE MESSAGES (no prefix — used only in the status display) | ||
| # =========================================================================== | ||
| _STANDALONE_MESSAGES: List[str] = [ | ||
| # 🐶 Puppy | ||
| "Puppy pondering...", | ||
| "Nose to the ground...", | ||
| "Tail-wagging intensifies...", | ||
| "Sitting. Staying. Coding...", | ||
| "Belly rub loading...", | ||
| "Paws on keyboard...", | ||
| "Puppy eyes activated...", | ||
| # 🎲 Fun | ||
| "Loading loading screen...", | ||
| "It's not a bug...", | ||
| "Works on my machine...", | ||
| "Have you tried unplugging?", | ||
| "Percussive maintenance...", | ||
| "Deleting System32... jk...", | ||
| "50/50 lifeline used...", | ||
| "Plot twist incoming...", | ||
| "Stay tuned...", | ||
| "Warming up the flux capacitor...", | ||
| # 🤓 Nerdy / Pop culture | ||
| "sudo make me a sandwich...", | ||
| "There is no spoon...", | ||
| "Hello, World!...", | ||
| "I'm sorry Dave...", | ||
| "To infinity and beyond...", | ||
| "May the source be with you...", | ||
| "Use the --force, Luke...", | ||
| "Live long and prosper...", | ||
| "Beam me up, Scotty...", | ||
| "Winter is compiling...", | ||
| "One does not simply code...", | ||
| "My precious... data...", | ||
| "I am Groot (processing)...", | ||
| "Avengers, assemble code...", | ||
| "This is the way...", | ||
| "I have spoken...", | ||
| "Bazinga!...", | ||
| "Allons-y!...", | ||
| "Fantastic!...", | ||
| "Geronimo!...", | ||
| "Elementary, my dear...", | ||
| "The cake is a lie...", | ||
| "Do a barrel roll...", | ||
| "All your base are belong...", | ||
| "It's dangerous to go alone...", | ||
| ] | ||
|
|
||
| # =========================================================================== | ||
| # Plugin Registry | ||
| # =========================================================================== | ||
| _plugin_categories: Dict[str, List[str]] = {} | ||
| _plugins_initialized: bool = False | ||
| _plugins_init_lock = threading.Lock() | ||
| _plugin_categories_lock = threading.RLock() | ||
|
|
||
|
|
||
| def _ensure_plugins_loaded() -> None: | ||
| """Fire the register_loading_messages callback once. | ||
|
|
||
| This is called lazily the first time messages are requested, | ||
| giving plugins time to register their callbacks at import. | ||
| Uses double-checked locking to avoid TOCTOU races from | ||
| concurrent spinner threads. | ||
| """ | ||
| global _plugins_initialized | ||
| if _plugins_initialized: | ||
| return | ||
| with _plugins_init_lock: | ||
| if _plugins_initialized: | ||
| return | ||
| _plugins_initialized = True | ||
|
|
||
| from code_puppy.callbacks import on_register_loading_messages | ||
|
|
||
| on_register_loading_messages() | ||
|
|
||
|
|
||
| _RESERVED_CATEGORIES = frozenset({"puppy", "dev", "fun", "action"}) | ||
|
|
||
|
|
||
| def register_messages(category: str, messages: List[str]) -> None: | ||
| """Register additional loading messages from a plugin. | ||
|
|
||
| All plugin-registered messages are included in the **spinner rotation** | ||
| (prefixed with ``"<PuppyName> is "``). If you need display-only | ||
| standalone messages, use the ``"standalone"`` category — those are | ||
| included in ``get_all_messages()`` but excluded from the spinner. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| category: | ||
| A unique plugin-specific category name (e.g. ``"walmart"``). | ||
| Core category names (puppy, dev, fun, action, standalone) are | ||
| reserved and will raise ``ValueError``. If the category | ||
| already exists the new messages are **appended**. | ||
| messages: | ||
| List of message strings. For spinner messages keep them | ||
| lowercase and gerund-style (e.g. ``"rolling back prices..."``). | ||
|
|
||
| Raises | ||
| ------ | ||
| ValueError | ||
| If ``category`` is empty or collides with a reserved core name. | ||
| """ | ||
| if not isinstance(category, str) or not category.strip(): | ||
| raise ValueError("category must be a non-empty string") | ||
| if category in _RESERVED_CATEGORIES: | ||
| raise ValueError( | ||
| f"'{category}' is a reserved core category; " | ||
| "use a plugin-specific category name" | ||
| ) | ||
| with _plugin_categories_lock: | ||
| if category in _plugin_categories: | ||
| _plugin_categories[category].extend(messages) | ||
| else: | ||
| _plugin_categories[category] = list(messages) | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| def unregister_messages(category: str) -> None: | ||
| """Remove a previously registered message category.""" | ||
| with _plugin_categories_lock: | ||
| _plugin_categories.pop(category, None) | ||
|
|
||
|
|
||
| # =========================================================================== | ||
| # Public API | ||
| # =========================================================================== | ||
|
|
||
|
|
||
| def _plugin_snapshot() -> Dict[str, List[str]]: | ||
| """Return a consistent shallow copy of all plugin categories.""" | ||
| with _plugin_categories_lock: | ||
| return {cat: list(msgs) for cat, msgs in _plugin_categories.items()} | ||
|
|
||
|
|
||
| def _all_spinner_messages( | ||
| snapshot: Dict[str, List[str]] | None = None, | ||
| ) -> List[str]: | ||
| """Combine built-in + plugin spinner messages (not standalone).""" | ||
| _ensure_plugins_loaded() | ||
| if snapshot is None: | ||
| snapshot = _plugin_snapshot() | ||
| combined = _PUPPY_SPINNER + _DEV_SPINNER + _FUN_SPINNER + _ACTION_SPINNER | ||
| for cat, msgs in snapshot.items(): | ||
| if cat != "standalone": | ||
| combined = combined + msgs | ||
| return combined | ||
|
|
||
|
|
||
| def get_spinner_messages() -> List[str]: | ||
| """Return a shuffled copy of all spinner messages. | ||
|
|
||
| Each call produces a fresh shuffle so that a spinner can draw | ||
| through the entire deck without repeats. | ||
| """ | ||
| msgs = _all_spinner_messages() | ||
| random.shuffle(msgs) | ||
| return msgs | ||
|
|
||
|
|
||
| def get_all_messages() -> List[str]: | ||
| """Return all messages (spinner + standalone) for status display.""" | ||
| _ensure_plugins_loaded() | ||
| snapshot = _plugin_snapshot() | ||
| plugin_standalone = snapshot.pop("standalone", []) | ||
| return ( | ||
| _all_spinner_messages(snapshot) + list(_STANDALONE_MESSAGES) + plugin_standalone | ||
| ) | ||
|
|
||
|
|
||
| def get_messages_by_category() -> Dict[str, List[str]]: | ||
| """Return messages organized by category (useful for testing).""" | ||
| _ensure_plugins_loaded() | ||
| result = { | ||
| "puppy": list(_PUPPY_SPINNER), | ||
| "dev": list(_DEV_SPINNER), | ||
| "fun": list(_FUN_SPINNER), | ||
| "action": list(_ACTION_SPINNER), | ||
| "standalone": list(_STANDALONE_MESSAGES), | ||
| } | ||
| with _plugin_categories_lock: | ||
| for cat, msgs in _plugin_categories.items(): | ||
| if cat in result: | ||
| result[cat] = result[cat] + list(msgs) | ||
| else: | ||
| result[cat] = list(msgs) | ||
| return result | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.