32 KiB
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 7 — Polish & Testing
- Status: Complete — automated scope
- Last updated: 2026-08-08
- Next milestone: Manual release audit on target browsers and assistive technology
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
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:
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:
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:
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:
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:
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.
Phase 7 — Polish & Testing
Phase 7.1 — Audit baseline and interaction hardening
Reviewed the application against WCAG 2.2 reflow, focus, target-size, motion, labeling, and page-structure criteria. The environment provides Node but no rendered-browser engine, Lighthouse, or pa11y, so this phase can provide automated structural evidence but not claim formal WCAG certification. Manual browser and assistive-technology review remains required before public release.
Expanded navigation, filter, task-focus, disclosure, and switch targets to at least 44 CSS pixels where practical—exceeding the WCAG 2.2 AA 24-pixel minimum. Existing 3-pixel focus outlines, 320-pixel reflow breakpoint, skip link, native labels, live regions, and reduced-motion override were retained.
Replaced the countdown's JavaScript-written inline width with a native <progress> element. This preserves semantics and enables a strict production Content Security Policy without allowing inline styles.
Why: accessibility claims should match available evidence. Structural automation catches regressions, while documenting the lack of a browser audit prevents a false certification. Larger targets and native controls reduce motor and assistive-technology friction.
Next: bound large task lists with pagination and add an index for the primary visibility/status/due-date query path.
Phase 7.2 — Bounded task-list performance
Added server-side task pagination with a hard maximum of 50 records per page, validated page parameters, preserved status filters, semantic previous/next navigation, and out-of-range handling. Added a composite database index over task owner, cleared visibility, status, and due date—the primary columns used by full-list and Today queries.
Why: the previous full list loaded every matching task and all subtasks, causing response size and memory use to grow without bound. Pagination caps database hydration and rendered HTML while retaining a complete navigable workspace. The composite index narrows user-visible task scans as data grows.
Next: generate the index migration, then add production response headers, friendly errors, and deployment configuration.
The Index visible task queries migration was generated, reviewed, applied, and followed by a green 54-test regression run.
Phase 7.3 — Production hardening
Added a strict Content Security Policy, MIME sniffing prevention, clickjacking protection, referrer and browser-capability restrictions, cross-origin isolation headers, no-store caching for dynamic responses, one-hour static caching, and HTTPS-only HSTS in production. Added optional trusted-host configuration and friendly 403, 404, 413, and 500 pages; server errors roll back the active database session.
Added Gunicorn 26, a bounded worker/thread/timeout configuration, and environment examples. Production continues to require an external SECRET_KEY; HSTS is emitted only for secure requests.
Why: response policies constrain the impact of injection and framing attacks, while avoiding HSTS over local HTTP prevents development lockout. A dedicated WSGI server replaces Flask's development server in production, and trusted-host configuration is explicit rather than guessing proxy topology.
Next: add automated structural accessibility, responsive-contract, pagination, header, error, and production-startup tests.
Phase 7.4 — Automated audit evidence
Added structural audits across primary authenticated pages for language, titles, landmarks, skip navigation, headings, unique IDs, named buttons, and labeled controls. Added CSS contract tests for reflow, 44-pixel targets, focus visibility, reduced motion, forced colors, and key WCAG contrast ratios. The audit found duplicate confirmation/submit IDs on the transfer page; each control now has a unique label target. Reusable action forms also avoid repeated CSRF element IDs.
Added performance tests for the 50-item rendering ceiling, invalid/out-of-range pages, a three-query pagination budget, and index presence. Added production tests for CSP and security headers, static/dynamic cache policy, HTTPS-only HSTS, trusted-host rejection, friendly errors, and upload limits.
Why: these tests convert important non-functional requirements into regression contracts. The initial query-count result included an unrelated expired-user refresh; capturing the ID before measurement isolated the pagination path at count, records, and subtasks.
Added ACCESSIBILITY.md with the evidence boundary and required manual matrix, plus DEPLOYMENT.md covering environment, migrations, Gunicorn, reverse proxies, TLS, backups, health checks, logging, and rollback.
Next: run all focused audits, smoke-test Gunicorn over HTTP, then execute the complete verification matrix.
Phase 7.5 — Live deployment and final verification
Started Gunicorn 26 with the production entry point, a temporary database, one worker, and a localhost-only bind. A real HTTP request to /health returned 200 and the expected JSON plus CSP, MIME, framing, referrer, permissions, cross-origin, and cache headers. Gunicorn then shut down cleanly and temporary databases were removed.
Completed the full regression, Alembic model comparison, admin-disabled and admin-enabled route discovery, Gunicorn configuration validation, Python compilation, JavaScript syntax validation, dependency consistency, line-length and whitespace scans, and patch-format validation.
Final results:
65 tests passed in 5.97s
All 11 focused polish tests passed.
Gunicorn 26 booted and served /health successfully.
Pagination remained within a three-query budget and 50-record ceiling.
Alembic detected no pending model operations.
Python, JavaScript, and Gunicorn configuration checks passed.
No broken Python requirements were found.
Line-length, whitespace, and git diff checks completed successfully.
Phase 7's automated scope is complete. Formal release sign-off still requires the manual browser, zoom, keyboard, forced-colors, and screen-reader matrix in ACCESSIBILITY.md; the current environment cannot execute those tools.
Next Work
Run the documented manual accessibility matrix on supported target platforms, deploy first to a TLS-enabled staging environment, verify trusted-host and proxy configuration there, rehearse database backup/restore, and record any findings before public release.
Local Operational Setup
- 2026-08-08: Promoted the sole local development account from
usertoadminand enabledFEATURE_ADMIN=truein the ignored local.envfile. Verified through the Flask CLI route map that/admin/is registered. No account identifier or credential is recorded in this log.