Spaces:
Sleeping
Sleeping
| # Progress: Election Process Assistant | |
| ## 2026-05-01 | |
| ### Starting State | |
| - Repository contained `.gitattributes`, an empty `implimentation_plan.md`, and `.kiro/specs/election-process-assistant/requirements.md`. | |
| - No application source files, tests, package manifest, or progress tracker existed. | |
| - `git status --short` showed `.kiro/` and `implimentation_plan.md` as untracked. | |
| ### Changes Made | |
| 1. Updated `implimentation_plan.md` | |
| - Converted the empty file into a requirement-by-requirement implementation checklist. | |
| - Documented the decision to use `.kiro/specs/election-process-assistant/requirements.md` as the source of truth. | |
| - Defined phases, done criteria, technical approach, and explicit coverage for all 20 requirements. | |
| 2. Created `progress.md` | |
| - Started this implementation log. | |
| - Recorded the repository starting state and first tracked change. | |
| 3. Added the application shell | |
| - Created `index.html` with accessible landmarks, personalization controls, primary navigation, and sections mapped to all requirements. | |
| - Created `styles.css` with mobile-first layout, keyboard focus states, responsive grids, print rules, and accessible control sizing. | |
| - Added `assets/icon.svg` and `assets/civic-map.svg` as local visual assets. | |
| - Added `manifest.webmanifest` for installable PWA metadata. | |
| - Added `sw.js` with core asset caching and offline fallback behavior. | |
| - Added `package.json` with `start` and `test` scripts that do not require external packages. | |
| 4. Added structured election data | |
| - Created `data/election-data.json`. | |
| - Included mock jurisdiction data for California, Texas, and New York. | |
| - Added deadlines, registration rules, eligibility rules, process steps, voting methods, ballot samples, polling locations, official-link fields, education modules, reform content, organizer tools, incident types, and integration schemas. | |
| - Marked the data boundary clearly as mock demonstration data that must be replaced with official feeds for production. | |
| 5. Implemented interactive application logic | |
| - Created `app.js`. | |
| - Added state restoration for progress, jurisdiction, language, accessibility mode, notifications, analytics, feedback, incidents, and organizer batches. | |
| - Implemented personalized dashboard, step guide, timeline, ICS export, registration tracking, eligibility validation, voting method comparison, ballot research notes, polling finder, voter guide generation, feedback collection, incident reporting, educational modules, voting innovation content, registration drive tooling, analytics summary, integration adapter, offline queue, session timeout, password policy validation, rate limiting, account deletion, and Web Crypto local vault controls. | |
| - Added service worker registration and online/offline status handling. | |
| 6. Added local tooling | |
| - Created `tools/dev-server.mjs` for a dependency-free local static server on `127.0.0.1:4173`. | |
| - Created `tests/smoke-check.mjs` to verify JavaScript syntax, UI requirement coverage for all 20 requirements, data integrity, PWA cache coverage, accessibility hooks, and progress tracking. | |
| 7. Adjusted smoke checks for the local sandbox | |
| - Initial `node tests/smoke-check.mjs` attempt failed because the test tried to spawn a nested Node process for `node --check`, which is blocked with `EPERM` in this sandbox. | |
| - Updated `tests/smoke-check.mjs` to parse browser scripts in-process and use structural checks for the local dev server instead of spawning child processes. | |
| 8. Ran verification | |
| - `npm test` was blocked by the local PowerShell execution policy for `npm.ps1`; the equivalent direct Node command was used instead. | |
| - `node tests/smoke-check.mjs` passed. | |
| - `node --check app.js` passed. | |
| - `node --check sw.js` passed. | |
| - `node --check tools/dev-server.mjs` passed. | |
| - Started the local dev server outside the sandbox after the sandboxed detached process exited immediately. | |
| - Verified HTTP `200` responses from: | |
| - `http://127.0.0.1:4173/` | |
| - `http://127.0.0.1:4173/data/election-data.json` | |
| - `http://127.0.0.1:4173/manifest.webmanifest` | |
| - After final cleanup and documentation updates, reran `node tests/smoke-check.mjs`; it passed. | |
| - Rechecked `http://127.0.0.1:4173/`; it returned HTTP `200`. | |
| 9. Cleaned temporary diagnostics | |
| - Removed empty `server.out.log` and `server.err.log` files created during detached server troubleshooting. | |
| ### Files Changed | |
| - `implimentation_plan.md` | |
| - `progress.md` | |
| - `index.html` | |
| - `styles.css` | |
| - `app.js` | |
| - `data/election-data.json` | |
| - `assets/icon.svg` | |
| - `assets/civic-map.svg` | |
| - `manifest.webmanifest` | |
| - `sw.js` | |
| - `package.json` | |
| - `tools/dev-server.mjs` | |
| - `tests/smoke-check.mjs` | |
| ### Production Boundaries | |
| - Election records, deadlines, candidates, measures, polling places, wait times, and status lookups use mock demonstration data. Production must connect official state and local election authority feeds before citizens rely on the information. | |
| - TLS 1.3 enforcement must be handled by the production hosting layer. The local prototype includes privacy controls, data minimization, password policy simulation, rate limiting, session timeout, and an AES-GCM local vault. | |
| - SMS, email, and push notifications are simulated locally. Production delivery needs approved providers, consent records, unsubscribe handling, and audit logs. | |
| ### Verification | |
| - Confirmed `node --version` works locally: `v24.14.1`. | |
| - Confirmed `python` is not available in the shell, so verification and local server tooling will use Node.js. | |
| ### Current Status | |
| - Implementation plan is complete. | |
| - App shell, structured data, interactive workflows, PWA files, local server, and smoke checks are complete. | |
| - Verification passed. | |
| - Local dev server is running at `http://127.0.0.1:4173/`. | |
| - No implementation-plan requirement was intentionally skipped; production-only integrations are explicitly listed above. | |
| ## 2026-05-01 Python Reimplementation | |
| ### User Direction | |
| - User requested that the current implementation be deleted and reimplemented in Python with full detailed implementation. | |
| ### Changes Made | |
| 1. Stopped the previous Node server | |
| - Found old `node` process `13160`. | |
| - Initial sandboxed stop attempt was denied by Windows process permissions. | |
| - Stopped it successfully after approval. | |
| 2. Deleted the previous JavaScript/Node implementation | |
| - Deleted `app.js`. | |
| - Deleted `index.html`. | |
| - Deleted `styles.css`. | |
| - Deleted `sw.js`. | |
| - Deleted `manifest.webmanifest`. | |
| - Deleted `package.json`. | |
| - Deleted `tools/dev-server.mjs`. | |
| - Deleted `tests/smoke-check.mjs`. | |
| - Deleted `data/election-data.json`. | |
| - Deleted `assets/icon.svg`. | |
| - Deleted `assets/civic-map.svg`. | |
| 3. Updated `implimentation_plan.md` | |
| - Replaced the JavaScript/static plan with a Python-first implementation plan. | |
| - Kept all 20 requirements mapped to Python-backed workflows, services, or routes. | |
| - Documented that the only remaining JavaScript will be a tiny static service worker required by browser PWA standards, with no application workflow logic. | |
| 4. Added Python application package and services | |
| - Created `server.py` as the Python entry point. | |
| - Created `election_assistant/__init__.py`. | |
| - Created `election_assistant/seed_data.py` with Python dictionaries for jurisdictions, deadlines, registration, eligibility, voting methods, ballot data, polling locations, education modules, innovations, organizer tools, incident types, integration schemas, and translations. | |
| - Created `election_assistant/utils.py` for shared path, HTML, JSON, date, and form helpers. | |
| - Created `election_assistant/state.py` for persistent JSON state in `storage/app_state.json`. | |
| - Created `election_assistant/timeline.py` for deadline sorting, days remaining, critical deadline detection, and ICS generation. | |
| - Created `election_assistant/registration.py` for registration status, eligibility validation, and problem resolution guidance. | |
| - Created `election_assistant/voting.py`, `election_assistant/ballot.py`, and `election_assistant/polling.py` for voting methods, candidate/measure comparison, and polling location workflows. | |
| - Created `election_assistant/documents.py` for Python-generated HTML/text voter guides and printable organizer toolkit output. | |
| - Created `election_assistant/feedback.py` for feedback, incident reporting, escalation, review flags, and pattern detection. | |
| - Created `election_assistant/analytics.py` for local anonymized usage summaries and completion metrics. | |
| - Created `election_assistant/integrations.py` for schema validation, mock official adapters, retry with exponential backoff, and integration logging. | |
| - Created `election_assistant/education.py` for civic modules, innovation content, process improvements, and registration drive batch tracking. | |
| - Created `election_assistant/security.py` for password policy validation, rate limiting, session timeout, account simulation, and a standard-library AES-256-CTR + HMAC-SHA256 local vault. | |
| - Created `election_assistant/render.py` for server-side HTML rendering of every requirement section. | |
| - Created `election_assistant/web.py` for Python HTTP routes, form actions, static serving, downloads, and status API. | |
| 5. Added Python-era static/PWA assets | |
| - Created `static/styles.css`. | |
| - Created `static/assets/icon.svg`. | |
| - Created `static/assets/civic-map.svg`. | |
| - Created `static/manifest.webmanifest`. | |
| - Created `static/service-worker.js`. | |
| - Created `static/pwa-register.js`. | |
| - Created `storage/.gitkeep`. | |
| - The service worker and registration file are browser-standard PWA support only; application workflows are implemented in Python. | |
| 6. Added Python smoke tests | |
| - Created `tests/test_python_app.py`. | |
| - Tests cover language support, jurisdiction data, rendered requirement coverage for all 20 requirements, ICS generation, guide exports, organizer toolkit export, password policy, AES vault round-trip, integration adapter logging, manifest integrity, and removal of old Node/JavaScript implementation files. | |
| 7. Added Python cleanup safeguards | |
| - Created `.gitignore` for Python cache files, local state, test caches, and logs. | |
| - Initial test run compiled successfully but failed because `tempfile.TemporaryDirectory()` cleanup was blocked by Windows temp permissions. | |
| - Updated `tests/test_python_app.py` to use in-memory copied default state instead of temp directories. | |
| - Removed generated `__pycache__` folders from the workspace. | |
| 8. Added Python usage documentation | |
| - Created `README.md` with the bundled Python run command, local URL, test commands, and production data notes. | |
| 9. Ran Python verification | |
| - `python -m compileall server.py election_assistant tests` passed using bundled Python `3.12.13`. | |
| - `python -m unittest tests.test_python_app` passed: 9 tests. | |
| - Started the Python server at `http://127.0.0.1:4173/`. | |
| - Verified HTTP `200` from: | |
| - `http://127.0.0.1:4173/` | |
| - `http://127.0.0.1:4173/static/manifest.webmanifest` | |
| - `http://127.0.0.1:4173/api/status` | |
| - `http://127.0.0.1:4173/download/ics?id=ca-general-election` | |
| - `http://127.0.0.1:4173/download/guide.txt` | |
| - Verified a feedback POST route returns HTTP `303` redirect after processing. | |
| - Removed smoke-test-generated `storage/app_state.json` so the app opens with clean default state. | |
| - Removed generated `__pycache__` folders after verification. | |
| - Final rerun after README/progress edits: `python -m unittest tests.test_python_app` passed again with 9 tests. | |
| - Final HTTP check for `http://127.0.0.1:4173/` returned `200`. | |
| ### Python Files Changed | |
| - `.gitignore` | |
| - `README.md` | |
| - `implimentation_plan.md` | |
| - `progress.md` | |
| - `server.py` | |
| - `election_assistant/__init__.py` | |
| - `election_assistant/analytics.py` | |
| - `election_assistant/ballot.py` | |
| - `election_assistant/documents.py` | |
| - `election_assistant/education.py` | |
| - `election_assistant/feedback.py` | |
| - `election_assistant/integrations.py` | |
| - `election_assistant/polling.py` | |
| - `election_assistant/registration.py` | |
| - `election_assistant/render.py` | |
| - `election_assistant/security.py` | |
| - `election_assistant/seed_data.py` | |
| - `election_assistant/state.py` | |
| - `election_assistant/timeline.py` | |
| - `election_assistant/utils.py` | |
| - `election_assistant/voting.py` | |
| - `election_assistant/web.py` | |
| - `static/styles.css` | |
| - `static/assets/icon.svg` | |
| - `static/assets/civic-map.svg` | |
| - `static/manifest.webmanifest` | |
| - `static/service-worker.js` | |
| - `static/pwa-register.js` | |
| - `storage/.gitkeep` | |
| - `tests/test_python_app.py` | |
| ### Python Production Boundaries | |
| - Election records, deadlines, candidates, measures, polling places, wait times, and status lookups still use mock demonstration data. Production must connect official election authority APIs. | |
| - SMS, email, and push delivery are simulated in Python state. Production needs real delivery providers, consent records, unsubscribe handling, and delivery audit logs. | |
| - TLS 1.3 enforcement belongs to the production hosting layer. | |
| - Superseded by TODO/review remediation: custom cryptography has been removed. Vault encryption now requires the optional `cryptography` package and otherwise fails closed. | |
| ### Current Python Reimplementation Status | |
| - Previous implementation has been removed. | |
| - Python-first plan, package, server, services, rendering, routes, static assets, storage scaffold, smoke tests, and ignore rules are in place. | |
| - Verification passed. | |
| - Python server is running at `http://127.0.0.1:4173/`. | |
| - Final changed-file review complete. | |
| - No implementation-plan requirement was intentionally skipped; production-only integrations and provider requirements are listed above. | |
| ## 2026-05-01 TODO/Review Remediation | |
| ### Source Files Reviewed | |
| - `todo.md` | |
| - `review.md` | |
| ### Changes Made | |
| 1. Removed custom cryptography | |
| - Replaced the hand-written AES implementation in `election_assistant/security.py`. | |
| - Vault sealing/opening now uses the optional `cryptography` package with Fernet and PBKDF2-HMAC when installed. | |
| - If `cryptography` is unavailable, the vault fails closed with an explicit dependency error instead of using custom crypto. | |
| - Added `cryptography>=42.0` to `requirements.txt`. | |
| 2. Added CSRF protection | |
| - Added session CSRF token generation in `election_assistant/state.py`. | |
| - Added CSRF hidden-field injection for every POST form in `election_assistant/render.py`. | |
| - Added POST CSRF validation in `election_assistant/web.py`. | |
| 3. Hardened input handling | |
| - Added `clean_text` and `clean_choice` helpers in `election_assistant/utils.py`. | |
| - Applied input normalization and allow-listing to settings, profile, process steps, notifications, method selection, notes, feedback, incidents, registration batches, and module completion actions. | |
| 4. Replaced single JSON state with SQLite session state | |
| - Reworked `election_assistant/state.py` to use `storage/app_state.sqlite3`. | |
| - Added per-session state isolation keyed by an HTTP-only `epa_session` cookie. | |
| - Updated `.gitignore` to exclude runtime SQLite state files. | |
| 5. Replaced `http.server` request handling | |
| - Reworked `election_assistant/web.py` into a WSGI application using `wsgiref.simple_server`. | |
| - Removed `BaseHTTPRequestHandler`, `ThreadingHTTPServer`, and direct `http.server` routing. | |
| - Added explicit response, request, cookie, static-file, download, and POST handling in the WSGI layer. | |
| - Added production dependencies to `requirements.txt` for a future FastAPI/Uvicorn/SQLAlchemy deployment path. | |
| 6. Added Google service integration hooks | |
| - Added Google Civic Information API support in `election_assistant/integrations.py` using `GOOGLE_CIVIC_API_KEY`. | |
| - Added Google Maps directions URL generation and optional embed URL support through `GOOGLE_MAPS_EMBED_KEY`. | |
| - Updated polling rendering to use Google Maps directions and optional embedded map when configured. | |
| - Kept mock fallback data when API keys are not configured. | |
| 7. Expanded tests | |
| - Reworked `tests/test_python_app.py`. | |
| - Added tests for SQLite session isolation, CSRF form rendering, WSGI GET/POST flow, missing-CSRF rejection path, registration eligibility unit cases, custom-crypto removal, Google Maps URL generation, and existing export/data coverage. | |
| 8. Updated review tracking documents | |
| - Updated `todo.md` checkboxes to reflect completed remediation items and the remaining asynchronous I/O dependency. | |
| - Added a remediation summary to `review.md`. | |
| - Added `requirements.txt` with production dependency targets: `cryptography`, `fastapi`, `uvicorn`, `sqlalchemy`, `httpx`, and `pytest`. | |
| - Updated `implimentation_plan.md` to match the remediated WSGI, SQLite session-state, CSRF, and Google adapter approach. | |
| ### Current Remediation Status | |
| - Code changes for the actionable TODO/review items are in place. | |
| - `python -m compileall server.py election_assistant tests` passed after the SQLite connection-close fix. | |
| - `python -m unittest tests.test_python_app` passed: 13 tests. | |
| - Started the updated WSGI Python server at `http://127.0.0.1:4173/`. | |
| - Verified HTTP `200` from: | |
| - `http://127.0.0.1:4173/` | |
| - `http://127.0.0.1:4173/api/status` | |
| - `http://127.0.0.1:4173/static/manifest.webmanifest` | |
| - Verified `/api/status` reports `"session_isolated": true` and `"storage": "sqlite"`. | |
| - Verified a CSRF-protected feedback POST returns HTTP `303`. | |
| - Verified a missing-CSRF feedback POST returns HTTP `303` after rejection/flash handling. | |
| - Fixed the initial SQLite test database cleanup failure by closing SQLite connections explicitly in `StateStore`. | |
| - Updated `README.md` with SQLite state, optional production dependencies, and Google API environment variables. | |
| - Previous server process PID `15952` has been superseded by the updated server process recorded below. | |
| - `todo.md` and `review.md` now reflect the remediation status. | |
| - Final rerun after documentation updates: compile passed, `python -m unittest tests.test_python_app` passed with 13 tests, and `/api/status` returned HTTP `200`. | |
| ## 2026-05-01 Global Dynamic Election Support Remediation | |
| ### Source Files Reviewed | |
| - `todo.md` | |
| - `review.md` | |
| ### Changes Made In This Pass | |
| 1. Added a country-aware data source layer | |
| - Added `election_assistant/data_sources.py` with pluggable adapters, a standard jurisdiction schema, country metadata, generic process steps, US, India, and generic JSON-backed adapters. | |
| - Added SQLite caching for standardized jurisdiction payloads in `storage/data_source_cache.sqlite3`. | |
| - Expanded the US adapter to list all 50 states, keeping rich existing seed data for California, Texas, and New York while adding generic Vote.gov-backed fallbacks for the other states. | |
| - Added country JSON configs under `data/countries/` for the United Kingdom, Canada, and European Union sample coverage. | |
| 2. Wired country and jurisdiction selection through the Python app | |
| - Added `country_code` and locale-suggestion tracking to `election_assistant/state.py`. | |
| - Updated `/setup`, settings, `/action/onboard`, `/action/settings`, `/api/status`, registration lookup, and data refresh flows in `election_assistant/web.py`. | |
| - Added browser `Accept-Language` country suggestion for setup when a safe country hint is available. | |
| 3. Removed dead admin and feedback code | |
| - Deleted the unused `render_admin()` function from `election_assistant/render.py`. | |
| - Removed the unused admin imports and repeated `section_feedback()` calls from content pages. | |
| 4. Added country-neutral ballot fallback support | |
| - Added local manual candidate notes to `election_assistant/state.py`, `election_assistant/render.py`, and `election_assistant/web.py`. | |
| - Kept private ballot research notes and official-source links available for jurisdictions without live candidate APIs. | |
| 5. Refined the UI | |
| - Updated `static/styles.css` with a cleaner palette, lighter shadows, white tab navigation, tighter header controls, setup-page layout, and less demo-like footer/caption copy. | |
| - Replaced implementation-flavored UI wording such as "Python server-rendered app" and "official-style status". | |
| 6. Expanded tests | |
| - Updated `tests/test_python_app.py` to cover country adapters, India setup, 50-state US listing, locale-based setup suggestion, non-US onboarding status, and manual candidate rendering. | |
| 7. Updated runtime and documentation | |
| - Made `server.py` tolerate missing optional `python-dotenv` so the bundled Python runtime can start the server without installing packages. | |
| - Updated `todo.md`, `review.md`, `implimentation_plan.md`, and `README.md` to match the country-aware implementation and remaining external-provider work. | |
| ### Verification Status | |
| - `python -m compileall server.py election_assistant tests` passed. | |
| - `python -m unittest tests.test_python_app` passed with 14 tests. | |
| - Started the updated WSGI Python server at `http://127.0.0.1:4173/` with bundled Python runtime process PID `28948`. | |
| - Verified `GET /setup` returned HTTP `200` and rendered the country selector, India locale suggestion, Tamil Nadu region, and polished setup copy. | |
| - Verified `GET /api/status` returned country, jurisdiction, SQLite storage, and session-isolation fields. | |
| - Verified `POST /action/onboard` with CSRF for `country_code=IN` and `jurisdiction_id=tamil-nadu` returned HTTP `303`. | |
| - Verified the same session then returned `"country": "India"` and `"jurisdiction": "Tamil Nadu"` from `/api/status`. | |
| - Verified `GET /static/manifest.webmanifest` returned HTTP `200`. | |
| - Cleaned generated `__pycache__` directories and temporary `storage/test_state_*.sqlite3` files. | |
| ## 2026-05-03 Google Services And Quality Remediation | |
| ### Source Files Reviewed | |
| - `todo.md` | |
| - `review.md` | |
| - Full `election_assistant/` package | |
| - `tests/test_python_app.py` | |
| - `README.md`, `.env.example`, `requirements.txt`, `pyproject.toml` | |
| ### Changes Made In This Pass | |
| 1. Google service correctness | |
| - Updated `election_assistant/google_services.py` so Google Calendar deep links remain always usable while separately exposing optional `GOOGLE_CALENDAR_API_KEY` state. | |
| - Fixed Google Calendar event URL timestamp generation so timezone offsets do not leak into the `dates=` value. | |
| - Kept Google service failures on safe fallbacks with logged warnings. | |
| 2. Google routes and endpoints | |
| - Added constants for `/action/export-sheets`, `/action/check-url`, `/auth/google`, and `/auth/google/callback`. | |
| - Updated `election_assistant/web.py` to use route constants for the new Google actions. | |
| - Added Google OAuth start and callback routes with secure state validation and safe fallback when credentials are absent. | |
| - Changed Sheets export POST fallback to return a real CSV download instead of only flashing a message. | |
| - Kept `/action/export-csv` as a backward-compatible alias while using `/action/export-sheets` as the documented route. | |
| 3. Safe Browsing integration | |
| - Integrated `SafeBrowsingService.check_url()` into manual candidate creation. | |
| - Candidate URLs flagged unsafe by Google Safe Browsing are rejected before being stored. | |
| - Candidate records store URL safety metadata when saved. | |
| 4. Security headers and logging | |
| - Expanded CSP Google domains for Gemini, Translate, Sheets, Safe Browsing, OAuth, Calendar, Maps, and Google APIs. | |
| - Added request logging and exception logging in `election_assistant/web.py`. | |
| 5. Accessibility and UI polish | |
| - Confirmed active navigation uses `aria-current="page"`. | |
| - Replaced non-ASCII UI status marks and separators in `render.py` with plain ASCII text. | |
| - Updated the document title separator to ASCII. | |
| - Changed the organizer export form to post to `/action/export-sheets`. | |
| - Added a Google Sign-In link on the services page. | |
| 6. Configuration and documentation | |
| - Added `GOOGLE_CALENDAR_API_KEY` to `.env.example`. | |
| - Updated `README.md` to document `/action/export-sheets`, `/auth/google`, and `/auth/google/callback`. | |
| 7. Tests expanded | |
| - Added fallback tests for Gemini, Translate, Calendar, Sheets, Safe Browsing, OAuth URL construction, and Google service status. | |
| - Added mocked API response parsing tests for Gemini, Translate, and Safe Browsing. | |
| - Added WSGI tests for `/api/google-services`, `/api/calendar-link`, `/auth/google`, and CSV fallback from `/action/export-sheets`. | |
| 8. Public API documentation sweep | |
| - Added one-line docstrings to every public function and class in `election_assistant/`. | |
| - Verified with an AST check that no public functions/classes are missing docstrings. | |
| ### Verification Status | |
| - `python -m compileall server.py election_assistant tests` passed. | |
| - `python -m unittest tests.test_python_app` passed with 44 tests. | |
| - Restarted the updated local WSGI server with bundled Python runtime process PID `25312`. | |
| - Verified `GET /api/google-services` returned all 10 Google service entries. | |
| - Verified `GET /api/calendar-link` returned a Google Calendar URL with clean `dates=20261005T170000/20261005T180000` structure. | |
| - Verified CSRF-protected `POST /action/export-sheets` returned `text/csv; charset=utf-8` fallback content when no Sheets key was configured. | |
| - Verified `GET /auth/google` returned HTTP `303` to `/services` when OAuth credentials were absent. | |
| - Verified `/learning` renders the `/action/export-sheets` form and active navigation includes `aria-current="page"`. | |
| - Cleaned generated `__pycache__` directories and temporary `storage/test*.sqlite3` files. | |