# TIEASY

Mobile-first task management web app built with CodeIgniter 4, PHP 8.2+, MySQL 8+, vanilla JavaScript/AJAX, and Chart.js.

The finalized Figma wireframe is the UI authority. The Master Build Specification is the functional/technical authority.

The UI stays a **narrow mobile-app shell on desktop**. It is not a wide desktop dashboard.

**V1 is complete.** Production install steps live in [DEPLOYMENT.md](DEPLOYMENT.md).

## V1 feature matrix

| Area | Status |
| --- | --- |
| Authentication (email OTP) | Complete |
| Persistent login until Logout | Complete |
| Profile onboarding | Complete |
| Categories | Complete |
| Tasks | Complete |
| Recurrence | Complete |
| Dashboard | Complete |
| Search | Complete |
| Analytics | Complete |
| Notifications (in-app) | Complete |
| Support | Complete |
| Settings V1 | Complete |

## Deferred (not V1 defects)

Voice/mic, AI auto-priority/category, analytics insight carousel, chart click-through, Compact/Full mode, move completed to bottom, reminder preference toggle, Quick Guide, Delete All / Delete Account, drag/drop, multi-select, long-press, swipe restore, fuzzy search, support admin portal, push/FCM/SMS notifications.

## Requirements

- PHP 8.2+ with extensions: `intl`, `mbstring`, `mysqli`, `openssl` (plus bundled `json` / `ctype`). `curl` and `fileinfo` are recommended.
- Composer
- MySQL 8+
- A writable `writable/` directory

## Local setup

```bash
composer install
cp env .env
php spark key:generate
```

Edit `.env`:

- `app.baseURL` — include trailing slash, matching the host/port you will use
- `app.appTimezone` — IANA timezone used by Dashboard, recurrence, and notifications (`UTC` by default; set `Asia/Kolkata` only if that is the intended product timezone)
- `database.default.*` — MySQL host (`127.0.0.1` avoids socket permission issues on some systems), database, user, password
- `encryption.key` — generated by `php spark key:generate`
- Email OTP: keep `email.protocol = mailbox` for local/dev (writes `writable/mailbox/`). Production must use `email.protocol = smtp`. Never put a real SMTP password in the tracked `env` file.

Do **not** set `session.savePath`. Sessions are stored in `writable/session` (never under `public/`).

Create the database (example):

```bash
mysql -u root -p -e "CREATE DATABASE tieasy CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
```

Run migrations and seed **support categories only** (no Home/Office task categories):

```bash
php spark migrate
php spark db:seed DatabaseSeeder
```

Start the app (uncommon port, bound for local preview):

```bash
php spark serve --host 0.0.0.0 --port 43123
```

Open http://127.0.0.1:43123/ — unauthenticated visits land on Sign In. After OTP login the session lasts until **Logout**; a persistent HttpOnly cookie restores the session if the normal CI4 session expired.

### Email OTP (SMTP)

`.env` keys (CI4 Email config):

```
email.fromEmail
email.fromName
email.protocol          # mailbox (local) or smtp (production)
email.SMTPHost
email.SMTPUser
email.SMTPPass
email.SMTPPort
email.SMTPCrypto        # tls, ssl, or empty
```

### Tests

PHPUnit always uses the `tests` database group (`database.tests.*` in `.env`), never production.

```bash
mysql -h 127.0.0.1 -u YOUR_TEST_USER -p -e "CREATE DATABASE IF NOT EXISTS tieasy_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
composer test
```

### Production

See [DEPLOYMENT.md](DEPLOYMENT.md) for VPS layout, Nginx, HTTPS, cron, SMTP, backups, and updates. Document root is `public/`. Set `CI_ENVIRONMENT = production`. Persistent login requires HTTPS.

### Scheduled jobs

Run occurrence extension **before** due-notification generation (daily cron, application timezone). Timed reminders are created on the next daily run after they become due.

```bash
php spark tasks:generate-occurrences
php spark tasks:notifications
```

Example crontab (use absolute paths on the VPS; see DEPLOYMENT.md):

