feat: configurable puppy emoji (puppymoji) 🦴#300
Closed
mattnico wants to merge 9 commits intompfaffenberger:mainfrom
Closed
feat: configurable puppy emoji (puppymoji) 🦴#300mattnico wants to merge 9 commits intompfaffenberger:mainfrom
mattnico wants to merge 9 commits intompfaffenberger:mainfrom
Conversation
termflow's wrap_ansi works character-by-character and has no concept of
word boundaries, causing words to be split mid-character whenever a line
reaches the column limit.
Add _word_boundary_wrap_ansi() in markdown_patches.py that:
- Tokenises input into ANSI codes, space-runs and word-runs
- Greedily fills each output line with whole words
- Re-emits active ANSI SGR codes at the start of every new line
- Falls back to hard (character-boundary) breaking only for words
that are individually wider than the configured width
patch_termflow_word_wrap() monkey-patches this function over
termflow.ansi.utils.wrap_ansi (and the public re-export in
termflow.ansi) at startup via messaging/__init__.py, so every
TermflowRenderer instance benefits automatically.
termflow/render/text.py uses which creates a module-local binding. Monkey-patching only termflow.ansi.utils and termflow.ansi left this local reference pointing at the original mid-word-chopping implementation. Also patch termflow.render.text.wrap_ansi directly so all call sites pick up the word-boundary-aware replacement.
- New schedule_type='daily_at' with schedule_value='HH:MM' or 'HH:MM,HH:MM,...'
- Daemon fires task if any target time has passed today and last_run predates it
- Restart-safe: daemon downtime at fire-time still fires on next wakeup
- Wizard: new 'Daily at specific time(s)...' menu option with HH:MM validation
- Wizard: friendly summary display ('daily at 09:00,17:00')
- cron remains as a stub with warning (would need croniter)
Covers parse_daily_at_times() and the daily_at branch of should_run_task(): - Single/multiple/whitespace-padded time parsing - Edge cases: midnight, 23:59, empty string, invalid formats - Invalid entries skipped with warning, valid entries still used - Never-run task fires after target, not before (boundary inclusive) - Does not re-fire after running today - Fires when last_run was yesterday (restart-safe) - Fires when daemon missed the window and caught up later - last_run before target but same day still triggers - Multi-time: first due + second future, first ran + second future/due - Disabled tasks never fire - All-invalid schedule_value returns False with warning - Midnight target edge case
- Fix test_wizard_code_puppy_first: 'Daily' was removed from schedule_map when daily_at replaced it; switched to 'Every hour' - Add test_daily_at_single_time: single HH:MM → schedule_type=daily_at - Add test_daily_at_multiple_times: comma list preserved verbatim - Add test_daily_at_cancel_time_input: None from TextInputMenu → None - Add test_daily_at_all_invalid_times: no valid times → None - Add test_daily_at_strips_invalid_times: bad entries dropped, valid kept - Add test_daily_at_summary_display: confirms 'daily at HH:MM' in stdout and raw 'daily_at' type string does not leak into summary
Replace the hard-coded 🐶 at every user-facing runtime surface with a
new puppy_emoji config (defaults to 🐶, mirrors how puppy_name works).
- config.get_puppy_emoji() / set_puppy_emoji() with validation
(non-empty, max 16 chars to allow ZWJ sequences like 🐕\u200d🦺)
- puppy_emoji exposed via get_config_keys() so /set & tab-completion work
- Replaced hard-coded 🐶 in:
- interactive prompt prefix (prompt_toolkit_completion)
- startup banner & 'Continuing in Interactive Mode' (cli_runner)
- /show status header (config_commands)
- onboarding wizard slides
- agent self-intro ('what is code puppy?')
- API landing page (/) & terminal page (/terminal)
- API startup/shutdown log lines
- terminal.html stays a static asset; emoji is substituted at request
time so we don't have to add Jinja2 just for one token.
- Intentionally left alone: pack-leader ASCII art, MOTD historical
content, oauth_puppy_html sprites, README/SETUP docs, plugin
examples, error_logging docstring, agent product display name
('Code-Puppy 🐶'). Those are branding/content, not user identity.
Tests: 10 new tests for get/set/validation + updated 2 snapshot tests
in TestGetConfigKeys for the new key. End-to-end verified via
TestClient that '/' and '/terminal' both reflect a custom emoji.
Use it: /set puppy_emoji 🦊
Make SpinnerBase.current_frame render with the live puppy_emoji config on every access, so '/set puppy_emoji 🦴' flips both the prompt prefix AND the 'Betty White is thinking... ( 🦴 )' spinner without a restart. - New _build_spinner_frames(emoji) helper as the single source of truth for the bouncing-puppy frame template (DRY: same shape used by both the frozen FRAMES default and the live current_frame render) - SpinnerBase.FRAMES kept as a class attribute frozen at import time with DEFAULT_PUPPY_EMOJI for backward compatibility — tests and any external code that reads it stay green - current_frame property now calls get_puppy_emoji() per access so the user's chosen emoji shows live; frame index logic untouched - Made two existing spinner tests hermetic by patching get_puppy_emoji to the default; they were silently coupled to the developer's real puppy.cfg (caught when puppy_emoji=🦴 was set in the dev config) - Added 2 new tests: * current_frame uses live puppy_emoji on access (not import-time) * FRAMES class attr stays default for backward compat Note: SpinnerBase.THINKING_MESSAGE / WAITING_MESSAGE / puppy_name still freeze at import (pre-existing behavior — puppy_name changes don't propagate to spinner without restart). Out of scope for puppymoji; worth a separate ticket if anyone ever cares.
Contributor
Author
|
Closing in favor of a clean re-cut from origin/main. The branch was inadvertently based on a local main with 7 unrelated WIP commits (termflow word-wrap work) that polluted the diff and caused unrelated CI failures (ruff F841 on markdown_patches.py + missing export-test update for patch_termflow_word_wrap). Reopening shortly with just the 2 puppymoji commits on top of origin/main. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Make the hard-coded
🐶emoji user-configurable via a newpuppy_emojiconfig key. Mirrors exactly howpuppy_namealready works — defaults to🐶so existing users see no change.Why
Some of us want our pup to be a fox, a wolf, or a bone 🦴. Trivial to ship, zero behavior change for default users, fully reversible.
Surfaces that now respect
puppy_emojiprompt_toolkit_completion.py( 🦴 )spinner_base.pycli_runner.py/showstatus header + newpuppy_emoji:rowconfig_commands.pyonboarding_slides.pyagent_code_puppy.py/)api/app.py/terminal)api/app.py(substituted at request time)api/app.pyIntentionally NOT touched (YAGNI / branding / content)
oauth_puppy_html.pystylized sprites (cannons, crying dogs, themed art)error_logging.py/__init__.pydecorative commentsCode-Puppy 🐶(the agent's brand, not the user's pup)Validation
set_puppy_emoji()rejects empty / whitespace-only / >16 chars /None🐕🦺(4 codepoints) without enabling abuseTests
TestPuppyEmojicovering get/set/validation/default/whitespace/ZWJcurrent_frameTestGetConfigKeyssnapshots for the new key + 2 spinner tests made hermetic — they were silently coupled to the dev's realpuppy.cfg)/and/terminalreflect a custom emojiDiff
11 files, +209 / -36. Each change is small and follows the existing
puppy_namepattern.Pre-existing oddity worth a follow-up (not in scope)
SpinnerBase.THINKING_MESSAGE/WAITING_MESSAGE/puppy_nameare evaluated at class-definition time, so changingpuppy_namemid-session doesn't propagate to the spinner without restart. Same lazy-resolution pattern this PR uses for emoji would fix it. Happy to do as a separate PR if desired.