This is the single starting point for any human or AI working on KeyQuest.
- Last updated: 2026-05-22 (1.20.0 release-readiness sweep — version-facing docs/tests and updater evidence references synced)
- Version: see
modules/version.py(single source of truth) - Platform: Windows only
- Accessibility: See user accessibility docs in
docs/user/. - Git status: previous notes about GitHub push being blocked by hostname-resolution errors are stale. A March 27, 2026 verification from this machine showed
git status --short --branchreporting## main...origin/mainonce missing Windows environment variables were restored in the embedded Codex shell.
- Open
docs/dev/HANDOFF.mdanddocs/dev/CHANGELOG.mdtop entry. - Run baseline checks before editing:
py -3.11 -m pytest -qpowershell -ExecutionPolicy Bypass -File tools/run_quality_checks.ps1
- If changing user-visible behavior, update:
README.html(and pointerREADME.mdonly if needed)docs/dev/CHANGELOG.md— technical detail (file names, functions, implementation notes)
- For release work:
- Update
docs/user/WHATS_NEW.md— plain English only, no code/file names - Prefer
powershell -ExecutionPolicy Bypass -File tools/ship_updates.ps1 - Or bump
modules/version.pymanually and runpowershell -ExecutionPolicy Bypass -File tools/release.ps1 - Both scripts skip local builds — CI builds and publishes all artifacts automatically
- Monitor the release at: https://github.com/WebFriendlyHelp/KeyQuest/actions
- Update
- Before handoff:
- Update this handoff file snapshot + recent changes
- Commit and push to
main - If releasing, verify GitHub release page and asset links
- Read this file.
- Read the top entry in
docs/dev/CHANGELOG.md. - Start implementation from
modules/keyquest_app.pyand the relevantmodules/*orgames/*file.
keyquest.pyw: thin entrypoint (runsmodules/keyquest_app.py).modules/keyquest_app.py: main application event loop and screen wiring.modules/: state, lessons, audio, dialogs, menu, shop/pets, etc.games/: game implementations (base_game.py,letter_fall.py,word_typing.py).ui/: rendering helpers.Sentences/: sentence/topic text pools..codex/config.toml: tracked Codex project config for repo-root detection and handoff fallback whenAGENTS.mdis absent.docs/: user and developer docs.tools/build/: batch build scripts and PyInstaller spec.tools/quality/: quality scripts (contrast audit).
- Install deps:
pip install -r requirements.txt - Run app:
py -3.11 keyquest.pyw - Run tests:
py -3.11 -m pytest -q - Local quality checks:
powershell -ExecutionPolicy Bypass -File tools/run_quality_checks.ps1 - Build exe:
tools/build/build_exe.bat - Build installer:
tools/build/build_installer.bat(requires Inno Setup 6) - Full release:
powershell -ExecutionPolicy Bypass -File tools/ship_updates.ps1 - Build source package:
tools/build/create_source_package.bat - Single build entrypoint:
powershell -ExecutionPolicy Bypass -File tools/build.ps1 -Target all -Clean(exe + source)powershell -ExecutionPolicy Bypass -File tools/build.ps1 -Target installer(installer only)
- EXE packaging docs policy: include
README.md,README.html, and user-facing docs underdist/KeyQuest/docs/. - Release policy:
docs/dev/RELEASE_POLICY.md - Windows source-launch safeguard:
keyquest.pywnow relaunches itself with Python 3.11 if file association starts it with a different Python install. - Python baseline policy: keep source, workflows, linting, and packaging aligned to Python 3.11 for consistency and TTS compatibility.
- Codex environment note: this machine was updated to Codex
0.117.0on 2026-03-27. That upstream release changed plugin/skill loading behavior, so if a future session hitsskipped loading skill, compare the exact wording against the post-0.117.0 behavior before assuming it is a repo issue. - Repo-shared Codex workflow assets now exist:
AGENTS.mddocs/dev/CODEX_GITHUB_WORKFLOW.mdtools/codex_exec_diagnostics.ps1
- Global MCP setup on this machine:
context7uses@upstash/context7-mcp@latestgithubuses GitHub's remote MCP server athttps://api.githubcopilot.com/mcp/- GitHub MCP expects a
GITHUB_PAT_TOKENenvironment variable; do not write the token into tracked repo files.
- GitHub-side maintainer roles such as
pr-reviewandissue-trackerare guidance only unless those skills/plugins are installed in the current Codex session. - Recommended Codex permission-rule allowlist for this repo:
- safe to approve persistently:
py -3.11 -m pytest -q,powershell -ExecutionPolicy Bypass -File tools/run_quality_checks.ps1,powershell -ExecutionPolicy Bypass -File tools/codex_exec_diagnostics.ps1,powershell -ExecutionPolicy Bypass -File tools/run_local_updater_integration.ps1 - generally safe when prompts appear: read-only inspection commands such as
git status --short,git diff -- ..., andrg ... - keep one-off approval for destructive or publishing commands (
Remove-Item,git reset, build cleanup, tag/release publication)
- safe to approve persistently:
- Recommended parallel-agent pattern for this repo:
- only split tasks that are independent and have disjoint write sets
- strong candidates: code change plus docs sync, updater changes plus harness verification, GitHub triage plus local implementation
- keep immediate blockers in the main session instead of waiting on a delegated result
- avoid parallel edits in the same file set unless the task is explicitly organized that way
- For OpenAI API, Codex-product, or model-selection questions:
- use the
openai-docsskill first - prefer current official OpenAI docs over repo memory
- use
docs/dev/CODEX_GITHUB_WORKFLOW.mdfor stable-vs-alpha guidance, model-selection defaults, and GitHub-agent routing
- use the
- Skill organization rule:
- keep general Windows/Codex workflows in global skills
- keep repo-local skills lean and KeyQuest-specific
- do not document repo-local
.codex/skillsas present unless the folders are tracked in this repo
- Core app + Phases 1-4 features implemented.
- Updater is fully hardened and uses pure
.batlaunchers — no PowerShell dependency. Previous PS1-based launchers (_INSTALLER_PS1_TEMPLATE,_PORTABLE_PS1_TEMPLATE,_write_bat_wrapper) were replaced. - Update launcher features: 30s PID wait with
taskkill /Fforce-close, restart-on-failure at every exit path, post-installmodules\version.pystructure verification,robocopy /XNsentence preservation, pre-download stale file removal, download truncation detection. - Generated updater
.batlaunchers useping -nfor one-second waits instead oftimeout /t;timeoutcan fail under redirected/noninteractive input and collapse retry windows. - Three fallback layers: primary bat → direct apply → re-download to
~/Downloads/. - Post-restart verification via
pending_update.jsonmarker: on next launch, compares current version against expected and announces spoken warning if update silently failed. - Temp file cleanup (
cleanup_stale_update_files) runs at startup: removes staged.exe/.zip/.bat/.sha256older than 3 days and leftover extract directories. - Integration test (
powershell -ExecutionPolicy Bypass -File tools/run_local_updater_integration.ps1 -StrictPortable): 22/22 steps passing for installer and portable paths. Harness artifacts intests/logs/local_updater/. - 1.20.0 release-readiness status:
modules/version.py,pyproject.toml,docs/user/WHATS_NEW.md, generatedsite/index.html, and generatedsite/changelog.htmlall show1.20.0.tests/test_about_menu.pyuses1.20.0in its version-facing About menu assertions.tests/run_local_updater_integration.pywrites strict portable evidence to dedicated files when run with--strict-portable/-StrictPortable, so the current saved strict run does not leave defaultREPORT.mdorresult.jsonfiles.- Push/PR GitHub automation is now consolidated into
.github/workflows/ci.yml; the overlapping.github/workflows/tests.ymlworkflow was removed after folding its quality-check coverage intoci.yml. tools/ship_updates.ps1now blocks accidental double bumps: ifmodules/version.pyis already modified, maintainers must publish withtools/release.ps1instead of the auto-bump wrapper.
- New user-facing guide is now
README.html(plain-language, WCAG-friendly structure).README.mdis a pointer. - Built-in sentence topics are now driven by
Sentences/manifest.jsonwith schema/docs indocs/dev/CONTENT_MANIFEST.mdanddocs/dev/schemas/sentences-manifest.schema.json. - Speed Test setup now uses a single source list with
Random Topicplus the regular manifest-driven practice topics; the separate dedicated speed-test branch was removed from the UI. - Speed Test source ordering now keeps
General,Random Topic, andGeneral Spanishgrouped together near the top. Random-topic mode for both Speed Test and Sentence Practice is English-only by topic name, but still includes other extra English sentence files. - Keyboard command sentence files were cleaned up for clearer, less technical wording.
- Blog-post helper content is now maintained locally outside Git and should not be treated as a tracked repo asset.
- Hangman is fully integrated and significantly expanded:
- offline dictionary-backed words/definitions
- weighted word-length selection centered on common lengths, with occasional short and very long words
- 10 wrong-guess visual stages with spoken stage descriptions
- comma-separated spoken word progress tokens for clearer SR pacing
- Left/Right/Home/End cursor navigation across word-progress positions with visual focus highlight
- results menu with
Word,Definition, copy action, replay, and sentence-practice bridge - sentence-practice prompts use varied style templates (story/mystery/science/etc.) instead of plain repetitive lines
- sentence-practice
Ctrl+Spacereads remaining text from current typing position Alt+Lreports letters left and total letters
- Speech formatting is now consistent for repeated letters, spaces, and mismatch feedback.
- Lesson prompts now speak authored practice words naturally, while drill patterns such as
asasoraassare spelled out. - Early lessons now front-load simpler 2, 3, and 4 key repeated drills before mixed patterns.
- Sentence Practice
Random Topicexcludes Spanish topics; Spanish is still available viaChoose Topic. Escape x3return to Main Menu is implemented across active non-menu modes.- Escape handling is centralized via
modules/escape_guard.py+ policy routing inmodules/keyquest_app.py.
- Escape handling is centralized via
- Main Menu Escape now uses the same guard: first and second Escape warn; the third Escape quits.
- Main menu labels/order updated (
Quests,Pets,Pet Shop,Badges). - Main menu now includes
About(menu-driven info screen with website launch action). - Word Typing countdown stutter at 10s/5s fixed.
- Startup speech ordering stabilized (title first via SR, then first menu item).
- Fixed slow first down-arrow announcement caused by startup speech protection window.
- Added fixed 250 ms duplicate-speech debounce to reduce stutter/repeats.
- Added spoken/visual goodbye message on app exit.
- Continue modularization of
modules/keyquest_app.pywhere practical —flash_managerandfont_managerare extracted; mode dispatch and cross-mode wiring remain candidates. - Keep docs in sync with active file layout under
tools/build/andtools/quality/. - Keep the local updater evidence current:
- Review
tests/logs/local_updater/REPORT_strict_portable.mdandtests/logs/local_updater/result_strict_portable.json. - Current saved strict run passes all 22 stages with
--strict-portable, including installer and portable relaunch into the harness version1.9.1. - Current saved portable log shows direct
tarextraction, robocopy code 3, successfulKeyQuest.exereplacement, restart, and launcher completion. - Saved evidence:
tests/logs/local_updater/REPORT_strict_portable.mdtests/logs/local_updater/result_strict_portable.jsontests/logs/local_updater/installed_app/keyquest_error.logtests/logs/local_updater/installed_app/fake_installer_trace.jsontests/logs/local_updater/portable_app/keyquest_error.logtests/logs/local_updater/installed_app/updater_boot.jsontests/logs/local_updater/portable_app/updater_boot.json
- Previous machine-level blockers (now resolved after Winsock reset + reboot):
py -3.11 -m pytestandasyncioimports previously failed withOSError: [WinError 10106]. Fixed afternetsh winsock reset+ reboot.py -3.11 -m pytest -qnow passes all 331 tests green.
- Remaining known quirk: Windows PowerShell 5.1 still fails to initialize (
8009001d) in this environment, but the portable updater tolerates it (tar fallback +startrestart). - The harness still supports the two portable test-only env vars, but the current strict run proves they are no longer required.
- Review
- Use
self.speech.say("...", priority=True, protect_seconds=2.0)for important announcements. - Keep visual and spoken content aligned.
- Use
get_app_dir()for runtime-safe path resolution (source and frozen exe). - Two-changelog pattern — both files must be updated on every release; neither replaces the other:
docs/dev/CHANGELOG.md— developer/technical changelog. Updated for every meaningful change with file names, function names, and implementation detail. This is the file AI assistants should update during feature/fix work.docs/user/WHATS_NEW.md— user-facing plain-English summary. Updated at release time only, describing what changed in terms a non-technical user can understand. No file names or code details.- The in-app What's New menu item links to
changelog.htmlon GitHub Pages (generated from these files), not the raw markdown directly. The URL isPAGES_CHANGELOG_URLinmodules/keyquest_app.py.
- Update
docs/dev/CHANGELOG.md,docs/user/WHATS_NEW.md, anddocs/dev/HANDOFF.mdfor meaningful behavior changes. - For new screens, use
ui/layout.pyfor screen size, centering, wrapped blocks, and footer placement. - For new game chrome, use
ui/game_layout.pyfor titles and status stacks. - Do not hardcode
900,600,450, or assume a single-line controls footer in new render code unless there is a documented reason.
- Confirmed
modules/version.pyis1.20.0and aligned withpyproject.toml,docs/user/WHATS_NEW.md,site/index.html, andsite/changelog.html. - Updated
tests/test_about_menu.pyso the version-facing About menu assertions use1.20.0instead of the previous release sample. - Verified the current local updater evidence paths match what the repo actually saves now:
REPORT_strict_portable.mdandresult_strict_portable.jsonexist; defaultREPORT.mdandresult.jsonare not present after the saved strict run. - Confirmed
result_strict_portable.jsonreportsPASS, 22/22 steps, and all step entries passed. - Baseline checks passed on 2026-05-22:
py -3.11 -m pytest -q -p no:cacheproviderreported 348 passed and 5 subtests passed;powershell -ExecutionPolicy Bypass -File tools/run_quality_checks.ps1passed release metadata validation and contrast checks. - Remaining before shipping: if the release should carry same-day updater evidence, rerun
tools/run_local_updater_integration.ps1 -StrictPortableto refresh the April 24 saved updater evidence, then publish through the normal release script.
- Added tracked root
AGENTS.mdso Codex has a repo-native instruction entrypoint. - Note: earlier notes mentioned repo-shared Codex skills under
.codex/skills/, but the current repo only tracks.codex/config.toml. Keep reusable general skills in the global Codex skill folders unless a KeyQuest-specific skill is intentionally added to this repo. - Added
tools/codex_exec_diagnostics.ps1:- dot-sources
tools/env_bootstrap.ps1 - reports PowerShell host details, PATH, repaired environment variables, command availability, UTF-8 state, and repo marker detection
- dot-sources
- Follow-up tooling hardening:
tools/codex_exec_diagnostics.ps1now falls back cleanly when Windows PowerShell 5.1 does not expose[Environment]::ProcessPathtools/env_bootstrap.ps1now normalizes console/pipeline encoding to UTF-8tools/env_bootstrap.ps1now adds a discoveredrg.exepath from the local Codex or Dyad install if present
- This reduces repeated session setup work through tracked instructions and diagnostics without requiring repo-local skill folders.
- This machine's Codex install was updated to
0.117.0on 2026-03-27. - Upstream release notes for the corresponding
rust-v0.117.0tag call out plugin-first workflows, plugin-backed mention fixes, and default rollout of plugin/app flags. - Practical implication for future sessions: a
skipped loading skillmessage may now reflect Codex/plugin gating behavior rather than a KeyQuest repo regression, so capture the exact post-update error text before changing repo files.
modules/update_manager.py:- Portable extraction now validates that
%EXTRACT_DIR%\KeyQuestexists afterExpand-Archive; if not, it logs the missing tree and falls back totareven when PowerShell does not return a useful non-zero exit code. - Portable replacement now copies
KeyQuest.exeas a separate retried step afterrobocopyso a transient EXE lock does not fail the whole update. - Detached helper sleeps now use
pinginstead oftimeout /t, becausetimeoutis unreliable in this environment and was collapsing the retry loops.
- Portable extraction now validates that
tests/run_local_updater_integration.py: added--strict-portableso the same harness can rerun the portable path with both test-only overrides disabled.tools/run_local_updater_integration.ps1: added-StrictPortablepassthrough.- At the time of this March 24 run, the harness saved a full 22/22 strict-portable pass to
tests/logs/local_updater/REPORT.mdandtests/logs/local_updater/result.json. Current saved evidence paths are listed in Active TODOs / Open Issues above. tests/logs/local_updater/portable_app/keyquest_error.log: shows the real portable sequence on this machine:Expand-Archive did not produce the extracted app tree. Trying tar fallback.Portable KeyQuest.exe replacement is still locked. Retrying.Portable KeyQuest.exe replacement succeeded.Portable update launcher finished.
- Winsock reset (
netsh winsock reset) + reboot resolvedOSError: [WinError 10106]for_overlapped/asyncio. py -3.11 -m pytest -qnow passes all 331 tests green.- Fixed one stale test assertion in
tests/test_update_manager.py: the portable launcher robocopy exclusion check now correctly matches the%ROBOCOPY_EXCLUDES%variable form used in the generated script.
modules/update_manager.py:- Added
UPDATE_URL_OVERRIDE_ENV = "KEYQUEST_UPDATE_RELEASE_URL"andget_configured_release_url()so the updater can target a fake local release feed without changing production defaults. - Added
is_installed_layout()and changedis_portable_layout()to returnFalsefor installer-based layouts that containunins*.exeor.keyquest-installed. - Updated the generated installer launcher to pass
/DIR="%APP_DIR%"when startingKeyQuestSetup.exe, so local and real installers are told exactly which app folder to replace. - Updated both generated launcher scripts to restart KeyQuest with
start "" "%APP_EXE%"first and only fall back to PowerShell ifstartfails. This avoids the local PowerShell-host failure from blocking relaunch. - Added
UPDATER_TEST_PYTHON_ENV = "KEYQUEST_UPDATER_TEST_PYTHON"andUPDATER_TEST_SKIP_EXE_COPY_ENV = "KEYQUEST_UPDATER_SKIP_EXE_COPY"for harness-only portable-path fallbacks. - Portable launcher now skips sentence merge when either side is missing, tries the optional Python ZIP extraction override before PowerShell /
tar, and can optionally excludeKeyQuest.exefrom the portablerobocopystep in harness runs.
- Added
modules/update_controller.py:check_for_update()now receivesupdate_manager.get_configured_release_url()so the running app can be pointed at a local feed through the environment.tests/updater_fixture_app.py: Added a tiny frozen-fixture app used by the updater harness.tests/run_local_updater_integration.py: Added a repeatable local updater harness for both installer and portable flows. It builds a fixture app exe and a fake installer exe with PyInstaller, creates a local file-based release feed (release.json, installer asset, portable zip, SHA-256 sidecars), stages installed-layout and portable-layout fixture apps, runs the real updater download + launcher paths, and saves a report totests/logs/local_updater/.tools/run_local_updater_integration.ps1: One-command wrapper for rerunning the local harness with the missing home-directory env vars populated.tests/test_update_manager.py: Added coverage for the/DIR=launcher argument, installed-layout detection, release URL override, and the new portable launcher fallback content.tools/build/KeyQuest-RootFolders.spec: Added temporary excludes forpkg_resources,setuptools, andjaracowhile debugging a local packaged-EXE startup failure (Failed to execute script 'pyi_rth_pkgres' ... The 'jaraco' package is required).- At the time of this earlier harness run,
tests/logs/local_updater/REPORT.mdandtests/logs/local_updater/result.jsonshowed a full pass for both installer and portable paths. Current saved evidence paths are listed in Active TODOs / Open Issues above.
Sentences/manifest.json: Added a canonical built-in sentence manifest so topic names, backing files, display labels, and explanations are data-driven instead of being hard-coded in app logic.modules/sentences_manager.py: Added manifest loading, fallback handling, topic metadata helpers, and speed-test file lookup via the manifest while still tolerating extra.txtfiles dropped intoSentences/. Follow-up cleanup removed the duplicated built-in manifest from Python; missing or invalid manifests now fall back by inferring topic entries from the actual sentence files on disk.modules/test_modes.py,modules/keyquest_app.py,ui/render_test_setup.py: Speed Test setup now uses one scrollable source list:Random Topicplus the regular topic entries using the same manifest-driven labels as Sentence Practice.modules/test_modes.py: Random-topic filtering now excludes Spanish and other non-English topic names for both Speed Test and Sentence Practice, while leaving other extra English.txttopics eligible.tests/test_sentences_manifest.py: Added coverage for manifest validity and runtime fallback behavior, including inference when the manifest is absent.tests/test_test_modes.py: Added focused coverage for the new Speed Test setup flow.docs/dev/CONTENT_MANIFEST.md,docs/dev/schemas/sentences-manifest.schema.json: Added developer documentation and a JSON schema for the sentence manifest format.modules/update_manager.py: Added_fetch_with_retry()— retriesfetch_latest_releaseup to 3 times with exponential backoff (3 s, 6 s) onUpdateNetworkError.UpdateHttpErrorand parse errors fail immediately.check_for_update()now calls this instead of callingfetch_latest_releasedirectly.modules/keyquest_app.py:- Updates now check automatically every 4 hours while the app is running (periodic timer in
_poll_update_work). - Updates also check each time the user reaches the main menu, rate-limited to once per hour.
- A found update is held until the user has been idle for 30 minutes (no keypresses), then installs silently. This prevents interrupting an active session.
_last_user_activityresets on everyKEYDOWN;_update_periodic_last_checkresets whenever a check thread starts.
- Updates now check automatically every 4 hours while the app is running (periodic timer in
tests/test_update_manager.py: 7 new tests for_fetch_with_retry(retry logic, backoff timing, no-retry on non-transient errors).tests/test_update_idle_logic.py: New file — 18 tests for the idle-gate and periodic-timer logic using a minimal app stub; includesTestConstantsMatchSourcewhich parseskeyquest_app.pywithastto ensure the test constants stay in sync.
modules/update_manager.py: Fixed_sentence_merge_powershell()— it was using{{and}}instead of{and}in plain Python strings. The batch file was passing literal{{to PowerShell, causing the sentence merge step to silently do nothing. Users' custom sentence files were not being preserved across any update.modules/test_modes.py:handle_practice_inputnow clearspending_compose_markon Escape, matching the behavior ofhandle_test_input. Without this, starting a Spanish compose sequence and then Escaping left a stale compose mark that would misfired unexpectedly in the next typing session.tests/test_update_manager.py: AddedassertNotIn('{{')/assertNotIn('}}')to both launcher tests.docs/dev/HANDOFF.md: Removed stale TODO about re-shipping the portable updater fix (already done in 1.5.13).
- Confirmed from the installed log at
C:\Users\csm12\AppData\Local\Programs\KeyQuest\keyquest_error.logthat portable updates were still stalling after download on the lineWaiting for KeyQuest process ... to exit before applying the portable update. - Confirmed the generated helper script on disk already had the 15-second force-close wait logic, so the stall was not the old no-timeout bug.
- Root cause:
modules/update_manager.pygenerated a malformed portable launcher after the wait loop by starting one PowerShell command and then injecting a second fullpowershell -Commandblock for sentence merging. That left the helpercmd.exeprocess alive without reaching extraction/copy/restart. - Fixed
modules/update_manager.pyto emit one valid merge command using%EXTRACT_DIR%\KeyQuest\Sentencesdirectly. - Tightened
tests/test_update_manager.pyto assert the portable launcher only contains onepowershell -Commandblock in that section. .githooks/pre-pushnow runsruff check .beforepytest -qfor release tags, matching GitHub’s CI gate and preventing anotherv1.5.12-style lint-only release failure.tools/dev/install_git_hooks.ps1now warns when Ruff is missing locally, in addition to pytest.
- Added
ui/layout.pyfor shared screen geometry:- live screen size lookup
- safe content width
- centered placement helpers
- wrapped centered and left-aligned text blocks
- footer row placement
- Added
ui/game_layout.pyfor shared game chrome:- centered game titles
- centered status-line stacks
- Refactored
games/base_game.pyandui/render_menus.pyto use the shared layout helpers instead of repeating centering and footer math. - Refactored
games/word_typing.py,games/letter_fall.py, andgames/hangman.pyto use the shared helpers for title, wrapped text, status, and footer placement while keeping gameplay-specific visuals local. - Added
tests/test_layout.pyto lock in the new helper behavior. - Accessibility review outcome: keep geometry in layout helpers, keep focus/emphasis in
ui/a11y.py, and keep gameplay meaning local to each screen.
- Extracted
modules/flash_manager.py(FlashState) andmodules/font_manager.py(detect_dpi_scale,build_fonts) fromkeyquest_app.py. progress.jsonsave is now atomic (temp-file + rename) — no data loss on crash.error_logging.pygained log rotation (2 MB cap) andlog_message()helper;dialog_manager.pynow routes errors there instead of a separate file.- Pet happiness decays 5 pts/day since last fed (applied at load time in
state_manager.py). keyquest.pywnow supports--versionflag; CI EXE smoke test uses it.- Ruff lint step and EXE smoke test were added to the release workflow.
requirements.lockandpyproject.toml(ruff + pytest config) added.- Test count: 100 → 179 (audio, speech, schema migration, file-not-found paths all covered).
docs/dev/ARCHITECTURE.mdadded (module map, mode state machine, conventions).- See top entry in
docs/dev/CHANGELOG.mdfor full detail.
- Added
tools/ship_updates.ps1as the preferred release entrypoint when you want version bump selection handled for you. - Added
tools/dev/release_bump.pyto suggest and apply conservativepatchorminorbumps. - Added
docs/dev/RELEASE_POLICY.mdsoupdate gitandship updateshave distinct meanings. - Added
docs/dev/CONTENT_STYLE_GUIDE.mdto keep guide, changelog, blog, and sentence wording consistent. - Audited and simplified Windows, NVDA, and JAWS command sentence files for clearer learner-facing wording.
- Expanded release/process notes in
docs/dev/DEVELOPER_SETUP.mdand refreshed this handoff file.
All items from the accessibility recommendations audit are now implemented. docs/dev/ACCESSIBILITY_RECOMMENDATIONS.md has been deleted. docs/user/ACCESSIBILITY_COMPLIANCE_SUMMARY.md updated.
Final three items completed this session:
modules/state_manager.py
- Added
font_scale: str = "auto"toSettingsdataclass; persisted viaload/save.
modules/menu_handler.py
- Added
get_font_scale_explanation(scale)andcycle_font_scale(current, direction)helpers.
modules/keyquest_app.py
- Added
_detect_dpi_scale()module-level function (ctypesGetDpiForSystem/ 96). - Added
_rebuild_fonts()method: recreatestitle_font,text_font,small_fontat the effective scale; propagates new font objects to all cached game instances. _rebuild_fonts()called at startup (afterload_progress) and on Font Size option change.- Added
Font Sizeoption to Options menu (auto/100%/125%/150%). - Added
_escape_remaining/_escape_nouninstance state. _handle_escape_shortcut()sets_escape_remainingon partial press, clears on completion or reset.draw()renders a centered top-of-screen text counter while_escape_remaining > 0(visual complement to the existing speech announcement).
tests/test_test_modes.py
- Added
trigger_flashstub to_DummyApp(required bytest_modes._record_typing_error).
README.html
- Options section expanded from one line to a full itemized list of all 8 settings (including Font Size).
- Quick Start Escape note updated to mention the on-screen remaining press counter.
- Accessibility section expanded with four new bullets: HC auto-detection, font size/DPI scaling, visual keystroke flash, escape press counter.
Earlier items (same session):
ui/a11y.py
- Added
draw_keystroke_flash(screen, color, alpha, screen_w, screen_h)— semi-transparent color overlay for visual keystroke feedback.
ui/render_results.py
- Added
small_font,screen_h,accentparameters. - Added
draw_controls_hint()at bottom ("Space/Enter continue; Esc menu") — this was the only render screen missing a controls hint.
ui/render_tutorial.py
- Added
screen_hparameter. - Fixed tutorial intro controls hint Y from hardcoded
540toscreen_h - 60, matching all other screens.
ui/render_menus.py
draw_lesson_menu(): silently truncated lesson list now shows "v more below v" when items exceed screen height.
modules/theme.py
detect_theme()now checks Windows High Contrast mode (viactypes/SPI_GETHIGHCONTRAST) beforedarkdetect. Users with HC enabled in Windows Settings get the in-apphigh_contrasttheme automatically.- Dark theme HILITE nudged from
(80, 120, 180)?(90, 130, 190): contrast ratio improved from 4.69:1 to 5.77:1, giving comfortable margin above the 4.5:1 WCAG AA minimum.
modules/notifications.py
- Removed emoji characters (
??,??,badge['emoji']prefix) from badge and level-up dialog text content. Screen readers that intercept wx TextCtrl content directly no longer expand emoji verbosely. Speech announcement paths (which already used clean text) unchanged.
modules/keyquest_app.py
- Added
_flash_color/_flash_untilstate andtrigger_flash(color, duration=0.12)method. draw()renders a fading flash overlay after all content when a flash is active.draw_results()call updated with newsmall_font,screen_h,accentparams.draw_tutorial()call updated with newscreen_hparam.- Tutorial correct/incorrect handlers call
self.trigger_flash()(green / red).
modules/lesson_mode.py
process_lesson_typing(): callsapp.trigger_flash((0, 80, 0), 0.12)on correct keystroke,app.trigger_flash((100, 0, 0), 0.12)on error.
modules/test_modes.py
_record_typing_error(): callsapp.trigger_flash((100, 0, 0), 0.12)on typing error.
Not implemented (future work — documented in docs/dev/ACCESSIBILITY_RECOMMENDATIONS.md):
- User-adjustable font size (
modules/config.py) — requires Options menu entry + font re-creation on change. - DPI scaling — should be paired with font size work; risk of layout regressions across all screens.
- Escape guard visual count — speech already announces remaining presses; visual overlay is low priority.
- Declared milestone release as
Version 1.0(modules/version.py). - Added
About: Ato the bottom of the main menu (modules/state_manager.py). - Added new
ABOUTmode inmodules/keyquest_app.py:- menu-driven about items (app/version, name, company, tagline, copyright, license, website, credits, back)
- website item opens
https://webfriendlyhelp.comvia default browser on Enter/Space - Escape returns to Main Menu
- Added
LICENSEfile with MIT terms for open collaboration and redistribution. - Added Windows installer build path with Inno Setup:
tools/build/installer/KeyQuest.isstools/build/build_installer.battools/build.ps1 -Target installer
- Updated
README.htmlmain menu docs with About details and Version 1.0 marker.
- Initialized git repo in workspace and published source to GitHub:
- Repo:
https://github.com/WebFriendlyHelp/KeyQuest - Default branch:
main
- Repo:
- Created and published release tag
v1.0with downloadable assets:KeyQuestSetup-1.0.exeKeyQuest-1.0-win64.zip- Release page:
https://github.com/WebFriendlyHelp/KeyQuest/releases/tag/v1.0
- Updated README feedback links to include email subject:
mailto:help@webfriendlyhelp.com?subject=KeyQuest%20Feedback
- Removed dedicated Hangman item from the user-facing Games list in
README.htmlwhile keeping Hangman implemented in app. - Performed a source-comment wording pass for public-facing clarity in a few modules (
dialog_manager,currency_manager,key_analytics,lesson_manager,ui/pet_visuals).
- Added shared escape press tracker:
modules/escape_guard.py. - Moved active-mode Escape behavior to centralized policy/handler in
modules/keyquest_app.py.- Keyboard Explorer uses
Escape x3via the shared path. - Sentence Practice finish-on-Escape-x3 now routes through the same centralized handler.
- Keyboard Explorer uses
games/hangman.pyupdates:- Word progress speech now uses comma separators (
c, blank, t) for better SR clarity. - Added word-position cursor navigation (
Left/Right/Home/End) with spoken position feedback and visual focus highlight. - Sentence-practice
Ctrl+Spacenow announces remaining text based on current typing position. - Replaced simple sentence-practice templates with randomized style-based prompts for higher-quality, less repetitive lines.
- Removed fixed max word length; max is now derived from dictionary data.
- Updated word selection to favor common lengths while still allowing short and rare very long words.
- Word progress speech now uses comma separators (
- Documentation:
- Updated
README.htmlHangman controls/details. - Updated
docs/dev/CHANGELOG.mdwith this change set.
- Updated
- Documentation:
- Replaced user-facing readme content with
README.html(plain-language, main-menu organized). - Added WCAG-oriented page structure for the guide (landmarks, skip link, focus-visible styles).
- Simplified
README.mdto a pointer toREADME.html. - Updated
docs/dev/CHANGELOG.mdwith latest Hangman/readme/build notes.
- Replaced user-facing readme content with
- Build/distribution:
- Updated
tools/build/KeyQuest-RootFolders.specto copyREADME.htmlto:dist/KeyQuest/README.htmldist/KeyQuest/docs/README.html
- Updated
- In-app docs text alignment:
- Updated gameplay hotkey/instruction text for
Letter Fall,Word Typing, andHangmanto reflectEscape x3exit behavior.
- Updated gameplay hotkey/instruction text for
modules/speech_format.py:- Added
spell_text_for_typing_instruction()for clearerType ...prompts. - Uses
thenseparators when sequence order is likely important (special keys/spaces or repeated characters).
- Added
modules/lesson_mode.py,modules/keyquest_app.py,modules/lesson_manager.py:- Unified typing-prompt formatting on the new helper.
- Kept mismatch/remaining feedback format unchanged (
Missing: ... Remaining text: ...).
modules/lesson_mode.py:- Early stages (0-5) now normalize practice targets to 3-4 keys for easier recall.
modules/test_modes.py:Random Topicnow picks from non-Spanish topics only (fallback to all topics if needed).
- Tests:
- Updated/added coverage in
tests/test_speech_format.py,tests/test_lesson_mode.py, andtests/test_test_modes.py.
- Updated/added coverage in
- Docs:
- Updated
README.mdanddocs/dev/CHANGELOG.mdto match implemented behavior.
- Updated
- Added shared speech-format helpers and consistent mismatch announcements.
- Updated menu labels/order and Word Typing countdown behavior.
- Reorganized root helper/build files into
tools/build/,tools/quality/, anddocs/. - Added targeted regression tests in
tests/test_test_modes.pyandtests/test_word_typing.py. - Added adaptive tutorial flow (space-first onboarding, phase pacing based on performance, extra reps for troublesome keys).
- Added typing sound intensity option (
subtle/normal/strong) with persistence. - Updated PyInstaller spec for reorganized repo layout and stabilized Python 3.9 compatibility.
- Updated exe distribution docs policy to include
README.md,README.html, and user-facing docs indist/KeyQuest/docs/.
modules/keyquest_app.py:- Startup menu announcement is timer-driven to avoid racing with screen-reader title announcement.
- Startup announcement uses non-interrupting speech for first menu item.
- Removed startup priority protection that delayed first menu arrow navigation speech.
- Quit path now shows and speaks a brief goodbye message before exiting.
modules/speech_manager.py:- Added fixed duplicate-speech debounce (
0.25s) for identical rapid repeats.
- Added fixed duplicate-speech debounce (
For full details, see the top entry in docs/dev/CHANGELOG.md.