1Project README
1.1README.md
TaskBoard
TaskBoard
Internal Kanban-style task manager for a single team. Angular frontend, plain PHP API, MySQL database. Full product documentation lives in docs/.
Current build stage: all steps complete — Step 7.1 (deploy) artifacts and guide are ready (docs Part 12). The live cut-over to https://task.scoaladeweb.ro/ runs once production credentials are filled into api/config/config.php (copy from config.production.sample.php).
Layout
api/ — plain PHP (no framework) JSON API. Front controller at api/public/index.php.
frontend/ — Angular 20 single-page app.
docs/ — the written specification (Parts 1–11).
Requirements
- PHP 8.4 (MAMP:
/Applications/MAMP/bin/php/php8.4.17/bin/php); Node 22 + npm;
Angular CLI 20; MySQL (MAMP, port 8889).
First-time setup
- Create the API config from the sample (holds secrets, git-ignored):
``bash cp api/config/config.sample.php api/config/config.php `` The committed sample already has working MAMP defaults; edit if yours differ.
- Install frontend dependencies:
``bash npm install --prefix frontend ``
- Create the database and load schema + seed (MAMP MySQL on port 8889).
MYSQL below is MAMP's client; adjust the path if your MAMP differs: ``bash MYSQL="/Applications/MAMP/Library/bin/mysql80/bin/mysql --protocol=TCP -h127.0.0.1 -P8889 -uroot -proot" $MYSQL -e "CREATE DATABASE IF NOT EXISTS taskboard CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" $MYSQL taskboard < api/database/schema.sql $MYSQL taskboard < api/database/seed.sql ` The seed creates the admin office@pixeltime.ro with the initial password ChangeMe-TaskBoard-2026! and must_set_password = 1`, so that password is replaced on first login (docs Part 5). The seed is idempotent — safe to re-run.
Run it locally
Start the API (PHP built-in server on port 8000):
/Applications/MAMP/bin/php/php8.4.17/bin/php -S localhost:8000 -t api/public
Start the frontend (Angular dev server on port 4200) in a second terminal:
npm start --prefix frontend
Then open http://localhost:4200. The home page calls GET /api/health and shows whether the API is reachable.
If port 4200 is busy, Angular offers another port (e.g. 4300). Ports 4200 and 4300 (localhost and 127.0.0.1) are already in the API's CORS allowlist (api/config/config.php).
Verify
# Health (expect 200 + JSON {status:"ok", service:"taskboard-api", ...}):
curl http://localhost:8000/api/health
# Admin login (expect 200 + a token):
curl -X POST http://localhost:8000/api/auth/login -H "Content-Type: application/json" \
-d '{"email":"office@pixeltime.ro","password":"ChangeMe-TaskBoard-2026!"}'
# Protected endpoint with that token (200 with it, 401 without):
curl http://localhost:8000/api/me -H "Authorization: Bearer <TOKEN>"
The home page shows a green Connected badge; visiting any page while signed out redirects to /login.
Configuration
Server config (JWT lifetime in hours, SMTP placeholders, DB credentials, CORS) lives in api/config/config.php — never in the database or the repo. See api/config/config.sample.php for every field.
Notes
- No framework or Composer for the API: a small custom PSR-4 autoloader and
router keep the code readable (a teaching example). Nothing is ever hard-deleted (the golden rule, docs Part 6).
4People & Roles
4.1docs/02-people-and-roles.md
Part 2 — People & Roles
← Table of Contents · ← Part 1
TaskBoard has one team and two roles: admin and member. This part is the permissions map — who can do what, and just as importantly, what each role cannot do. Getting permissions right is what keeps the data trustworthy, so we spell them out precisely. The detailed per-area tables continue in Part 2a.
The roles at a glance
- Admin — one system-wide superuser (
office@pixeltime.ro). Runs the whole app: manages users, projects, and labels; can reassign, move, archive, and unarchive any task; can log time on any task; sees time reports; configures notification settings for the team. There is exactly one admin account, seeded into the database from the start.
- Member — a regular team member. Does their own work: creates tasks for themselves, comments anywhere, logs time and moves the board only for tasks currently assigned to them, and manages their own profile. Members see everything (nothing is hidden) but can only change their own things.
A guiding principle runs through the whole app: visibility is open, editing is scoped. Everyone can see all projects, tasks, comments, time logs, and the user list. What differs by role is what you are allowed to change.
Accounts & profile
| Action | Admin | Member |
| Create user accounts (generates password, emailed to user) | Yes | No |
| Deactivate / reactivate a user account | Yes | No |
| Edit own profile (full name, email) | Yes | Yes |
| Change own password | Yes | Yes |
| See the full list of users | Yes | Yes |
| Change another user's profile or password | No | No |
Notes:
- No one can delete a user; accounts are deactivated instead. A deactivated user cannot log in, and their tasks appear slightly transparent on the board so it is clear the person is inactive. Their data stays intact.
- Even the admin does not edit other people's profiles or reset their passwords directly; a user manages their own credentials.
Projects
| Action | Admin | Member |
| Create a project | Yes | No |
| Rename / edit a project | Yes | No |
| Archive / manage a project | Yes | No |
| View projects | Yes | Yes |
| Add tasks inside an existing project | Yes | Yes |
Projects are entirely the admin's domain to create and manage. Members live inside the projects the admin has set up.
Tasks
| Action | Admin | Member |
| Create a task | Yes | Yes |
| Choose the assignee when creating | Anyone | Only themselves |
| Reassign a task (change assignee) after creation | Yes | No |
| Edit a task's details (title, description, priority, label) | Any task | Own tasks (created by or assigned to them) |
| Move a task across board columns (status change) | Any task | Only tasks currently assigned to them |
| Archive a task | Any task | Only tasks they created |
| Unarchive a task | Any task | Only tasks they created |
| Delete a task | No | No |
Notes:
- The golden rule: tasks are never deleted. They are archived instead, which is reversible. There is no delete action for tasks anywhere, for anyone.
- Assignee is always required. A member creating a task assigns it to themselves. The admin may assign to anyone.
- Moving on the board: a member can drag only their own assigned tasks. The admin can drag anyone's task.
← Table of Contents · Part 2a — more permissions →
4.2docs/02a-roles-permissions.md
Part 2a — Roles: comments, time, labels, notifications
← Part 2
Continuation of the permissions map from Part 2: comments, time log, labels, notifications, the universal prohibitions, and the seeded admin.
Comments & mentions
| Action | Admin | Member |
| Add a comment to any task | Yes | Yes |
| Mention someone with @ | Yes | Yes |
| Edit own comment | Yes | Yes |
| Delete own comment | Yes | Yes |
| Edit someone else's comment | No | No |
| Delete someone else's comment | Yes | No |
Anyone can comment on any task (visibility is open). You manage only your own comments, except the admin, who may also delete others' comments (moderation) but never edit them.
Time log
| Action | Admin | Member |
| Log time on a task assigned to them | Yes | Yes |
| Log time on a task not assigned to them | Yes | No |
| Edit / delete own time entries | Yes | Yes |
| Edit / delete someone else's time entries | No | No |
| See time reports (by project / task / user) | Yes | No |
Notes:
- A member can log time on a task only while it is currently assigned to them. After a reassignment they can no longer add time, though hours they already logged remain counted in the task's total and history.
- The admin can log time on any task, regardless of assignee.
- Everyone edits or deletes only their own entries. Any such edit/deletion is recorded in the task's activity log, so history is never silently lost.
- Time reports are admin-only.
Labels
| Action | Admin | Member |
| Attach an existing label to a task | Yes | Yes (on tasks they can edit) |
| Create a new label while creating/editing a task | Yes | Yes (creator of the task) |
| Set / change a label's color | Yes | No |
| Manage the global label list | Yes | No |
Labels are global and shared across all projects. A task has at most one label. When creating a task, the admin or the task's creator may pick an existing label or add a new one. Label colors are controlled by the admin only.
Notifications & settings
| Action | Admin | Member |
| Receive in-app notifications | Yes | Yes |
| Configure which events send email | Yes (team-wide events) | Yes (limited, see below) |
| Configure team-wide notification settings | Yes | No |
Notes:
- In-app notifications are always delivered; only whether an event also sends an email is configurable.
- The admin can turn email on/off for: task assigned, task created by someone else, new comment, status change, time log added, and mention.
- A member also has email settings, but only for the projects where they have at least one task, or for tasks they follow (a task they have already commented on is considered followed).
- Exactly which events notify whom lives in Part 3 (Features) and Part 6 (Rules & States).
What no one can do
- Delete a task. (Archive instead.)
- Delete a user. (Deactivate instead.)
- Edit another person's comment, profile, password, or time entries.
- Members cannot create or manage projects, reassign tasks, move tasks that are not theirs, set label colors, or view time reports.
The seeded admin
The database ships with one admin account already created: email office@pixeltime.ro, with an initial password set at seed time, replaced after first use. This is the only account that exists before anyone else is invited; every other account is created by this admin.
← Part 2 · Table of Contents →
5Features
5.1docs/03-features.md
Part 3 — Features
← Table of Contents · ← Part 2
This part lists everything the app does, grouped by area. Because it is long, it is split into three linked files. Read them in order.
How to read the feature lists
TaskBoard has a single version — there is no "v1 then later." Everything below is what we build. To stay honest about scope, each area ends with an Out of scope note listing things we deliberately do not build, so no one expects them.
Each feature is written as a short capability ("the system lets X do Y"), in plain language. Detailed rules (allowed status transitions, exact permission edges, edge cases) live in Part 6 — Rules & States; this part is about what exists, not the fine print.
← Table of Contents · Next: Part 3a — Accounts, Projects, Tasks
5.2docs/03a-features-core.md
Part 3a — Features: Accounts, Projects, Tasks
← Part 3 index
Accounts & login
- Admin creates accounts. The admin adds a person by full name and email. The system generates a random password and emails it to that person. (There is no public sign-up.)
- Generated password by email. The new user receives their initial password by email and uses it to log in the first time.
- First-login password setup. On first login, the user is required to set their own password (minimum 8 characters) before continuing. From then on they log in with that password.
- Login. A user logs in with email + password and receives a JWT that the frontend sends with every request. The token's lifetime (in hours) is a backend setting the admin can change.
- Logout. The user can log out, which discards the token on the client.
- Change own password. Any logged-in user can change their own password (min. 8 characters) from their profile.
- Edit own profile. A user can edit their own full name and email.
- Deactivate / reactivate. The admin can deactivate an account (the user can no longer log in) and reactivate it later. Deactivated users are never deleted; their tasks and history remain, and their tasks appear slightly transparent on the board.
- User list. Everyone can see the list of users (needed for assignment and @mentions). Only the admin can create, deactivate, or reactivate accounts.
Out of scope: public self-registration; social/SSO login; password-strength rules beyond the 8-character minimum; admin resetting another user's password directly; deleting users.
Projects
- Create project. The admin creates a project with a name (and optional short description).
- Edit project. The admin can rename a project or edit its description.
- Archive project. The admin (only) can archive a project that is no longer active. Archiving a project automatically archives all of its tasks, and they disappear from the board until the project is reactivated. (Projects, like tasks, are archived rather than deleted.)
- View projects. Everyone sees all projects and can open any of them.
- Add tasks to a project. Any user can add tasks inside an existing project; only the admin creates the projects themselves.
Out of scope: members creating or editing projects; nested projects/sub-projects; per-project custom columns (columns are fixed team-wide); deleting projects.
Tasks
A task is the core unit of work. It always belongs to exactly one project and always has exactly one assignee.
- Create task. A user creates a task inside a project with: a title, an optional description (text), a priority, and at most one label. A member's new task is assigned to themselves; the admin may assign it to anyone. The creator becomes the task's reporter.
- Create another (Jira-style). The create-task dialog offers a "create another" action that keeps the shared fields already filled in — project and label — while clearing assignee, title, and description, so several related tasks can be added quickly.
- Reporter & assignee. Every task tracks its reporter (creator, fixed) and its assignee (current owner). Assignee is required at all times.
- Priority. Every task has a priority: Low, Medium, or High, each shown with a color.
- Label. A task may carry at most one label (global, colored). The task's creator or the admin can pick an existing label or add a new one; label colors are admin-managed.
- Status. Every task has a status: To Do, In Progress, or Done. Status changes by moving the task on the board.
- Edit task. The admin can edit any task's details (title, description, priority, label). A member can edit details of tasks they created or are assigned to.
- Reassign. Only the admin changes a task's assignee.
- Archive / unarchive. Tasks are archived instead of deleted. The admin can archive/unarchive any task; a member can archive/unarchive only tasks they created. Archived tasks leave the normal columns and appear in a hidden Archived column revealed by a button.
- Activity log. Every task keeps a full history (see Part 3b): created, comments, status changes, time logged/edited/deleted, archiving — each with who and when.
Out of scope: deleting tasks; due dates / deadlines / overdue; multiple assignees; multiple labels per task; subtasks and checklists; file attachments; moving a task to a different project after creation (a task's project is fixed once created).
← Part 3 index · Next: Part 3b — Board, Comments, Time Log
5.3docs/03b-features-work.md
Part 3b — Features: Board, Comments, Time Log
← Part 3 index · ← Part 3a
The board (Kanban)
- Global board. There is one shared board showing tasks across projects, not a separate board per project.
- Fixed columns. Three status columns, in order: To Do, In Progress, Done. A fourth Archived column is hidden by default and revealed with a button.
- Swimlanes per project. When the board shows more than one project at once, each project appears as its own horizontal section (swimlane) so tasks stay grouped by project. When a single project is in view, its tasks simply fill the three columns.
- Card contents. Each task shows as a card with its title, priority (color), label (color), assignee, and project. Cards for tasks owned by a deactivated user appear slightly transparent.
- Ordering. Within a column, tasks are ordered newest-first (by creation). There is no manual vertical reordering.
- Drag and drop to change status. Moving a card to another column changes the task's status. A member can drag only tasks currently assigned to them; the admin can drag any task (typically after filtering the board to one user).
- Filtering. The board can be filtered by one or more projects, one or more users, label, and priority. Filters combine, and can show one, several, or all projects/users.
- Archived view. A button reveals archived tasks so they can be reviewed and, where allowed, unarchived.
Out of scope: custom or per-project columns; manual card ordering within a column; WIP limits; multiple independent boards.
Comments & mentions
- Comment on any task. Any user can add a text comment to any task.
- @mentions. A comment can mention a user with
@ (for example @GB). Mentioned users are notified.
- Edit own comment. The author can edit their own comment.
- Delete comments. The author can delete their own comment. The admin can also delete anyone's comment (moderation) but cannot edit someone else's.
- Following via comments. Commenting on a task marks the commenter as following that task, which affects which notifications they may receive (see Part 3c).
Out of scope: attachments in comments; threaded replies; reactions/emojis; editing another person's comment.
Time log
- Log hours. A user records how many hours they spent on a task. An entry stores the number of hours, who logged it, and an automatic date. There is no description field on an entry.
- Multiple entries. A task can receive many time-log entries over time, building a history of effort.
- Who can log. Only the task's current assignee can log time on it, and only while they remain the assignee. The admin can log time on any task regardless of assignee (for example, time spent reviewing).
- After reassignment. If a task moves from user A to user B, A can no longer log time, but the hours A already logged remain counted in the task's total. B can now log time. This lets the task's total reflect everyone's effort.
- Edit / delete own entries. Everyone (admin included) can edit or delete only their own time entries. Every add, edit, and delete is written to the task's activity log, so nothing is silently lost.
Out of scope: logging against a deadline/estimate; start/stop timers; billing/rates; logging time you are not entitled to (non-assignee members).
Activity log (per task)
- Full history. Each task keeps a chronological record of what happened to it, with who and when:
- task created,
- comment added / edited / deleted,
- status changed (from → to),
- time logged / edited / deleted,
- task archived / unarchived,
- assignee changed (reassignment).
- Read-only. The activity log is a record; it is shown on the task detail view and is not editable.
Out of scope: editing or pruning history; exporting the activity log (not planned now).
← Part 3 index · Next: Part 3c — Search, Notifications, Reports, Settings
5.4docs/03c-features-support.md
Part 3c — Features: Search, Notifications, Reports, Settings
← Part 3 index · ← Part 3b
Search & filtering
- Filter the board. As covered in Part 3b, the board filters by project(s), user(s), label, and priority.
- Find tasks. A search lets a user find tasks by text in the title (and description), narrowing quickly to what they need.
- Combine with filters. Search results respect the active filters so a user can drill down (for example, High-priority tasks assigned to one person in one project).
Out of scope: full-text search across comments/history; saved searches; advanced query syntax.
Notifications
Notifications come in two channels: in-app (always delivered, shown in a dedicated section with an unread counter) and email (configurable — see Settings). The events differ by role.
Admin receives
- a task was assigned (to anyone),
- a task was created by someone else (not self-created),
- a new comment was added (on any task),
- a task's status was changed (any task),
- time was logged on a task,
- the admin was @mentioned.
Member receives
- a task was assigned to them,
- a new comment was added on a task assigned to them,
- a new comment was added on a task they follow (a task they have commented on before),
- they were @mentioned.
In-app behavior
- Unread state and counter. Notifications have a read/unread state, with a badge showing the unread count.
- Mark as read. A user can mark a single notification as read, and there is a "mark all as read" action.
- Open target. Selecting a notification opens the related task.
Out of scope: push/mobile notifications; digest emails; per-notification snooze; notifying on every field edit.
Time reports (admin only)
- Who sees them. Only the admin can view time reports.
- What they show. Totals of logged hours, viewable by project, by task, and by user (for example, total hours on a project, hours per user within a project, hours per task).
- Archived handling. Reports present two figures where relevant: the total excluding archived tasks, and — in parentheses — the total including archived tasks. This keeps active effort clear while still accounting for archived work.
Out of scope: exporting reports (CSV/PDF); charts/graphs (numbers first); date-range filtering (not planned now); members seeing reports.
Settings
User profile (all users)
- Edit own full name and email.
- Change own password (min. 8 characters).
- Manage own email-notification preferences (members: limited to projects where they have a task, and tasks they follow).
Admin settings (admin only)
- User management. Create accounts, deactivate/reactivate users.
- Label management. Manage the global label list and set label colors.
- Project management. Create, edit, and archive projects.
- Email notification settings. Turn email on/off per event type for the team: task assigned, task created by someone else, new comment, status change, time log added, mention. In-app notifications are always on; only email is configurable.
- Token lifetime. The JWT expiry (in hours) is a backend configuration the admin can change.
Out of scope: team/workspace management (there is one team); custom roles/permissions; theming.
← Part 3 index · Next: Part 4 — Data Model (to be written)
6Data Model
6.1docs/04-data-model.md
Part 4 — Data Model
← Table of Contents · ← Part 3c
What the app remembers and how the pieces relate, in plain language. The table-by-table field lists are in Part 4a. The database is MySQL, named taskboard, no table prefix. Exact column types and indexes are refined in Part 7; here we focus on what is stored and how it connects.
The things we store, in plain words
- A user is a person who can log in. Full name, email, hashed password, a role (admin or member), and an active/inactive flag. One user is the admin.
- A project is a container the admin creates. A name, an optional description, and an archived flag. A project has many tasks.
- A task belongs to exactly one project. Title, optional description, priority, status, an archived flag, and timestamps. It always points to one reporter (creator) and one assignee (currently responsible), and to at most one label.
- A label is a global, colored tag shared across projects. A task has at most one label; a label can be on many tasks.
- A comment belongs to one task and one author. Text + timestamps. A task has many comments.
- A mention links a comment to a user who was @mentioned in it; used to drive notifications.
- A time log entry belongs to one task and one user. Hours + the date logged. Totals feed the admin's reports.
- An activity log entry belongs to one task: what happened, which user did it, and when.
- A notification belongs to one recipient. A type, a reference to the related task, a read/unread flag, and a timestamp.
- Notification settings record, per user, which event types also send email (in-app is always on).
How they relate
- A project has many tasks; each task belongs to one project. (Archiving a project archives its tasks.)
- A task has one reporter and one assignee (both users) and at most one label.
- A label can be applied to many tasks; a task has zero or one label.
- A task has many comments; each comment belongs to one task and one author.
- A comment can mention many users (many-to-many via mentions).
- A task has many time-log entries and many activity-log entries; each belongs to one task and one user.
- A user has many notifications; each references one task.
- A user has one set of notification settings.
← Table of Contents · Part 4a — tables & fields →
6.2docs/04a-data-model-tables.md
Part 4a — Data Model: tables & fields
← Part 4
The table-by-table field lists for the taskboard database. Names are indicative; final naming/types are settled in Part 7. See Part 4 for the plain-words overview and relationships.
users
id · full_name · email (unique) · password_hash · role (admin|member) · is_active · must_set_password (true until first-login setup done) · created_at · updated_at
projects
id · name · description (optional) · is_archived · created_at · updated_at
labels
id · name · color (admin-managed) · created_at · updated_at
tasks
id · project_id → projects (fixed once created) · reporter_id → users (creator, fixed) · assignee_id → users (required, changeable by admin) · label_id → labels (nullable, at most one) · title · description (optional) · priority (low|medium|high; colors fixed in the design theme, not admin-editable) · status (todo|in_progress|done) · is_archived · archived_by_project (set when archived by a project-archive cascade, so unarchiving the project restores exactly those tasks — see Part 6) · created_at · updated_at
comments
id · task_id → tasks · author_id → users · body · is_edited (explicit flag, since timestamps are only second-precise) · created_at · updated_at
comment_mentions
id · comment_id → comments · mentioned_user_id → users · (a row per user mentioned in a comment)
time_logs
id · task_id → tasks · user_id → users (who logged) · hours · logged_on (date, automatic) · created_at · updated_at
activity_logs
id · task_id → tasks · actor_id → users · type (e.g. task_created, comment_added/edited/deleted, status_changed, time_logged/edited/deleted, task_archived/unarchived, assignee_changed) · detail (small structured note: from→to status, old→new assignee, hours) · created_at
The activity log is shown two ways: per task (that task's history, visible to everyone) and per project (all activity across the project's tasks, admin-only — see Part 8).
notifications
id · user_id → users (recipient) · type (matches the events in Part 3c) · task_id → tasks · is_read · created_at
notification_settings
id · user_id → users · one on/off flag per configurable email event: email_task_assigned, email_task_created_by_other, email_new_comment, email_status_changed, email_time_logged, email_mention
System configuration (not a table)
System-wide configuration lives in a PHP config file on the server, not in the database: the JWT lifetime (in hours) and SMTP credentials. This keeps secrets out of the database.
Notes and open points
- Following a task is derived, not stored: a user follows a task if they are the admin, the current assignee, the reporter, or have at least one comment on it. A
task_followers table can be added later if performance needs it.
- Deactivated users are kept (never deleted); their tasks and history remain, shown slightly transparent on the board.
- Archiving is a flag on
tasks and projects; nothing is ever hard-deleted. Archiving a project cascades to its tasks (Part 6/7).
- Reassignment changes
tasks.assignee_id and writes an activity_logs row; prior time logs keep their original user_id, so history stays accurate.
← Part 4 · Table of Contents →
7User Journeys
7.1docs/05-user-journeys.md
Part 5 — Main User Journeys
← Table of Contents · ← Part 4
The important flows from beginning to end. For each, we say what the user does, what the system does behind the scenes, and what they see at the end. Journeys 7–12 continue in Part 5a.
Journey 1 — Admin creates an account and the user's first login
User does (admin): Users → "Add user". Enters full name and email. Confirms. System does: Creates a user (role member, is_active true, must_set_password true), generates a random password, stores it hashed, emails the plain password, records the creation. User sees (admin): The new user appears, marked pending first login. User does (new user): Goes to login, enters email + the generated password. System does: Verifies credentials; because must_set_password is true it requires a new password (min. 8 chars) before completing login. User does (new user): Enters and confirms a new password (≥ 8 chars). System does: Stores the new hash, sets must_set_password false, issues a JWT. User sees: The dashboard, logged in.
Journey 2 — Logging in (returning user)
User does: Enters email + password. System does: Verifies credentials and that the account is active. If active, issues a JWT (lifetime from server config); if deactivated, refuses with a clear message. User sees: The dashboard, or an "account is deactivated" message.
Journey 3 — Admin creates a project
User does (admin): Projects → "New project". Enters a name (and optional description). Saves. System does: Creates the project (is_archived false). User sees: The new project appears; it can be selected on the board and receive tasks. (Members can see it but cannot create or edit projects.)
Journey 4 — Creating a task and assigning it
User does: "New task", picks a project, types a title + optional description, chooses a priority, optionally picks or creates a label, sets the assignee (a member can only assign to themselves; the admin to anyone). Confirms. System does: Creates the task (status todo, not archived), sets reporter = creator and assignee = chosen user, links the label if any, writes a task_created activity entry. Notifies the assignee ("assigned to you") and the admin if the creator was not the admin. Emails go where the recipient's settings allow. User sees: The task appears in the To Do column of its project's swimlane. "Create another" keeps project + label, clearing the rest.
Journey 5 — Moving a task across the board
User does: Drags a card between columns. A member can drag only tasks assigned to them; the admin can drag any task. System does: Checks permission, updates status, writes a status_changed activity entry (from → to), notifies the admin and followers per the rules. Emails where allowed. User sees: The card in the new column. Transitions are free between the three columns (exact rules in Part 6).
Journey 6 — Commenting and mentioning someone
User does: Opens a task's detail, types a comment, optionally @Name-mentions a person, posts it. System does: Saves the comment, records mentions, writes a comment_added entry, marks the author as a follower. Notifies: each mentioned user; the task's assignee (if not the commenter); other followers; the admin. Emails go where each recipient's settings allow (members only within their followed scope). User sees: Their comment in the list and the activity log; interested users get a notification.
← Table of Contents · Part 5a — journeys 7–12 →
7.2docs/05a-user-journeys-more.md
Part 5a — Main User Journeys (7–12)
← Part 5
Continuation of the flows from Part 5: time, notifications, archiving, projects, user state, and reports.
Journey 7 — Logging time
User does: On a task assigned to them, "Log time" and enters hours. (The admin can do this on any task.) Confirms. System does: Verifies the logger is the current assignee (or admin), creates a time-log entry (hours, user, automatic date), writes a time_logged entry, notifies the admin, updates report totals. User sees: The new entry in the task's time list and activity log; the total updated. Editing/deleting their own entry is recorded in the activity log too.
Journey 8 — Getting notified
User does: Sees the badge with an unread count; opens notifications. System does: Lists that user's notifications, newest first, with read/unread state. User does: Clicks one (opens the task), or marks one / all as read. System does: Opens the task; updates read state and the counter. User sees: The relevant task and a cleared/reduced badge. Where settings allowed, they also got the matching email.
Journey 9 — Archiving and unarchiving a task
User does: On a task, "Archive". A member can archive only tasks they created; the admin any task. System does: Sets is_archived, writes a task_archived entry; the task leaves the normal columns. User sees: The task disappears from To Do / In Progress / Done; the hidden Archived column shows it. Unarchiving (admin for any; member for their own created tasks) returns it to its previous status column and writes task_unarchived.
Journey 10 — Admin archives a project
User does (admin): On a project, "Archive project". System does: Archives the project and automatically archives all its tasks; those tasks leave the board until the project is reactivated. Records the change. User sees: The project's tasks disappear from the board. Reactivating restores visibility.
Journey 11 — Deactivating and reactivating a user
User does (admin): In Users, "Deactivate" on a person. System does: Sets is_active false; that user can no longer log in. Tasks and history remain; their cards appear slightly transparent. User sees: The user marked inactive, their tasks faded. Reactivating restores login.
Journey 12 — Admin views time reports
User does (admin): Reports. Chooses by project, by task, or by user. System does: Sums logged hours across the grouping; presents the total excluding archived tasks and, in parentheses, the total including archived. User sees (admin only): The hour totals per project / task / user, with the archived-inclusive figure alongside. Members do not see reports.
← Part 5 · Table of Contents →
8Rules & States
8.1docs/06-rules-and-states.md
Part 6 — Rules & States
← Table of Contents · ← Part 5
The logic that keeps the app predictable: what states things can be in, which changes are allowed, and who may do what. This page covers states and the permission summary; notification rules and edge cases are in Part 6a.
Task status
A task has exactly one status at all times: todo · in_progress · done.
Allowed transitions: any status can move to any other — To Do → Done directly is allowed, as is Done → To Do. There is no forced order. Every change is recorded in the activity log (from → to, who, when). A new task always starts in todo.
Archived is a separate state, not a status
Archived is a flag (is_archived), independent of status. A task keeps its status underneath while archived; unarchiving returns it to that status column.
- Archiving removes the task from To Do / In Progress / Done and places it in the hidden Archived column.
- Tasks are never deleted. Archive is the only way to set a task aside — the golden rule, for everyone including the admin.
- Who can archive/unarchive: the admin any task; a member only tasks they created (reporter).
- Both archiving and unarchiving are recorded in the activity log.
Project state
A project is either active or archived (is_archived):
- Only the admin creates, edits, or archives a project.
- Archiving cascades: all of its tasks are automatically archived and leave the board until the project is reactivated.
- Reactivating restores the project and its tasks; tasks return to the status they held.
- Projects are never deleted, only archived.
Assignment rules
- Assignee is required at all times. A task cannot exist without one.
- On creation: a member must assign to themselves; the admin may assign to anyone.
- Reassignment is admin-only, and is recorded in the activity log (old → new).
- The reporter (creator) is fixed and never changes.
Who can edit / move / act
Summary of the enforced rules (full map in Part 2):
- Edit task details (title, description, priority, label): admin any; member on tasks they created or are assigned to.
- Move task status: admin any; member only tasks currently assigned to them.
- Archive/unarchive task: admin any; member only tasks they created.
- Comment: anyone, on any task. Edit own comment: author only. Delete comment: author (own) or admin (any).
- Log time: current assignee (while assigned) or admin (any). Edit/delete time entry: only the entry's author.
- Reports; projects, users, label colors, team email settings: admin only.
Time-log rules
- An entry stores hours, the user who logged it, and an automatic date. No description.
- Only the current assignee may add time, and only while assigned. After a reassignment they can no longer add time; existing entries stay and keep counting.
- The admin may add time to any task.
- A task's total = the sum of all entries across everyone who ever logged.
- Editing/deleting is allowed only for the entry's author, and every change is written to the activity log.
- Hours must be positive; fractional allowed (comma normalized:
1,5 → 1.5); max 8 per entry — add several entries for more.
← Table of Contents · Part 6a — notifications & edge cases →
8.2docs/06a-rules-notifications.md
Part 6a — Rules: following, notifications & edge cases
← Part 6
Continuation of the rulebook from Part 6: how following is derived, the notification matrix, deactivated users, and the edge-case guarantees.
Following a task (derived)
A user is a follower of a task if any of these is true:
- they are the admin, or
- they are the task's current assignee, or
- they are the task's reporter (creator), or
- they have at least one comment on the task.
Following is computed, not stored. It drives which comment notifications a member may receive.
Notification rules
In-app notifications are always created for the relevant recipients. Email is sent only when the recipient's settings allow that event type.
Admin receives (in-app always; email per admin settings):
- task assigned (any), task created by someone else, new comment (any), status changed (any), time logged (any), mentioned.
- The admin is never self-notified for their own actions.
Member receives (in-app always; email per their settings, limited to their followed scope):
- a task was assigned to them,
- a new comment on a task assigned to them,
- a new comment on a task they follow,
- they were mentioned.
Configurable email events (on/off): task assigned, task created by someone else, new comment, status changed, time logged, mention. In-app is not configurable — always on.
Deactivated users
- A deactivated user cannot log in.
- Their data (tasks, comments, time logs, history) is preserved; nothing is deleted.
- Their task cards appear slightly transparent on the board.
- The admin can reactivate them later.
- Their tasks remain assigned to them after deactivation; the admin may reassign if desired.
Edge cases and guarantees
- Deleting is never possible for tasks, projects, or users. Archive (tasks/projects) or deactivate (users) instead.
- An archived task is read-only: viewable, but no edits, no status move, no comments, no time logging — until unarchived.
- Archived project + its tasks: hidden and frozen (read-only) until the project is reactivated.
- Reassignment preserves history: the old assignee's time logs keep their
user_id; the change is logged.
- First login: a user with
must_set_password true cannot use the app until they set a password (≥ 8 chars).
- JWT expiry: tokens expire after the configured number of hours; an expired token forces re-login.
- Priority colors are fixed in code (per theme), not admin-editable. Label colors are admin-editable.
- One label per task; labels are global and shared across projects.
← Part 6 · Table of Contents →
12Build Plan
12.1docs/10-build-plan.md
Part 10 — Build Plan
← Table of Contents · ← Part 9
The order we actually build TaskBoard in. Each step is small enough to finish and see working on its own, building from the foundations (accounts, projects) up to the richer features (board drag-and-drop, comments, notifications, reports) and finally the deploy. Each step lists what we build and how we know it works ("done when"), plus an As built note recording what actually shipped.
The plan is split by phase so each page stays short and readable:
- Part 10a — Phases 0–1: foundations (skeleton, schema) and accounts & auth (login/JWT, passwords, user management).
- Part 10b — Phases 2–3: projects & tasks core, then the Kanban board (render, drag-and-drop, archive).
- Part 10c — Phases 4–5: task detail, comments & time logs, then reassignment & the notifications engine.
- Part 10d — Phases 6–7: time reports & project activity, the non-negotiables pass, and the deploy.
Status at a glance
| Phase | Scope | State |
| 0 | Skeleton, config, schema, seed | ✅ Done |
| 1 | Login/JWT, passwords, user management | ✅ Done |
| 2 | Projects, tasks core | ✅ Done |
| 3 | Board render, drag-and-drop, archive | ✅ Done |
| 4 | Task detail, comments, time logs | ✅ Done |
| 5 | Reassignment, notifications, email settings | ✅ Done |
| 6 | Reports, project activity, non-negotiables | ✅ Done |
| 7 | Deploy | ✅ Prepared (awaiting live credentials) |
Notes
- Documentation stays in sync. As code is built, the relevant
.md files are updated in the same change if behavior differs from the plan.
- Test as we go. Each step has a "done when" we verify before moving on.
- No deletes, ever — enforced from the schema up.
← Table of Contents · Part 10a — start the plan → · Part 12 — Deploy →
All build steps are complete; the deploy artifacts and guide (Part 12) are ready for the live cut-over once production credentials are provided.
12.2docs/10a-plan-phase0-1.md
Part 10a — Build Plan: Phases 0–1 (foundations, accounts & auth)
← Part 10 index
Phase 0 — Foundations
Step 0.1 — Project skeleton & config. ✅ Done. Create tasks/api (plain PHP, PDO-ready, custom router + PSR-4 autoloader, config file for JWT lifetime + SMTP placeholders) and tasks/frontend (Angular app shell, top bar, routing, design tokens). Done when: both run locally and the frontend can call a GET /api/health returning JSON. ✅ As built: the API runs on PHP's built-in server (php -S localhost:8000 -t api/public) rather than MAMP's Apache, which is simpler for students; MySQL still comes from MAMP in Step 0.2. CORS allows the Angular dev origin on ports 4200 and 4300. See tasks/README.md to run it.
Step 0.2 — Database & seed. ✅ Done. Create the taskboard schema from Part 4 (users, projects, labels, tasks, comments, comment_mentions, time_logs, activity_logs, notifications, notification_settings), with indexes. Seed the admin office@pixeltime.ro with the initial password (hashed) and a couple of labels. Done when: schema loads cleanly and the admin row exists. ✅ As built: api/database/schema.sql (10 tables, InnoDB/utf8mb4, 15 foreign keys, indexes per Part 7) and api/database/seed.sql (admin + its notification settings + labels Bug/Feature; idempotent). Verified against MySQL 8.0.44 on MAMP (port 8889). Initial admin password is ChangeMe-TaskBoard-2026! with must_set_password = 1, so it is replaced on first login (docs Part 5).
Phase 1 — Accounts & auth
Step 1.1 — Login + JWT. ✅ Done. POST /auth/login, JWT issue/verify, server-side auth middleware, login screen. Done when: admin logs in, gets a token, and a protected endpoint accepts it / rejects a missing or expired one. ✅ As built: backend — Database (PDO), Jwt (hand-rolled HS256, expiry from jwt.lifetimeHours), User + UserRepository, AuthService, AuthController (POST /api/auth/login), Auth::requireUser middleware, and a protected GET /api/me. Frontend — AuthService (token + user in localStorage), HTTP interceptor (attaches Bearer; on 401 logs out and routes to /login), route guard, and a login screen.
| Test case | Expected | Result |
| Login with correct admin password | 200 + token + mustSetPassword + user | ✅ |
GET /me with valid / no / expired token | 200 / 401 / 401 | ✅ |
| Login with wrong password | 401 (generic message) | ✅ |
GET /me after deactivation / login while deactivated | 401 / 403 | ✅ |
Step 1.2 — First-login password + change password. ✅ Done. must_set_password flow, POST /auth/set-password (min. 8 chars), Set Password screen; GET/PUT /me. Done when: a seeded/new user is forced to set a password on first login and can later change it. ✅ As built: backend — POST /api/auth/set-password (first-login setup needs no current password; a later change requires and verifies the current one; both enforce the 8-char minimum and matching confirmation, then clear must_set_password and reissue a token) and PUT /api/me (own name/email, unique email → 409). Frontend — a Set Password screen, an authGuard that funnels mustSetPassword users to it, and a Profile & Settings screen.
| Test case | Expected | Result |
| Login as a first-login user | 200, mustSetPassword: true | ✅ |
| Set password < 8 chars / mismatch | 400 | ✅ |
| Valid first-login set | 200, mustSetPassword: false, new token | ✅ |
| Change password with no / wrong / correct current password | 400 / 400 / 200 | ✅ |
PUT /me with valid / invalid email | 200 / 400 | ✅ |
Step 1.3 — User management (admin). ✅ Done. GET /users, POST /users (generate password + email via SMTP), deactivate/reactivate. Admin Users screen. Done when: admin creates a user who receives the email and logs in; deactivation blocks login. ✅ As built: backend — GET /api/users (any authenticated user), POST /api/users, PUT /api/users/{id}/deactivate|reactivate (admin-only). POST generates a strong password, stores it hashed, creates the member (with must_set_password and a default notification-settings row), and emails it. Guards: cannot deactivate yourself or the last active admin. Frontend — an admin Users screen and an adminGuard. Dev-mail fallback: while SMTP is unset the Mailer writes each email to api/storage/mail/*.log and POST /api/users returns the generated password so the admin can pass it on.
| Test case | Expected | Result |
Admin GET /users / POST /users (valid) | 200 / 201 (+ dev password) | ✅ |
POST /users with duplicate email | 409 | ✅ |
| New user logs in with the generated password | 200, mustSetPassword: true | ✅ |
| Admin deactivates a user → that user logs in | 200 → 403 | ✅ |
| Admin deactivates self / the last admin | 409 | ✅ |
Member calls any admin users endpoint | 403 | ✅ |
← Part 10 index · Phase 2–3 →
12.3docs/10b-plan-phase2-3.md
Part 10b — Build Plan: Phases 2–3 (projects, tasks, the board)
← Part 10 index
Phase 2 — Projects & tasks (no board yet)
Step 2.1 — Projects (admin). ✅ Done. CRUD-without-delete: create, edit, archive/unarchive (cascade to tasks). Projects list + project page. Done when: admin manages projects; archiving hides their tasks. ✅ As built: backend — GET /api/projects (any user; ?includeArchived=1), GET /api/projects/{id}, POST/PUT /api/projects, PUT /api/projects/{id}/archive|unarchive (admin-only). Archive/unarchive run in a transaction. A tasks.archived_by_project flag lets a project-unarchive restore exactly the tasks the cascade archived, leaving individually-archived tasks archived. Frontend — an admin Projects screen (create, inline edit, archive/unarchive, show-archived toggle, task counts) and a project page.
| Test case | Expected | Result |
| Admin list / create / edit | 200 / 201 / 200 | ✅ |
| Create with blank name | 400 | ✅ |
| Archive a project (active + pre-archived task) | active archived (archived_by_project), pre-archived untouched | ✅ |
| Unarchive the project | cascade-archived task restored; pre-archived stays archived | ✅ |
| Member views / attempts a write | 200 / 403 | ✅ |
Step 2.2 — Tasks core. ✅ Done. Create task (title, description, priority, label, assignee rules), edit details, GET /tasks with filters + search, GET /tasks/{id}. New Task dialog with "create another". Done when: a member creates a self-assigned task; the admin assigns to anyone; listing/filtering/search work. ✅ As built: backend — labels (GET/POST /api/labels) and tasks (GET /api/tasks with project/assignee/label/priority/status/archived + text search; GET/POST /api/tasks; PUT /api/tasks/{id}). Create sets status todo, reporter = creator, writes a task_created activity row, and enforces the assignee rule (member → self only; admin → any active user). PUT /tasks/{id} is a full replace of editable fields. Frontend — a New Task dialog and a Tasks browse view.
| Test case | Expected | Result |
| Admin creates task assigned to a member | 201 (status todo, reporter = admin) | ✅ |
| Member creates task for someone else / for self | 403 / 201 | ✅ |
| Blank title / invalid priority / missing project | 400 / 400 / 404 | ✅ |
| List + filters + search (title/description) | correct subsets | ✅ |
| Member edits an assigned task / a task that is neither theirs | 200 / 403 | ✅ |
| Edit a task whose project is archived | 409 (read-only) | ✅ |
Phase 3 — The board
Step 3.1 — Kanban render. ✅ Done. Global board: fixed columns, project swimlanes, cards, deactivated-user transparency, newest-first, filters. Done when: tasks appear in the right columns and filters change what is shown. ✅ As built: a Board (home route /) fetches active tasks and groups them into To Do / In Progress / Done. One project → plain columns; several → a swimlane each (heading + count). Cards carry title, a priority pill, the label chip, the assignee avatar; a deactivated assignee's cards render faded. A filter bar (project/assignee/label/priority) re-queries the API.
Step 3.2 — Drag-and-drop status + rules. ✅ Done. PUT /tasks/{id}/status with permissions, activity log entry, keyboard alternative. Done when: allowed moves persist; disallowed moves are blocked server-side. ✅ As built: PUT /api/tasks/{id}/status validates the status, blocks archived tasks (409), and authorizes the mover (admin any; member only a task assigned to them), writing a status_changed activity row ({from,to}) in a transaction. On the board, cards drag between columns via Angular CDK; drag is disabled where not allowed, with an optimistic update that reverts on error. Note (later change): the per-card "Move to" select (the keyboard alternative) was removed from the UI on request; status changes are now drag-only.
| Test case | Expected | Result |
| Admin moves any task / member moves own assigned | 200 / 200 | ✅ |
| Member moves a task not assigned to them | 403 | ✅ |
| Invalid status / archived project / missing task | 400 / 409 / 404 | ✅ |
| Activity log after moves | status_changed with correct {from,to} | ✅ |
Step 3.3 — Archive/unarchive tasks + Archived column. ✅ Done. PUT /tasks/{id}/archive|unarchive, hidden Archived column toggle, read-only rule for archived tasks. Done when: archiving removes a card from normal columns and it appears under Archived; archived tasks reject edits/comments/time. ✅ As built: PUT /api/tasks/{id}/archive|unarchive — admin any task; a member only a task they created (reporter). A manual archive leaves archived_by_project untouched, so it stays archived even if its project is later unarchived. Status is preserved so unarchiving returns the task to the same column. The board's "Show archived" toggle reveals a 4th Archived column. Note (later change): the archive/unarchive action was moved off the board cards onto the task detail page (/tasks/:id) on request.
| Test case | Expected | Result |
| Member archives own / someone else's task | 200 / 403 | ✅ |
| Admin archives / unarchives any task | 200 (status preserved) | ✅ |
| Re-archive an already-archived task | 409 | ✅ |
| Edit / move an archived task | 409 | ✅ |
| Activity log | task_archived then task_unarchived | ✅ |
← Phase 0–1 · Phase 4–5 →
12.4docs/10c-plan-phase4-5.md
Part 10c — Build Plan: Phases 4–5 (detail/comments/time, notifications)
← Part 10 index
Phase 4 — Task detail, comments, time
Step 4.1 — Task right panel + full page. ✅ Done. Right panel (quick peek) from the card button; full page (rich overview) with description + activity log. Done when: both open with correct data and respect rules. ✅ As built: GET /api/tasks/{id}/activity (newest-first, paginated, 404 on a missing task) returns each entry with the actor's name and decoded detail. Each board card has an open button that slides in the right panel; the full page at /tasks/:id shows the full description, meta, and the complete activity log, with a read-only badge on archived tasks.
| Test case | Expected | Result |
GET /tasks/{id}/activity after create + two moves | 3 entries, newest-first, with {from,to} | ✅ |
Activity pagination (?limit=1) | 1 of 3, total correct | ✅ |
| Open the right panel / full page | essentials + activity load | ✅ |
Step 4.2 — Comments + mentions. ✅ Done. List/add/edit-own/delete (own or admin), @mentions, following derived. Blocked on archived tasks. Done when: comments post, mentions are recorded, permissions hold. ✅ As built: GET/POST /api/tasks/{id}/comments and PUT/DELETE /api/comments/{id}. Anyone reads/adds; only the author edits; author or admin deletes; archived tasks are closed (409). @mentions resolve against active users (longest-match wins) and are stored in comment_mentions. Each change writes a comment_added/edited/deleted activity row; a comments.is_edited flag marks edited comments reliably (DATETIME is only second-precise).
| Test case | Expected | Result |
Add a comment that @mentions a user | 201, mention stored | ✅ |
| Member edits / deletes someone else's comment | 403 / 403 | ✅ |
| Admin deletes a member's comment (moderation) | 200 | ✅ |
| Add / edit a comment on an archived task | 409 | ✅ |
Step 4.3 — Time logs. ✅ Done. Add (current assignee or admin, ≤ 8h, fractional, comma normalized), edit/delete own, activity entries. Blocked on archived task/project. Done when: entries save with correct rules and totals compute. ✅ As built: GET/POST /api/tasks/{id}/time-logs and PUT/DELETE /api/time-logs/{id}. Only the current assignee or the admin may add; everyone edits/deletes only their own; archived tasks are closed. A shared helper validates hours (positive, fractional, max 8, comma→dot). The task total sums across everyone, so reassignment keeps the earlier assignee's hours counted.
| Test case | Expected | Result |
| Assignee logs "1,5" | 201, stored as 1.5 | ✅ |
| Admin logs on any task / a non-assignee member logs | 201 / 403 | ✅ |
| Hours > 8 or ≤ 0 | 400 | ✅ |
| After reassignment: old assignee logs / earlier hours | 403 / still in total | ✅ |
| Edit/delete own / someone else's entry | 200 / 403 | ✅ |
Phase 5 — Notifications & reassignment
Step 5.1 — Reassignment (admin). ✅ Done. PUT /tasks/{id}/assignee, activity log, notification. Done when: admin reassigns; history and notification recorded. ✅ As built: PUT /api/tasks/{id}/assignee (admin only). New assignee must be active; a no-op reassignment → 409; an archived task → 409. In one transaction it updates the assignee, writes assignee_changed ({from,to} by name), and creates a task_assigned notification (not for the admin assigning to themselves). Prior time logs keep their original author.
| Test case | Expected | Result |
| Admin reassigns A → B | 200, assignee updated | ✅ |
| A member attempts reassignment | 403 | ✅ |
| Reassign to current assignee / a deactivated user / an archived task | 409 / 409 / 409 | ✅ |
| Old assignee's time logs after reassignment | preserved and still counted | ✅ |
Step 5.2 — Notifications engine. ✅ Done. In-app notifications per Part 6 for assign/create/comment/status/time/mention; unread count, list, mark one/all read. Done when: the right recipients get the right in-app notifications and the badge works. ✅ As built: a central Notifier holds all recipient rules in one place, called from create/status/reassign/comment/time. It never notifies the actor and dedupes; "follower" is derived (admin, assignee, reporter, commenter). Read side: GET /api/notifications (+ unread-count), PUT /api/notifications/{id}/read, PUT /api/notifications/read-all, all caller-scoped. The top-bar bell shows a live unread badge + dropdown.
| Event (actor) | In-app recipients | Result |
| Member creates a self-assigned task | admin: task_created_by_other + task_assigned; creator: none | ✅ |
| Status change / time logged | admin: status_changed / time_logged | ✅ |
Comment with @mention | mentioned: mention; assignee/followers/admin: new_comment (not the author) | ✅ |
| Mark one / all; marking another user's | correct; → 404 | ✅ |
Step 5.3 — Email notifications + settings. ✅ Done. SMTP send per event; admin team-wide + member limited email settings. Done when: enabling an event emails the right people; disabling stops the email while in-app stays on. ✅ As built: each user has a notification_settings row (six per-event flags). When the Notifier creates an in-app notification it also sends an email only if that recipient's flag for the event is on — so each person's own preferences gate their own email. Send is best-effort (a failure never breaks the request) via the Mailer (dev-log until SMTP is set). GET/PUT /api/me/notification-settings back a six-toggle card on Profile & Settings. In-app stays always-on. Email format (post-deploy): messages are multipart/alternative — a styled HTML layout plus a plain-text fallback — and carry a clickable link built from app.url: the welcome email links to /login and shows the temporary credentials; notification emails deep-link to /tasks/{id}. The host blocks mail(), so the Mailer sends over authenticated SMTP (465/SSL) and appends a copy to the IMAP Sent folder for Roundcube. Landing in spam from a new address is fixed in DNS (SPF ✅ + DKIM ✅ already present on scoaladeweb.ro; add a DMARC record), not in code.
| Test case | Expected | Result |
| Default settings for a new user | all six enabled | ✅ |
| Event flag ON / OFF | in-app + email / in-app only | ✅ |
GET / PUT own settings | only the caller's flags | ✅ |
← Phase 2–3 · Phase 6–7 →
12.5docs/10d-plan-phase6-7.md
Part 10d — Build Plan: Phases 6–7 (reports, polish, ship)
← Part 10 index
Phase 6 — Reports & polish
Step 6.1 — Time reports (admin). ✅ Done. GET /reports/time by project/task/user, with an archived-inclusive figure in parentheses. Done when: totals match logged hours and the archived figure is shown. ✅ As built: GET /api/reports/time?by=project|task|user (admin only; invalid grouping → 400; default project). Each row carries activeHours (non-archived) and totalHours (incl. archived), via SQL aggregates. The admin Reports screen shows the active figure plus "(N h incl. archived)" where they differ, with a footer total.
| Test case | Expected | Result |
| By project (2h active + 3h archived task) | active 2h, total 5h | ✅ |
| By task | active 2/2; archived 0/3 | ✅ |
| By user | admin 2/5 | ✅ |
| A member requests the report / invalid grouping | 403 / 400 | ✅ |
Step 6.2 — Project activity log (admin). ✅ Done. Project-level activity view. Done when: a project shows all its tasks' activity. ✅ As built: GET /api/projects/{id}/activity (admin only; 404 on a missing project; ?limit/?offset) rolls up every activity entry across the project's tasks, newest-first, each tagged with its task title and actor. The admin project page's placeholder is replaced with that list.
| Test case | Expected | Result |
| Project with two tasks + several events | all entries, newest-first, with task titles | ✅ |
| A member requests the project log / missing project | 403 / 404 | ✅ |
Pagination (?limit=2) | 2 of 4, correct total | ✅ |
Step 6.3 — Non-negotiables pass. ✅ Done. Responsive/mobile check, accessibility pass, security review, performance check. Done when: the Part 9 checklist is satisfied and verified where possible. ✅ As built: a review-and-harden sweep against Part 9. The only code change needed was adding Escape-to-close to the two overlays (New Task dialog, task right panel); everything else already met the bar.
| Part 9 area | Checked | Result |
| Security — authz on every endpoint | all controllers call requireUser/requireAdmin except public health + login | ✅ |
| Security — prepared statements | all SQL bound; dynamic bits whitelisted/placeholder-only | ✅ |
| Security — secrets in config | config.php + storage/mail/ git-ignored; none in source | ✅ |
| Security — live spot-check | no-token → 401; member on admin → 403 | ✅ |
| Performance — indexes / pagination & aggregates | indexes cover filter/join columns; activity + notifications paginate; board one joined query | ✅ |
| Accessibility — keyboard / labels | dialogs close on Escape + global :focus-visible; icon-only buttons have aria-label; forms use <label> | ✅ |
| Responsive — mobile | board scrolls horizontally; filters wrap; top bar collapses ≤560px; panel is a full-width sheet | ✅ |
Caveat: full WCAG conformance needs manual testing with assistive technologies and expert review.
Phase 7 — Ship
Step 7.1 — Deploy. ✅ Prepared (awaiting live credentials). Configure production (single domain), SMTP credentials, JWT secret/lifetime, database backups. Done when: the app runs on the server, the admin logs in, and a full journey works end to end. As built: the deploy artifacts and guide are in place; the live cut-over runs on the server once credentials are filled into config.php. Target: https://task.scoaladeweb.ro/ (app) + /api (API) on one domain, so the production frontend already calls a relative /api.
- Production config template
api/config/config.production.sample.php — env => 'production', CORS locked to the real origin, placeholders for the DB password, a fresh random jwt.secret, and SMTP.
- Web-server rules —
api/public/.htaccess (front controller + Authorization pass-through), api/.htaccess (blocks config/,src/,database/,storage/), frontend/public/.htaccess (SPA deep-link fallback, ships in the build).
- Database backups —
api/database/backup.sh, a cron-friendly mysqldump wrapper (timestamped .sql.gz, prune by RETENTION_DAYS, env-driven credentials).
- Deploy guide — docs Part 12 (12a server setup + 12b verify & backups).
Verified locally: production build passes and ships the SPA .htaccess; the config template is valid PHP. Live checks (docs 12b) run on the server once credentials are in.
← Phase 4–5 · Part 10 index →
13Design Guidelines
13.1docs/11-design-guidelines.md
Part 11 — Design Guidelines
← Table of Contents · ← Part 10
This part defines TaskBoard's visual system — the "2027 light theme" taken from the reference dashboards we reviewed. It is the single source of truth for how every screen should look and feel, so the board, cards, forms, and settings all read as one product.
Because it is detailed, it is split into two linked files:
radii, shadows, motion. The raw tokens everything else is built from.
buttons, inputs, cards, pills/badges, avatars, the sidebar, the top bar, and the Kanban card.
The look in one paragraph
A calm, bright interface: a soft off-white background with pure-white cards that float on gentle, diffuse shadows. Corners are generously rounded. A single vivid accent (indigo/violet) carries primary actions and the active navigation state; everything else is a quiet neutral grey. Status and priority appear as soft-tinted pills — a pale colored fill with saturated text — never as color alone. Type is clean and modern (Inter), with bold dark headings over muted grey secondary text. People appear as small rounded avatars.
Principles
- Light and airy. Whitespace is a feature. Let content breathe; avoid
dense borders — separate with space and soft shadow instead.
- One accent, used sparingly. The accent marks the primary action and the
current location. If everything is accented, nothing is.
- Color is never the only signal. Every status/priority also carries text
(and often an icon), so it is readable without color (docs Part 9).
- Soft, not flat, not heavy. Rounded corners and low-opacity shadows give a
gentle lift. No hard 1px drop shadows, no harsh outlines.
- Consistent tokens. Components reference the tokens in Part 11a; they do
not hard-code colors or sizes. The live tokens are in frontend/src/styles.scss and must match this document.
Where the tokens live
The foundations in Part 11a are already implemented as CSS custom properties in frontend/src/styles.scss (e.g. --color-accent, --radius-lg, --shadow-md). When this document and the stylesheet disagree, treat it as a bug and bring them back in sync in the same change (docs Part 10 — docs stay in sync).
← Table of Contents · Next: Part 11a — Foundations
13.2docs/11a-foundations.md
Part 11a — Design Foundations
← Part 11 index
The raw design tokens. These map one-to-one to the CSS custom properties in frontend/src/styles.scss. Keep the two in sync.
Color
Neutrals
| Token | Value | Use |
--color-bg | #f5f6fa | App background (soft off-white) |
--color-surface | #ffffff | Cards, bars, menus |
--color-surface-muted | #eef0f6 | Subtle fills (search, chips, hover) |
--color-border | #e4e7ef | Hairline borders / dividers |
--color-text | #1c2130 | Primary text (soft near-black) |
--color-text-muted | #727a8c | Secondary text, labels |
Accent
| Token | Value | Use |
--color-accent | #6c5ce7 | Primary buttons, active nav, focus ring |
--color-accent-strong | #5a4bd4 | Hover / pressed accent |
--color-accent-soft | #ece9fc | Tinted accent background (active row) |
Status & priority tints
Each is a (soft fill, saturated text) pair for pills. Always shown with text.
| Meaning (example) | Soft fill | Text |
| Success — Done / Resolved | --color-success-soft #dcfce7 | --color-success #16a34a |
| Warning — In Progress | --color-warning-soft #fef3c7 | --color-warning #d97706 |
| Info — To Do / Open | --color-info-soft #dbeafe | --color-info #2563eb |
| Danger — High / Blocked | --color-danger-soft #ffe4ea | --color-danger #e11d48 |
Priority mapping (fixed in code, docs Part 4/6): Low → info, Medium → warning, High → danger. Status mapping: To Do → info, In Progress → warning, Done → success. Label colors are admin-managed and arbitrary.
Typography
- Family:
Inter, with a system-sans fallback (--font-sans).
- Base: 15px, line-height 1.4. Weights used: 400, 500, 600, 700.
| Role | Size | Weight |
| Page title (h1) | ~1.6rem | 700 |
| Section title (h2) | ~1.1rem | 600 |
| Body | 1rem (15px) | 400–500 |
| Secondary / meta | 0.8–0.85rem | 500, muted color |
| Pill / badge | 0.78rem | 600 |
Spacing
A 4px base scale (--space-1 … --space-7 = 4, 8, 12, 16, 20, 24, 40px). Use tokens, not raw pixels. Default card padding is --space-6 (24px); gaps between cards are --space-5/--space-6.
Radii
| Token | Value | Use |
--radius-sm | 8px | Small controls, inputs |
--radius-md | 12px | Buttons |
--radius-lg | 16px | Cards |
--radius-xl | 20px | Large panels / sheets |
--radius-pill | 999px | Pills, avatars, icon buttons |
Shadows
| Token | Use |
--shadow-sm | Resting chips / subtle lift |
--shadow-md | Cards (default) |
--shadow-lg | Raised elements: dragged card, dialogs, right panel |
Shadows are low-opacity and large-blur (soft). Never use hard 1px drop shadows.
Motion
--transition: 150ms ease for hover/focus/background changes.
- Keep motion subtle; respect
prefers-reduced-motion by avoiding large,
essential animations.
← Part 11 index · Next: Part 11b — Components
13.3docs/11b-components.md
Part 11b — Components
← Part 11 index · ← Part 11a
How the foundation tokens combine into the UI pieces. Shared base classes .card, .pill, and .btn already exist in frontend/src/styles.scss.
Buttons
- Primary (
.btn--primary): accent fill, white text, --radius-md,
padding ~10/18px. Hover → --color-accent-strong. One primary action per view (e.g. "New task", "Create ticket" in the references).
- Ghost / secondary (
.btn--ghost): transparent fill, --color-border
outline, neutral text; hover fills with --color-surface-muted.
- All buttons show a visible focus ring (accent, 2px, 2px offset) for keyboard
users (docs Part 9). Minimum touch target ~40px high.
Inputs & forms
- Fields sit on
--color-surface-muted or white with a --color-border edge,
--radius-sm/--radius-md, comfortable padding.
- Focus: border switches to
--color-accent (and/or focus ring). No glow.
- Labels are above fields in
--color-text; helper/error text below. Errors use
--color-danger and plain-English messages (docs Part 9).
- The top-bar search is a pill (
--radius-pill) with a leading search icon.
Cards
.card: white surface, --radius-lg, --shadow-md, --space-6 padding,
hairline border. Used for dashboard panels, settings sections, the status card. Group a title row (h2 + optional pill/action) above the content.
Pills / badges
.pill + a modifier (--success, --warning, --info, --danger,
--neutral): fully rounded, soft fill, saturated text, 0.78rem/600. Carries text always (status label, priority name) so color is not the only signal.
Avatars
- Round (
--radius-pill), small (28–32px) for inline/card use. Image when
available; otherwise the person's initial on an accent background. Cards for a deactivated user's tasks render at reduced opacity (docs Part 3b/6).
Sidebar navigation (to build)
The references use a left sidebar; TaskBoard adds one as screens grow (docs Part 8). Spec:
- White surface, right hairline border; app brand at the top.
- Items are icon + label rows with
--radius-md. The active item uses an
--color-accent-soft background with accent text/icon (pill-highlighted).
- Hover:
--color-surface-muted. Collapses behind a menu button on phones.
Top bar
- 64px tall, white, bottom hairline border. Brand/home at the left, pill search,
then right-aligned: notifications bell (round icon button with a danger-colored unread badge) and the user menu (rounded avatar + name). Implemented in shared/top-bar.
Kanban board & card (to build)
- Columns: To Do / In Progress / Done as light lanes with a header showing
the column name and a count. Generous gaps; the hidden Archived column is revealed by a button (docs Part 3b).
- Swimlanes: when multiple projects show, each is a horizontal section with a
project heading.
- Card: white,
--radius-md, --shadow-sm at rest and --shadow-lg while
dragging. Contents: title (600), a priority pill and an optional label chip (label's own color), the project name (muted), and the assignee avatar. Newest-first order; no manual reordering. A dedicated button opens the task right panel (docs Part 8).
- Drag-and-drop changes status; a keyboard alternative must exist (docs
Part 9). The drop target column shows a subtle --color-accent-soft highlight.
Right panel & dialogs
- Right panel: a raised surface (
--shadow-lg) sliding beside the board;
becomes a full-width sheet on phones (--radius-xl top corners). Dialogs (New Task) are centered cards with a dimmed backdrop. Both trap focus and close on Escape.
Icons
- Icons use Lucide (outline, MIT-licensed) via
lucide-angular, rendered as
inline SVG that bundles with the app — no network request, cached with the JS.
- Only the icons in use are registered (
LucideAngularModule.pick) in
app.config.ts, keeping the bundle small. Icons draw with currentColor and take a size attribute.
- In use: add (
plus), edit (pencil), delete (trash-2), archive/unarchive
(archive / archive-restore), expand a card (maximize-2), notifications (bell), close (x), log out (log-out), search (search), brand (square-kanban). Icon-only buttons keep an aria-label + title.
← Part 11 index · Back to Table of Contents
14Deploy
14.1docs/12-deploy.md
Part 12 — Deploy
← Table of Contents · ← Part 11
How TaskBoard goes live on https://task.scoaladeweb.ro/ (the app) with the API under https://task.scoaladeweb.ro/api (docs Part 7 — one domain). This is the final build step (Part 10, Step 7.1).
Parts
config, database load.
automated database backup.
The shape of it
One domain serves two things:
| URL | Served from | What it is |
https://task.scoaladeweb.ro/ | the Angular build (dist/) | the app (SPA) |
https://task.scoaladeweb.ro/api/... | api/public/index.php | the JSON API |
The frontend already knows to call a relative /api in production (src/environments/environment.production.ts), so the production build needs no URL edits. Because the app and API share one origin, requests are same-origin and CORS is effectively a non-issue — the allowlist still names the real origin as a safety net.
What ships vs. what stays secret
- Ships (committed): all source, the two
.htaccess files, the
config.production.sample.php template, the schema.sql/seed.sql, and the backup.sh script.
- Never ships:
api/config/config.php (the real JWT secret, DB and SMTP
credentials) and api/storage/mail/ — both git-ignored. You create config.php on the server by copying the production sample and filling in the values (docs Part 9 — secrets stay out of the repo and the database).
One-line summary of the flow
Build the frontend → upload dist/ to the web root → upload api/ outside or beside it → point /api at api/public → copy + fill config.php → load the schema + seed → verify → schedule backups. The next two pages walk each of these in order.
← Table of Contents · 12a — Server setup →
14.2docs/12a-deploy-server.md
Part 12a — Deploy: server setup
← Part 12
Steps to put the files on the server and wire the two document roots. Commands assume an Apache host (cPanel/Plesk style); adapt paths to yours.
1. Build the frontend (locally)
npm install --prefix frontend
npm run build --prefix frontend
The production bundle lands in frontend/dist/frontend/browser/. It already targets a relative /api (production environment file) and includes the SPA .htaccess (from frontend/public/.htaccess) at the root of that folder.
2. Upload
- Upload the contents of
frontend/dist/frontend/browser/ (including the
hidden .htaccess) into the site document root so index.html sits at https://task.scoaladeweb.ro/.
- Upload the
api/ folder to the server. The preferred layout keeps api/
outside the web root and points a sub-path at it (next step).
3. Point /api at api/public
The API routes expect the request path to start with /api (e.g. /api/health). Two common ways to achieve that:
- Alias (best): map the URL path
/api to the api/public directory, e.g.
an Apache Alias /api "/full/path/api/public" with a <Directory> grant. The api/public/.htaccess then routes everything to index.php.
- Subdirectory fallback: if you can only drop
api/ under the web root,
the API becomes reachable at /api/public/.... The included api/.htaccess still blocks config/, src/, database/, and storage/ from the web. In this layout, serve the app from the root and set the frontend's apiBaseUrl to /api/public before building (one-line edit in environment.production.ts).
Either way, confirm the Authorization header reaches PHP (the API .htaccess forwards it) so Bearer-token auth works.
4. Create the real config
cp api/config/config.production.sample.php api/config/config.php
# then edit api/config/config.php and fill in every «...»:
# - db.name / db.user / db.pass (from the hosting panel)
# - jwt.secret (generate: php -r "echo bin2hex(random_bytes(32));")
# - smtp.* (mail provider credentials)
Keep env => 'production' so errors are not shown to clients.
5. Load the database
Create the database + user in the hosting panel, then load schema and seed:
mysql -h 127.0.0.1 -P 3306 -u <DB_USER> -p <DB_NAME> < api/database/schema.sql
mysql -h 127.0.0.1 -P 3306 -u <DB_USER> -p <DB_NAME> < api/database/seed.sql
The seed creates the admin office@pixeltime.ro with must_set_password = 1, so its password is replaced on first login. Change the seeded admin email first if the live admin differs.
← Part 12 · 12b — Verify & backups →
14.3docs/12b-deploy-verify.md
Part 12b — Deploy: verify & backups
← Part 12a
The go-live checklist and the automated database backup. "Done when" for Step 7.1 (docs Part 10): the app runs on the server, the admin logs in, and a full journey works end to end.
Verify the API
curl https://task.scoaladeweb.ro/api/health
Expected: HTTP 200 with JSON { "status": "ok", "service": "taskboard-api", ... }. If you see an HTML error page instead, the /api document root or the rewrite rules are not yet pointing at api/public.
# Admin login returns a token.
curl -X POST https://task.scoaladeweb.ro/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"office@pixeltime.ro","password":"<seeded-or-changed>"}'
Verify the app
- Open
https://task.scoaladeweb.ro/ — the login screen loads over HTTPS.
- Hard-refresh a deep link (e.g.
/admin/users) — it still loads (SPA
.htaccess fallback working, not a 404).
- Sign in as admin → set a new password → create a user → create a task → move
it on the board → comment → log time → check the notification bell. This is the Step 7.1 end-to-end journey.
Full go-live checklist
| Check | Expected |
GET /api/health over HTTPS | 200 JSON |
api/config/config.php exists, env => 'production' | errors hidden from clients |
jwt.secret is a fresh long random value (not the dev default) | tokens cannot be forged |
| SMTP filled in | generated-password + notification emails actually send |
Fetch /api/config/config.php in a browser | blocked (403/404), never the file |
| Deep-link hard refresh | app loads, no 404 |
| Backups scheduled (below) | daily dump present |
Database backups
api/database/backup.sh dumps the database to a timestamped .sql.gz and prunes dumps older than RETENTION_DAYS (default 14). It reads credentials from the environment, so no secrets live in the script.
# Manual run:
DB_NAME=<name> DB_USER=<user> DB_PASS=<pass> bash api/database/backup.sh
# Daily cron at 03:15 (keep 14 days), credentials from a protected env file:
15 3 * * * set -a; . /home/you/taskboard.env; set +a; \
bash /home/you/taskboard/api/database/backup.sh >> /home/you/taskboard-backup.log 2>&1
Restore a dump with:
gunzip < taskboard_YYYY-MM-DD_HHMMSS.sql.gz | \
mysql -h 127.0.0.1 -u <DB_USER> -p <DB_NAME>
← Part 12a · ← Table of Contents