# Development Progress This is the living implementation log for Steady. Update it after every meaningful development step with what changed, why it changed, how it was verified, and what remains. ## Current Status - Current phase: Phase 6 — Admin Dashboard Placeholder - Status: Complete — placeholder scope - Last updated: 2026-08-08 - Next phase: Phase 7 — Polish & Testing ## Phase 1 — Core Infrastructure ### 1. Application foundation Added an application factory, environment-specific configuration, secure defaults, feature flags, and independently initialized Flask extensions. Why: isolated application instances support testing and future deployments, while `init_app()` keeps extensions reusable. Feature packages can be added without creating a single tightly coupled application module. Key files: `app/__init__.py`, `app/extensions.py`, `config.py`, and `wsgi.py`. ### 2. Modular routing Added `core` and `auth` Blueprints, a `/health` endpoint, and reserved packages for tasks, settings, admin, and the versioned API. Why: each feature owns its routes and implementation. The health endpoint provides a lightweight operational check that does not depend on authentication or database access. ### 3. Secure authentication Implemented user registration, login, POST-only logout, password hashing, CSRF protection, safe redirects, generic login failures, and unique username/email handling. Authentication is separated into models, forms, services, and routes under `app/auth/`. Why: separating HTTP handling from business logic allows future web and API interfaces to reuse the same account services. Security checks are part of the initial design rather than later additions. ### 4. Shared interface Added a reusable Jinja base template, authentication pages, navigation, feedback messages, and a responsive pastel stylesheet. Why: all future features inherit consistent navigation and accessible interaction. The interface includes keyboard focus indicators, system light/dark themes, restrained animation, and reduced-motion support. ### 5. Database migrations Configured Flask-Migrate and generated the initial Alembic migration for the `user` table. Why: committed migrations provide a reproducible database history as additional features introduce new tables and schema changes. ### 6. Tests and documentation Added factory, configuration, CSRF, route, registration, password, duplicate-account, login/logout, and redirect-security tests. Added local setup, migration, and testing instructions to `README.md`. Why: tests protect observable behavior and security boundaries. The documentation gives contributors a repeatable development workflow. ## Verification ```text 10 tests passed in 0.52s Flask discovered all expected routes. Alembic reported no pending model changes. Python compilation completed successfully. git diff --check completed successfully. ``` ## Phase 2 — Task Management ### Phase 2.1 — Task data model and migration Added an independently registered task Blueprint plus typed `Task` and `Subtask` models. Tasks include status, priority, due date, ownership, completion time, and reserved Phase 3 timing fields. Database constraints restrict status and priority values; owned subtasks cascade safely when their task is deleted. Progress is derived from subtask completion instead of stored redundantly. Why: database constraints protect invariants even outside web forms, while ownership keys prepare every query for user isolation. Keeping progress derived prevents stale percentages when subtasks change. Generated, reviewed, and applied the `Add tasks and subtasks` Alembic migration. The existing 10-test suite remained green after the schema change. ### Phase 2.2 — Task service layer Added service operations for owned task lookup, filtered lists, the seven-item Today view, creation, editing, deletion, subtask creation, and subtask toggling. Today excludes completed and future-dated tasks while accepting undated inbox items. Sorting consistently favors active, higher-priority, nearer-due work. Why: ownership filtering in the service boundary prevents routes and future APIs from accidentally exposing another user's records. Centralized ordering and completion timestamps keep behavior consistent across every interface. Next: add validated forms and authenticated routes that use these services. ### Phase 2.3 — Forms and authenticated routes Added validated task and subtask forms plus authenticated routes for task lists, Today, create, detail, edit, delete, subtask creation, and subtask toggling. Invalid filters return `400`; missing or foreign-owned records return `404`; all mutations require POST and CSRF validation. Why: forms constrain input at the HTTP boundary, while the service layer remains the ownership authority. Separating read routes from POST mutations prevents links or crawlers from changing data and gives CSRF protection complete coverage. Next: build the templates and visual components for filtering, priorities, subtasks, and progress. ### Phase 2.4 — Task-management interface Added the full task list, Today view, create/edit form, and task detail templates. The interface includes status filters, calm priority badges, accessible native progress bars, subtask toggles, responsive layouts, empty states, and a deliberately disclosed delete action. Authenticated navigation now links to Today and Tasks. Why: Today reduces visible choices to seven without hiding the full workspace. Native progress elements retain semantic meaning for assistive technology. Delete is available but visually separated to reduce accidental destructive actions, while empty states give a low-pressure next action. Next: cover Phase 2 behavior and ownership boundaries with automated tests. ### Phase 2.5 — Automated coverage Added eight Phase 2 tests covering authentication gates, complete task CRUD, status filtering, invalid filters, the seven-item Today limit, future/completed hiding, cross-user isolation, subtask-derived progress, and whitespace-only title rejection. The shared database fixture now always establishes an application context. Why: these tests protect the feature's behavioral and security boundaries. In particular, the ownership test proves that another user receives `404` and cannot delete the record, while the Today test validates the query result and not merely page text. Verification at this step: `18 passed in 1.51s`. Next: run compilation, migration consistency, route discovery, whitespace, and full regression checks. ### Phase 2.6 — Final verification Completed the full regression suite, Alembic model comparison, route discovery, Python compilation, dependency consistency check, trailing-whitespace scan, and patch-format validation. Why: a feature is complete only when its behavior, schema, dependencies, and integration with earlier work agree. Running the Phase 1 suite alongside the new tests proves that task management did not regress authentication or the application factory. Final results: ```text 18 tests passed in 1.50s Alembic detected no pending model operations. Flask discovered all expected authentication, core, and task routes. Python compilation completed successfully. No broken Python requirements were found. Whitespace and git diff checks completed successfully. ``` ## Phase 3 — ADHD-Specific Features ### Phase 3.1 — Focus domain Added validated focus settings for an optional countdown, helpful context, and up to 12 comma-separated time blocks. Service operations configure focus without resetting unchanged completed blocks, toggle individual blocks, complete tasks, and calculate a forgiving streak with one internal missed-day freeze. Why: focus behavior belongs in reusable services so the web interface does not become the only possible client. The streak is derived from completion history rather than stored counters, avoiding drift and making the freeze rule explainable. The current day is never counted as missed while it is still underway. Next: expose these operations through authenticated, ownership-safe Focus Mode routes. ### Phase 3.2 — Focus Mode routes Added Focus Mode routes for the highest-ranked Today task or an explicitly selected owned task. Added protected mutations for focus settings, chunk toggles, and completion. Focus rendering includes the derived streak and moves to the next eligible task after completion. Why: the default route surfaces exactly one decision, while explicit task focus preserves user control. All mutations remain POST-only with CSRF and ownership checks. Completing a task redirects to the next focus item instead of returning users to a visually dense list. Next: build the focused screen, countdown controls, chunk plan, context cue, and positive completion feedback. ### Phase 3.3 — Focus interface and feedback Added a single-task Focus Mode screen with an optional persistent visual countdown, time-block checklist, visible context cue, forgiving streak card, and focused completion action. Added navigation from task cards and details. Completion moves to the next task and renders a calm celebration with an optional synthesized chime. The timer uses local browser storage to survive reloads, supports start/pause/reset, announces completion to assistive technology, and continues in-page if storage is unavailable. Chime failures are ignored so browser audio policy can never block task completion. Why: only one task is rendered as the primary action. Timer state is local interaction state rather than high-frequency database traffic. Context is explicitly a visible placeholder with no hidden location tracking. Feedback remains optional and respects the user's existing chime preference. Next: add accessible styling and automated tests for focus selection, settings, chunks, streak rules, completion, and ownership. ### Phase 3.4 — Automated coverage Added eight tests for single-task focus selection, focus configuration, invalid chunk plans, exact block toggling, completion and advancement, ownership isolation, an internal rest-day freeze, and an unfinished current day. The full suite now contains 26 passing tests. Why: deterministic dates make the emotional contract of the forgiving streak testable. Ownership tests cover every new mutation, while the advancement assertion distinguishes the completed-task confirmation from the next primary focus heading. Verification at this step: `26 passed in 2.53s`. Next: run JavaScript syntax, Python compilation, migration, dependency, routing, formatting, and full regression checks. ### Phase 3.5 — Final verification Completed the full regression suite, JavaScript syntax validation, Alembic model comparison, Flask route discovery, Python compilation, dependency consistency, line-length and trailing-whitespace scans, and patch-format validation. Added the same positive completion action to task details so feedback is not restricted to users who enter Focus Mode first. Why: Phase 3 combines client-side state with server-side ownership rules, so both language runtimes and their integration points require validation. Reusing the protected completion route keeps completion semantics, streak history, and feedback consistent from every entry point. Final results: ```text 26 tests passed in 2.54s JavaScript syntax validation completed successfully. Alembic detected no pending model operations. Flask discovered all expected routes. Python compilation completed successfully. No broken Python requirements were found. Line-length, whitespace, and git diff checks completed successfully. ``` ## Phase 4 — Import, Export & Backup ### Phase 4.1 — Reversible completed-task clearing Added soft-clear timestamps and user-scoped batch identifiers to tasks. Normal task queries now exclude cleared records, while streak history intentionally retains their completions. Added services to clear all currently completed tasks in one batch and restore only that user's matching batch. Upload requests are globally limited to 2 MiB. Why: “clear” should not mean immediate permanent deletion. Batch identifiers create a precise undo target without exposing sequential database IDs, and ownership is enforced again during recovery. Retaining completion timestamps preserves the user's forgiving streak after tidying the task list. Next: migrate the schema, then implement bounded and atomic import/export services. The `Add reversible task clearing` migration was generated, reviewed, applied, and followed by a green 26-test regression run. ### Phase 4.2 — Validated transfer services Added UTF-8 Markdown/plain-text parsing for headings, bullets, numbered items, and checklists, with a 200-task and 200-character-title limit. Bulk creation commits once after the entire document validates. Added a versioned JSON backup format containing active tasks, subtasks, focus setup, completion history, and user preferences—but no password hash, email address, database IDs, or ownership fields. Restore validates the complete document, bounds nested collections and values, assigns every task to the current user, and supports merge or replace behavior in one transaction. Why: uploads are untrusted input. Full validation before mutation prevents partial imports, explicit schema versions allow safe evolution, and omitting identity/security fields prevents a backup from changing account ownership or credentials. Atomic restore ensures failure leaves existing data intact. Next: add CSRF-protected forms and authenticated routes for import, export, clear, and undo. ### Phase 4.3 — Protected transfer routes Added an authenticated data-transfer hub plus separate routes for Markdown import, JSON download, JSON restore, clearing completed tasks, and undoing a specific clear batch. All uploads and state changes require validated Flask-WTF forms and CSRF; downloads contain only the current user's active data. Undo route identifiers use Flask's UUID converter and are rechecked against the current user in the service. Why: separating each mutation keeps permissions and error handling explicit. Downloads remain GET because they do not change server state; import, replace, clear, and undo remain POST-only. File names and ownership values from uploads are never used to select database records. Next: build the instructional transfer interface and visible undo recovery control. ### Phase 4.4 — Transfer and recovery interface Added a responsive Data & Backups hub with the supported Markdown format, explicit limits, JSON privacy scope, merge/replace distinction, confirmation controls, and a disclosed completed-task clear action. After clearing, a prominent recovery banner explains that tasks are hidden and provides a CSRF-protected undo button. Authenticated navigation now links to the hub. Why: import and replacement have different risk profiles, so their consequences are explained next to the action. File inputs advertise accepted formats without treating browser hints as security validation. Clear remains visually available but not effortless to trigger accidentally, and recovery is surfaced immediately. Next: test parser limits, atomic restore, privacy, ownership, replacement, clear/undo, and upload routes. ### Phase 4.5 — Automated coverage Added tests for Markdown syntax, encoding and size limits, confirmed upload behavior, versioned/private JSON output, nested backup round trips, JSON upload ownership, invalid-replace atomicity, user-isolated replacement, streak-preserving soft clear, foreign-user undo rejection, and browser-route recovery. During the quality pass, Markdown database failures gained explicit rollback handling, and backup validation now rejects inconsistent task states such as completed tasks without completion timestamps. Why: rollback behavior keeps the SQLAlchemy session usable after failure, while state/date consistency preserves streak and status meaning after restore. Testing the upload route proves ownership is assigned from the active session rather than merely working in direct service calls. Next: run the complete regression, migration, route, dependency, compilation, JavaScript, line-length, whitespace, and patch checks. ### Phase 4.6 — Final verification The first full verification exposed a misplaced date-validation block in the JSON serializer. Four transfer tests failed while 33 passed. The validation was moved into `_validate_task`, the focused 11-test transfer suite passed, and then the complete verification was repeated successfully. Why: structurally similar dictionary-return blocks made a broad patch match the wrong function. Running focused tests after the correction provided fast evidence for the affected subsystem before the final regression. Recording this makes the progress log reflect corrective work as well as completed features. Final results: ```text 37 tests passed in 3.79s All 11 focused transfer tests passed. Alembic detected no pending model operations. Flask discovered all expected routes. Python and JavaScript syntax validation completed successfully. No broken Python requirements were found. Line-length, whitespace, and git diff checks completed successfully. ``` ## Phase 5 — Settings & Personalization ### Phase 5.1 — Settings domain and route Added an independent settings Blueprint, validated preference form, and service for theme, readability typography, completion chime, and future reminder frequency. The service validates choices again outside the form and commits all preferences together. Browser reminders remain disabled behind a configuration flag. Why: settings belong to their own feature boundary rather than authentication or task routes. Form validation supports the current web UI, while service-level validation protects future callers such as an API. One commit prevents partially saved preference combinations. Next: apply preferences during server rendering and build the accessible settings interface. ### Phase 5.2 — Personalized rendering and interface Added an accessible settings page grouped into appearance, feedback, and reminders. Every page now receives server-rendered `data-theme` and `data-dyslexia` attributes from the authenticated user, with anonymous users defaulting to system theme and standard typography. CSS supports explicit light/dark overrides, automatic system dark mode, and a local readability-focused font stack with increased spacing. Why: server-rendered preferences work without JavaScript and prevent a flash of the wrong theme. The readability option uses installed system fonts rather than sending reading preferences to an external font provider. Native checkboxes retain keyboard and assistive-technology behavior while receiving switch semantics. The reminder section clearly states that no service is active, no notifications are scheduled, and no permission has been requested. Next: add a capability-only browser check that never requests permission or creates a subscription. ### Phase 5.3 — Browser-reminder placeholder Added a user-triggered JavaScript capability check for the Notification, Service Worker, and Push APIs. It reports whether the browser exposes the necessary foundations while reiterating that reminders remain disabled. It does not call `requestPermission`, register a service worker, create a push subscription, or change the saved notification preference. Why: notification permission prompts are disruptive and should occur only when a working feature can explain immediate value. Capability detection lets the future integration expose environmental readiness without creating side effects or false expectations. Next: test preference persistence, user isolation, theme markup, sound behavior, invalid input, authentication, and reminder-placeholder guarantees. ### Phase 5.4 — Automated coverage Added eight tests for authentication, inactive-reminder disclosure, preference persistence across pages, invalid-select rejection, cross-user isolation, Focus Mode chime suppression, service-level validation, and the absence of permission/subscription calls in JavaScript. The full suite now contains 45 passing tests. Why: settings must affect consuming pages, not merely save successfully. The tests inspect global HTML attributes and Focus Mode output after persistence. The source-level negative assertion makes the placeholder's no-permission promise enforceable during future development. Next: run regression, migration, routing, dependency, Python/JavaScript syntax, line-length, whitespace, and patch checks. ### Phase 5.5 — Final verification Completed the full regression suite, Alembic model comparison, Flask route discovery, Python compilation, JavaScript syntax validation, dependency consistency, line-length and trailing-whitespace scans, and patch-format validation. No migration was required because Phase 1 already established all preference columns. Why: personalization affects every rendered page and Focus Mode audio behavior, so the full suite is necessary to detect cross-feature regressions. Confirming zero schema drift proves the settings implementation matches the existing database contract. Final results: ```text 45 tests passed in 4.62s Alembic detected no pending model operations. Flask discovered all expected routes, including settings. Python and JavaScript syntax validation completed successfully. No broken Python requirements were found. Line-length, whitespace, and git diff checks completed successfully. ``` ## Phase 6 — Admin Dashboard Placeholder ### Phase 6.1 — Role and feature boundary Added a database role constraint for `user`, `admin`, `viewer`, and `coach`; a reusable allow-list authorization decorator; a disabled-by-default `FEATURE_ADMIN` environment flag; and safe application-factory overrides for isolated feature testing. The admin Blueprint is imported and registered only when enabled. Added an admin-only placeholder dashboard and a calm `403` page. The dashboard explicitly labels user management and analytics as unimplemented and queries no user or analytics data. Admin navigation appears only when both the feature flag and role allow it. Why: a disabled route is safer than a visible placeholder protected only by convention. Role checks use an explicit allow-list, the database rejects unknown roles, and conditional imports keep the future feature absent from the route map when disabled. Factory overrides allow testing without changing deployment defaults. Next: enforce the viewer role as read-only across every task mutation, migrate the role constraint, and test all role boundaries. ### Phase 6.2 — Viewer read-only enforcement Applied the shared task-editor allow-list (`user`, `admin`, `coach`) to every task-changing route: create, edit, delete, completion, subtasks, focus configuration, chunk toggles, both imports, clear, and undo. Viewer accounts retain authenticated access to lists, details, Focus Mode, export, and personal settings but receive `403` before mutation logic runs. Why: defining a viewer role without enforcing read-only behavior would create a misleading security promise. Centralizing the allow-list prevents individual routes from inventing different role semantics, while ownership checks continue to restrict editors to their own tasks. Next: generate the role-constraint migration and test disabled, anonymous, non-admin, admin, viewer, and coach behavior. The `Constrain user roles` migration was generated, reviewed, applied, and followed by a green 45-test regression run. ### Phase 6.3 — Authorization coverage Added tests for default route absence, anonymous login redirection, `403` responses for user/viewer/coach roles, admin placeholder access, database rejection of unknown roles, viewer read access, all twelve viewer mutation denials, and retained coach write access to owned tasks. Why: checking only the admin landing page would leave lateral privilege gaps in task operations. Enumerating every mutation proves the viewer contract across uploads, recovery, focus actions, subtasks, and CRUD. The database test verifies defense in depth beneath route authorization. Next: run the focused role suite, then the complete regression and architecture checks. ### Phase 6.4 — Final verification and scope boundary Completed the focused authorization suite and full regression, Alembic model comparison, default and enabled route discovery, Python compilation, JavaScript syntax validation, dependency consistency, line-length and whitespace scans, and patch-format validation. User-management and analytics operations remain deliberately unimplemented. The design labels both as future work, and adding role assignment, account suspension, or analytics collection would require explicit product requirements, audit logging, privacy rules, and recovery policy. Phase 6 therefore completes the safe placeholder and authorization boundary without creating speculative administrative power. Final results: ```text 54 tests passed in 5.49s All 9 focused authorization tests passed. The default route map contains no admin endpoint. The enabled route map contains the guarded /admin/ placeholder. Alembic detected no pending model operations. Python and JavaScript syntax validation completed successfully. No broken Python requirements were found. Line-length, whitespace, and git diff checks completed successfully. ``` ## Next Work Phase 7 will focus on evidence-driven polish: responsive behavior, reduced-motion verification, accessibility auditing, performance checks, and deployment/documentation hardening. Future admin operations should remain separate until their requirements are approved. ## Local Operational Setup - 2026-08-08: Promoted the sole local development account from `user` to `admin` and enabled `FEATURE_ADMIN=true` in the ignored local `.env` file. Verified through the Flask CLI route map that `/admin/` is registered. No account identifier or credential is recorded in this log.