```
15 0 * * * cd /path/to/tieasy && php spark tasks:generate-occurrences && php spark tasks:notifications
```

## Phase 6 scope

Recurring tasks: Daily (every N days), Weekly (every N weeks + weekdays), and Monthly (day-of-month or first/second/third/fourth/last weekday). One parent `tasks` row, one `task_recurrence_rules` row, and generated `task_occurrences` about 90 days ahead.

- Recurring tasks require Due Date as the series start. Due Time is optional and is copied onto each occurrence (never defaulted to 23:59).
- Months that do not contain the requested day (for example 31 February) are **skipped**.
- `php spark tasks:generate-occurrences` extends each active series to a ~90-day future horizon without duplicating dates or recreating intentionally deleted occurrences.
- Edit regenerates future pending occurrences and keeps completed history. Non-recurring ↔ recurring conversion is supported.
- Delete this occurrence tombstones that date. Delete this and future tombstones the cutoff date onward and ends the series there.

## Phase 7 scope

Dashboard and priority lists read live `task_occurrences` (joined to the signed-in user's non-deleted `tasks` and `categories`). Recurring and one-off tasks use the same occurrence rows.

- Default filters on first load: **Today**, **Pending**, **All Categories**.
- Date filters: All Dates (includes undated), Today, Tomorrow, This Week (Monday–Sunday), Next 7 Days (today through today+6), This Month, Custom From/To (inclusive). Date-specific filters exclude undated tasks.
- Status is the occurrence status. Deleted occurrences (`is_deleted = 1`) never appear.
- Category filter lists only the current user's active categories. Combined Date + Status + Category filtering is server-side.
- Row tap opens `/tasks/{taskId}/edit`. Swipe (with Complete/Delete fallback buttons) completes or deletes the **occurrence**. Recurring delete offers this occurrence vs this and future. Non-recurring delete confirms and soft-deletes the task.
- Canonical mutation routes: `POST /tasks/occurrences/{occurrenceId}/complete`, `/delete`, `/delete-future`, and `POST /tasks/{taskId}/delete` for non-recurring tasks. There is no `/tasks/{id}/delete-future`.
- Search field remains visual only (Phase 8). Analytics, notifications, support, AI, voice, and drag/drop are not in this phase.

## Phase 8 scope

Title search from the Dashboard **Search Tasks** field. Results open `/tasks/search` as a mixed-priority list (not a single quadrant).

- Search is case-insensitive, trimmed, partial `LIKE` on **task title** only, scoped to the signed-in user’s occurrences.
- Active Date / Status / Category filters (including Custom From/To) apply to search results. Empty or whitespace queries do **not** list every task; the Search screen shows `Enter something to search.`
- Rows reuse the Phase 7 swipe/complete/delete behavior and show Priority because results can mix quadrants. Matching title text is highlighted with escaped `<mark>` tags.
- Edit Back/Save can return to `/tasks/search` with the allowlisted `q` + filter query. Analytics, notifications, support, and later phases are not in this slice.

## Phase 9 scope

Authenticated Analytics at `/analytics` using Chart.js. Counts are occurrence-level (not collapsed to master tasks), scoped to the signed-in user’s non-deleted tasks, categories, and occurrences.

- **Date scope (V1):** all currently retained non-deleted occurrences. Analytics does **not** inherit the Dashboard Today filter and has no date picker. Undated occurrences (`occurrence_date IS NULL`) still count.
- **Completed vs Pending:** doughnut of `pending` vs `completed` with counts and percentages. Unexpected statuses are excluded (not treated as Pending).
- **Tasks by Priority:** all four `Config\Tieasy::$priorities` labels, including zero-count quadrants. Recurring occurrences each count once.
- **Tasks by Category:** pending occurrences only. Categories with zero pending occurrences are omitted. Deleted categories are excluded; moved tasks count under their current category.
- Percentages use `count / total * 100` with whole numbers when exact and one decimal otherwise (`50%`, `33.3%`). Independently rounded slices are not required to sum to 100.0%. Empty users see `No task data yet.` with no sample chart values.
- Chart.js renders server-computed JSON from an `application/json` script tag (XSS-safe encoding). There is no public JSON analytics endpoint, no insight carousel, and no pie-click drill-down.

## Phase 10 scope

In-app due notifications stored in `notifications`. No Firebase/FCM, browser push, SMS, or email reminders.

- **Priority 4** display text is `Not Important & Not Urgent` from `Config\Tieasy::$priorities` only. Dashboard, Search, Analytics, and Create/Edit Task consume that map.
- Generator `php spark tasks:notifications` creates at most one `kind=due` row per `(user_id, occurrence_id)` (unique index). Read rows are not recreated. Overdue pending occurrences are picked up on the next run.
- Date + time: notify at or after that local datetime. Date-only: notify on that calendar day (no hidden 23:59). Undated occurrences never notify.
- Recurring occurrences notify independently. Completed or tombstoned occurrences do not generate. Completing after a notification exists leaves the historical row.
- Authenticated `/notifications` list (newest first), header bell + unread badge, hamburger Notifications, `POST` mark-one / mark-all / open. Open marks read then goes to `/tasks/{id}/edit` with `list_return=notifications`. Missing tasks fail gracefully.
- Timezone: `Config\App::$appTimezone` via `App\Support\AppClock` (default `UTC`).

## Phase 11 scope

Authenticated Contact Support at `/support`. Submissions snapshot Name/Email/Phone onto `support_requests` and never write back to the user profile.

- **How can we help?** loads active rows from `support_categories` (`sort_order`, then `id`). Seeded titles stay: Technical Issue, Account/Login Issue, Task Management, Notification Issue, Feedback/Suggestion, Other. Arbitrary or inactive IDs are rejected.
- Saved Profile Name and Phone are prefilled read-only. Missing Name is editable and required. Missing Phone is editable and optional. Email is always the verified account email, read-only, and never taken from POST.
- Valid POST inserts one request (`status=open`) and redirects (PRG) with `Your support request has been submitted.`
- Settings V1 is unchanged: editable Profile Name, Age Group, Gender, Occupation, Phone; read-only Email. No Compact/Full, completed-to-bottom, reminder, Quick Guide, theme, notification preferences, or Delete Account.
- No admin ticket inbox, chat, attachments, or support-team email.

## Phase 12 scope

Final audit and VPS production readiness. No new product features.

- Timezone helper `App\Support\AppClock` shared by Dashboard, recurrence, and notifications.
- Production refuses mailbox OTP delivery and forces Secure cookies.
- OTP and persistent-login hashing require `encryption.key` (no static pepper fallback).
- Cron commands take a non-blocking lock. Daily cadence is unchanged; timed reminders wait for the next run.
- Deployment: [DEPLOYMENT.md](DEPLOYMENT.md) and `deploy/nginx-tieasy.conf`.

## Phase 5 scope

Create Task and Edit Task for non-recurring tasks, plus the shared form used by Phase 6 recurrence.

- Title is required (trimmed, max 255 characters).
- Priority is required and must be 1–4 using the configured labels.
- Category is required and must be an active category owned by the signed-in user.
- Due Date is optional and may be `NULL`. A blank due date is **not** defaulted to today.
- Due Time is optional. If Due Date is blank, Due Time is ignored/cleared server-side even if the browser submitted a time.
- Creating a task also creates exactly one `task_occurrences` row in the same transaction (`pending`). Editing updates that same occurrence and does not reset a completed status.

## Phase 4 scope

Category Manager: user-owned list/add/edit/delete, including move-or-delete-all when a category has tasks. No task authoring in this phase.

## Phase 3 scope

Profile onboarding and Settings profile editing. Categories, tasks, and later features are not in this phase.

## Phase 2 scope

Email OTP sign-in, resend/cooldown/rate limits, persistent login until Logout, and post-OTP routing to Profile onboarding or Dashboard. Profile field save is Phase 3.

## Phase 1 scope

Foundation only: project structure, config, migrations, models, AuthFilter, guest/mobile layouts, routes, service/command shells. Email OTP and persistent login behavior ship in Phase 2.
