# PRD — unity-lite-v2 (UnityLite)

Evidence date: 2026-09-30. Source: `https://bitbucket.org/overdrive/unity-lite-v2.git`, `master` `46f8a1f` (2022-05-26, "Copy Unity Lite v1").

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/unity-lite-v2/` before this file. Requirements below are recovered from the React screens, the route table, and the Amplify calls. They are not a new product decision.

Identifier values in `src/aws-config.js` are omitted. The login form's initial email and password literals are omitted. The files that hold them are named where the behaviour depends on their presence.

## Product purpose

A person opens a web app titled **UnityLite**. The shell brands itself **UnityLite**. While `localStorage` has no `roles` key they see a landing page, login, register, or forgot-password. After a sign-in attempt the app always navigates to `/app`. `/app` stays open only when that `roles` key is present.

**Implemented** in `public/index.html`, `src/components/common/TopNav.js`, `src/routes/Auth.js`, `src/routes/PrivateRoutes.js`, and `src/components/Login.js`.

The README calls the project "Unity - Lite", "Unity with out styling and redux components", and says it uses Amplify / lambda. The Lambda code is not in this repository. Provenance of the Lambda claim: **Documented** in `README.md` only.

Three names are in the tree and they are not the same string: page title `UnityLite`, README title `Unity - Lite`, package name `role-based-access-control`. The web-app manifest still says `React App`. Which name is the product name is **Unknown / Requires Confirmation**.

## Users and roles

`src/config/Roles.js` defines five role strings and comments that they are "placeholder roles you can replace". Provenance of the list: **Implemented**. Provenance of "placeholder": **Documented** in that comment.

| Role | Where the route table names it | What the code actually distinguishes | Provenance |
|---|---|---|---|
| `SUPER_ADMIN` | Config, Dashboard, Profile | May open those screens when the stored roles parse to an array containing this string | **Implemented** |
| `ADMIN` | Config, Dashboard, Profile | Same gate as `SUPER_ADMIN` for those three screens | **Implemented** |
| `MANAGER` | Config, Profile. Not Dashboard | Cannot open Dashboard. Can open Config and Profile | **Implemented** |
| `CUSTOMER` | Profile only | Cannot open Config or Dashboard. Can open Profile | **Implemented** |
| `GUEST` | Defined. No route lists it | If it is the only stored role, screens with no `permission` still open (calendar, booking, task list, kanban, users, user). Config, Dashboard, and Profile do not | **Implemented** definition. No screen assigns this role |

Screens with no `permission` array are Calendar, Booking, Tasklist, Kanban, Users, and User (`src/config/PrivateRoutesConfig.js`). `getAllowedRoutes` keeps a route when `permission` is missing or empty, for any caller who already passed the `roles` key check.

`getAllowedRoutes` (`src/utils/index.js`) keeps a permissioned route when `lodash.intersection` of the route's `permission` array and the parsed `roles` value is non-empty.

Signed-in state is not a Cognito session check. `isLoggedIn()` returns true when `localStorage` has a `roles` key. The function comment says the app assumes that key means the user is authenticated, and that the check is temporary. Provenance: **Implemented**, and **Documented** in that comment.

What the API field `strRole` contains (one name, or a JSON array of names) is **Unknown / Requires Confirmation**. Login stores it with `localStorage.setItem('roles', body.strRole)` and does not `JSON.stringify` it. Route filtering then `JSON.parse`s that value. If `strRole` is not already a JSON array string, `JSON.parse` throws and the private route tree does not render. **Inferred** from those two call sites. Not executed in a browser for this document.

There is no user-administration screen that saves a role. The user form renders a multi-select of the five role strings and does not submit it.

## Major workflows

### 1. Open the app

Trigger: browser loads the CRA bundle (`src/index.js`).

Behaviour:

- Amplify is configured for Auth (Cognito, `mandatorySignIn: true`), Storage (S3 bucket `unity-lite-files`), and an API endpoint named `unity`. Region in config is `eu-west-1`. Ids live in `src/aws-config.js`.
- `serviceWorker.unregister()` runs. `public/manifest.json` is linked (`display` standalone, name "Create React App Sample"). The service worker is not registered.
- `App` renders `Routes` inside a fluid Bootstrap container.
- Paths under `/app` render `PrivateRoutes`. Every other path renders `Auth`.
- `Auth` sends the visitor to `/app` when `isLoggedIn()` is true, and otherwise renders `PublicRoutes`.
- Public switch: `/forgot-password`, `/register`, `/login`, and a catch-all `LandingPage`.
- `LandingPage` shows `TopNav` with Login and Register, and the silicon overdrive credit.

Output: landing page, or a redirect into `/app` when a `roles` key already exists.

Error / empty: no splash screen. No error boundary was found. A missing `#root` is the CRA default (`public/index.html` noscript text: "You need to enable JavaScript to run this app.").

### 2. Sign in

Trigger: Login button on `/login` (`src/components/Login.js`).

Inputs: email and password state. The component initialises both from literals in the file. Those literals are omitted here.

Validation: none. The button is not disabled. The email and password `onChange` handlers do not read `e.target.value`. Each handler builds an empty array from `e.target.options` (a text input has no options) and stores that array. **Implemented**. Typing in the fields does not update the state with the typed characters. **Inferred** from those handlers. Not executed in a browser for this document.

Behaviour when the button is clicked (`handleClick`):

- `Auth.signIn(strEmail, strPassword)`.
- On a session, the access token is read (`response.signInUserSession.accessToken.jwtToken`) and is not written to `localStorage` or to an axios header. The Cognito username is `response.username`.
- `API.get("unity", "/unity/uuid/{username}")`.
- The code expects `{ body }` whose `body` is a JSON string, then takes index `0`.
- It stores `localStorage.roles` = `body.strRole` (see the parse mismatch above).
- It stores `localStorage.userDetails` = JSON `{ userID: body.UserID, userName: body.strUser }`.
- On an API exception inside the uuid call, the function returns `{ result: "Api Error" }` instead of throwing. `handleClick` then stores `roles` from `body.strRole` of that object (no such field). `setItem` coerces a missing value to the string `"undefined"`, which is truthy, so `isLoggedIn()` becomes true. `JSON.parse("undefined")` in `getAllowedRoutes` then throws. **Inferred** from `Login.js` and `utils/index.js`. Not executed here.
- `history.push('/app')` runs after the try/catch, including when `Auth.signIn` throws. A failed sign-in still navigates to `/app`. If `roles` was not set, `PrivateRoutes` redirects back to `/`.

The access token variable is unused after it is read. Session continuity for later Amplify calls depends on Amplify's own Cognito storage, which this file does not configure beyond `Amplify.configure` in `src/index.js`. **Inferred**.

A commented block in the same file offered a multi-select of the five roles and the text "Support multi roles." It is not rendered.

Outputs: `/app` navigation; `roles` and `userDetails` keys only when the statements that set them run.

### 3. Register and forgot password

Trigger: links from login, or the paths `/register` and `/forgot-password`.

Behaviour: each screen is a jumbotron whose title is "Register" or "ForgotPassword", plus a link back to `/login`. Neither calls `Auth.signUp`, `Auth.forgotPassword`, or the `unity` API.

Provenance: **Implemented** placeholders. A working registration flow is **not implemented**. The README does not describe one.

Logged-in visitors do not reach these screens: `Auth` redirects them to `/app` before `PublicRoutes` renders.

### 4. Private shell

Trigger: navigation to any path under `/app`.

Behaviour:

- If `isLoggedIn()` is false, redirect to `/`.
- Otherwise `getAllowedRoutes(PrivateRoutesConfig)` builds the nav and the switch.
- `TopNav` lists each allowed route's `title`, linking to `/app` + that route's `path`. It shows Logout when `isLoggedIn()` is true. The brand is UnityLite.
- `MapAllowedRoutes` renders each allowed component. An unmatched path under `/app` renders `NotFound` ("404 not found", link "Back to home" to `/`).
- `NotFound`'s link target `/` is the public catch-all. A visitor who still has `roles` set is redirected to `/app` by `Auth`, whose private index is the calendar. **Inferred** from the route split.

Logout (`TopNav.handleLogout`): removes `localStorage.roles` only. It does not remove `userDetails`. It does not call `Auth.signOut()`. It pushes `/`. Provenance: **Implemented**.

Config's children are not separate entries in the top nav. `Configer` receives `children` (Staff at `/staff`, Cron Jobs at `/cronjobs`), filters them with `getAllowedRoutes` (neither child has a `permission`, so both pass), and links them under base path `/app/configer`. So the staff URL is `/app/configer/staff` and cron jobs is `/app/configer/cronjobs`. The parent route path `/app/configer` is not `exact`, so those deeper paths still mount `Configer`. Provenance: **Implemented**.

### 5. Calendar

Trigger: `/app` or `/app/` (route path `/`, exact). This is the first private route.

Instructions rendered in the sidebar (**Documented** in the component):

- Select dates and you will be prompted to create a new event.
- Drag, drop, and resize events.
- Click an event to delete it.
- A checkbox toggles weekends. Initial state `weekendsVisible: true`.

Views: month (`dayGridMonth`, the initial view), week, day. Toolbar: prev, next, today, title. `editable`, `selectable`, `selectMirror`, `dayMaxEvents` are true.

When the visible range changes (`datesSet` → `handleDates`):

- Input: `userID` from `getUserDetails().userID`, `dtStartDate` = `rangeInfo.startStr`, `dtEndDate` = `rangeInfo.endStr`.
- Call: `unityRequests("/unity/getcalendarevents/", that object)`. `unityRequests` defaults to POST.
- Expects `{ body }` to be a JSON string of rows. Maps `CalendarIDitem` → `id`, `txtDescription` → `title`, `txtNotes` → `description`, `dtStartDate` → `start`, `dtEndDate` → `end`, plus `strFirstUser` and `refSysUserID`.
- On failure: `console.log`, loading flag cleared, previous events left as they are. No user-visible error string.

Create:

- `window.prompt('Please enter a new title for your event')`.
- Empty or cancelled prompt: selection is cleared and no event is added.
- Otherwise FullCalendar `addEvent` with `id` from `createEventId()` (a process-local increment starting at 0), the title, start, end, `allDay`, `strFirstUser` = `userDetails.userName`, `refSysUserID` = `userDetails.userID`.
- `eventAdd` POSTs `/unity/addcalendarevent/` with the DB field names above. On success it sets `event.id` from `body.id`. On failure it `revert()`s and logs.

Move or resize: POST `/unity/updatecalendarevent/` with the same field mapping. Failure reverts.

Delete: `window.confirm` including the event title. Confirm removes the event, which POSTs `/unity/removecalendarevent/`. Failure reverts. Cancel leaves the event.

`getUserDetails()` is `JSON.parse(localStorage.userDetails)`. If that key is missing, parse throws and the range load fails. **Inferred**. The login success path is what writes the key.

`INITIAL_EVENTS` in `src/components/event-utils.js` (an all-day event and a timed event for today) is not passed to FullCalendar. The calendar `events` prop is `state.currentEvents`, which starts empty and is replaced by the API result.

Loading flags are stored and are not rendered.

### 6. Users list

Trigger: `/app/users`. No `permission` restriction.

On mount, `Users` POSTs `/unity/getusers/1` (the `1` is a literal path segment). It `JSON.parse`s `body` and passes the array to a `react-table` table.

Columns: Name `strUser`, Contact `strTel`, Job `strJobTitle`, Role `strRole`, Email `strEmail`, Location `strLocation`, and an EDIT button.

EDIT links to pathname `/app/user/{UserID}` and also sets a `data` property on the `to` object to `UserID`.

`makeData(20)` from `src/components/makeData.js` (random `namor` people) is computed and is not passed to the table. The table uses the API array.

`isLoading` is cleared after the call and is not rendered. The effect's second argument is the boolean `isLoggedin`, not a dependency array. `isLoggedin` is never set to true. Provenance: **Implemented**.

Failure: `console.log`, table stays on the initial empty array. No empty-state copy.

What the path segment `1` means (tenant, company, page, or a fixed user id) is **Unknown / Requires Confirmation**.

### 7. User edit form

Trigger: route `/app/user` (not exact, so `/app/user/123` still matches this route). The component reads `useLocation().data`, not the URL segment.

On mount it POSTs `/unity/getdetails` with `{ id: userId }`. It expects `JSON.parse(body)[0]` and stores that object.

The form shows:

- Company, disabled, default value "Silicon Overdrive".
- Username, email, first name, last name, address bound in `value` to `user.strUser` or, for email, `user.strEmail`. First name, last name, and address all read `strUser`. City, country, and postal code all read `strLocation`.
- A multi-select of `SUPER_ADMIN`, `ADMIN`, `MANAGER`, `CUSTOMER`, `GUEST`. The selection is local state only.
- About Me textarea defaulting to a fixed lyric string, also repeated beside the avatar.
- Update Profile is `type="submit"` and the form has no `onSubmit`. No update API call was found in this file.
- Avatar `src` is `require("../assets/images/faces/placeholder.png")`. That file is not in the tree (the tracked image assets are `src/assets/images/logo.svg`, `src/assets/img/loading-bubbles.svg`, and `src/assets/fonts/nucleo-icons.svg`). **Implemented** import of a missing file.

React Router location objects expose `state`, not `data`. The users link sets `data` on `to`. Whether `useLocation().data` is ever the user id was not executed. **Unknown / Requires Confirmation**. If it is undefined, `getdetails` is still called with `{ id: undefined }`.

### 8. Dashboard

Trigger: `/app/dashboard`. Permission: `SUPER_ADMIN` or `ADMIN`.

The screen is a static Bootstrap card layout. No API call.

Visible figures and titles written in `src/components/Dashboard.js`:

- Number `150GB`, Revenue `$ 1,345`, Errors `23`, Followers `+45K`.
- Chart titles: "Users Behavior", "Email Statistics" (series 40 / 20 / 40), "2017 Sales".
- A tasks card, category "Backend development", whose first row text is `Sign contract for "What are conference organizers afraid of?"`. Edit and remove buttons have no `onClick`.

Provenance: **Implemented** sample UI. These figures are not loaded from the `unity` API.

### 9. Task list

Trigger: `/app/tasklist`. No permission restriction.

Two tasks are written in the component:

| id | task | tags |
|---|---|---|
| 1 | Sign contract for: What are conference organizers afraid of? | New Project |
| 2 | Work on Language changes for SAWIS | Frontend, Backend |

An "Add Task" button toggles a form with text fields `strTask`, `strDescription`, `strTags` and a Save Task button (`type="submit"`). There is no `onSubmit` and no API call. The tasks array is not state, so the form cannot append a row.

Star and priority icon state is one boolean for the whole list, not per task. The checkbox inputs are not wired to state.

The file imports `react-icon-checkbox` and `react-icons/bs`. Neither package is in `package.json`. A production build that typechecks or bundles this module will fail until those packages exist. **Implemented** import. Whether a developer machine has them installed outside the lockfile is **Unknown**.

### 10. Placeholder screens

| Path | Component | What renders |
|---|---|---|
| `/app/booking` | `Booking.js` | Jumbotron title "Booking" and the default lorem ipsum description from `JumbotronWrapper` |
| `/app/kanban` | `Kanban.js` | Title "Kanban" and a link to `/login` |
| `/app/profile` | `Profile.js` | Title "Profile" |
| `/app/configer` | `Configer.js` | Title "Developer Tools / Examples", links to staff and cron jobs |
| `/app/configer/staff` | `Staff.js` | Title "Staff" |
| `/app/configer/cronjobs` | `CronJobs.js` | Title "CronJobs" |

`JumbotronWrapper` default description is a long lorem ipsum paragraph. Title is required.

`Kanban_data.json` holds lanes `PLANNED` and further cards (the first card is "Buy milk"). `Kanban.js` does not import it. `react-trello` is a dependency and is not imported. A kanban board is **not implemented**. The JSON file is **Unused**.

Profile's permission list includes `CUSTOMER`. The component does not read `userDetails`.

### 11. Quotes and pivot (not routed)

`Quotes.js` is exported from `src/components/index.js` and is not referenced by `PrivateRoutesConfig.js`. It POSTs the same `/unity/getusers/1` call as Users and renders the same columns, including EDIT. It is unreachable from the router. Provenance: **Unused**.

`Pivot.js` is a pivot-table demo (sample dataset "Tips", CSV drop, textarea parse) and is not mounted. `PrivateRoutesConfig.js` imports `components/PivotTable` and does not put it in the route array. Provenance: **Unused**.

## Integrations

| System | Purpose | Trigger | Direction | Auth | Response the client expects | Failure behaviour |
|---|---|---|---|---|---|---|
| Amazon Cognito | Sign in | Login button | Client → Cognito via Amplify `Auth.signIn` | Email and password. Pool ids in `src/aws-config.js`. `mandatorySignIn: true` | A session whose `username` is sent to the uuid API | `console.log`, then still `history.push('/app')` |
| API Gateway, Amplify name `unity` | User row for the Cognito username | After `Auth.signIn` resolves | `GET /unity/uuid/{username}` | Amplify Auth session. Host in `src/aws-config.js`, region `eu-west-1` | `{ body }` JSON string; element `[0]` with `strRole`, `UserID`, `strUser` | Returns `{ result: "Api Error" }` and the caller still writes storage |
| Same API | Calendar range | FullCalendar `datesSet` | `POST /unity/getcalendarevents/` body `{ userID, dtStartDate, dtEndDate }` | Amplify session. `unityRequests` calls `Auth.currentSession()` and does not use the return value | `{ body }` JSON string of event rows | `console.log`, events unchanged |
| Same API | Create / update / delete event | Calendar add, change, remove | `POST /unity/addcalendarevent/`, `/unity/updatecalendarevent/`, `/unity/removecalendarevent/` | Same | Add path reads `body.id` | `revert()` the calendar change and `console.log` |
| Same API | User table | Users mount, and Quotes mount if that module loaded | `POST /unity/getusers/1` | Same | `{ body }` JSON array of user rows | `console.log`, empty table |
| Same API | One user | User form mount | `POST /unity/getdetails` body `{ id }` | Same | `{ body }` JSON array; element `[0]` | `console.log`, form stays on `{}` |
| Amazon S3 | Configured bucket `unity-lite-files` | App start | Config only | Identity pool id in `src/aws-config.js` | No `Storage.get` / `Storage.put` call was found | Not applicable in this tree |

The Lambda or other handler behind API Gateway is not in this repository. Contract details beyond the paths and field names above are **Unknown / Requires Confirmation**.

`unityRequests` (`src/components/unityRequests.js`): default `action` is `"post"`, which calls `API.post(apiName, path, { body: vars })`. The `"get"` branch calls `API.get(apiName, path, vars)` and is not used by any current caller. Comparison uses `==`.

## Constraints

- **Documented:** README says the app is Unity without styling and without Redux, started with yarn.
- **Implemented:** React 16 and `react-scripts` 3.0.1. No Redux dependency in `package.json`. Bootstrap and W3.CSS are present, so "without styling" means without a separate theme layer, not without CSS. **Inferred** from the README wording plus the stylesheets that are actually linked.
- **Implemented:** private routes exist only under `/app`.
- **Implemented:** `MAX_ATTACHMENT_SIZE` is 5000000 in `src/aws-config.js` and is not read by any other file.
- **Implemented:** service worker stays unregistered.
- Region, bucket name, and endpoint name are fixed in `src/aws-config.js`. No environment file is tracked. `.gitignore` does not mention `.env`.

## Functional requirements

### FR-001 — Public landing

Requirement: A visitor with no `roles` key who opens a path other than `/login`, `/register`, `/forgot-password`, or `/app` sees the landing page, with nav links to login and register, and the silicon overdrive credit.

Trigger: browser navigation. Actor: anonymous visitor.

Evidence: `LandingPage.js`, `PublicRoutes.js`, `Auth.js`.

Implementation: **Implemented**. Confidence: High.

### FR-002 — Sign-in attempt

Requirement: The login screen collects email and password and, on Login, calls Cognito and then `GET /unity/uuid/{username}`. It stores `strRole`, `UserID`, and `strUser`, then navigates to `/app`. Navigation also happens when Cognito throws.

Inputs: email, password. The change handlers do not copy typed text into state (see workflow 2).

Validation: none in the component.

Error: API failure is swallowed into `{ result: "Api Error" }`. Cognito failure is logged. Neither shows an alert in the UI.

Evidence: `Login.js`.

Implementation: **Implemented**, with the input-handler and storage-format behaviour described above. Confidence: High for the control flow. The live API payload shape is **Unknown**.

### FR-003 — Role-gated navigation

Requirement: Under `/app`, the nav and the switch include a route with no `permission` for every signed-in visitor. Config requires `SUPER_ADMIN`, `ADMIN`, or `MANAGER`. Dashboard requires `SUPER_ADMIN` or `ADMIN`. Profile requires one of those three or `CUSTOMER`.

Evidence: `PrivateRoutesConfig.js`, `utils/index.js`, `PrivateRoutes.js`, `TopNav.js`.

Implementation: **Implemented**, provided `JSON.parse(localStorage.roles)` returns an array. Confidence: High for the filter. Confidence in the stored format: Low. **Requires Confirmation**.

### FR-004 — Calendar CRUD against the unity API

Requirement: The signed-in user can load events for the visible range, add an event by selecting dates and entering a title, move or resize an event, and delete an event after confirm. Writes use the field names in workflow 5. Failed writes revert the calendar edit.

Evidence: `Calendar.js`, `unityRequests.js`, `event-utils.js`.

Implementation: **Implemented**. Confidence: High for the client calls. Server rules (who may edit whose events, required fields) are **Unknown**.

### FR-005 — User list

Requirement: `/app/users` loads users from `POST /unity/getusers/1` and shows name, telephone, job title, role, email, location, and an EDIT control.

Evidence: `Users.js`.

Implementation: **Implemented**. The meaning of `1` is **Unknown**. Confidence: High for the client call.

### FR-006 — User edit

Requirement: The user screen loads one record with `POST /unity/getdetails` and shows an edit form. The Update Profile control does not call an API.

Evidence: `User.js`.

Implementation: **Partial**. Load is implemented. Save is not. The id source (`location.data`) **Requires Confirmation**.

### FR-007 — Placeholder modules

Requirement: Booking, kanban, profile, staff, and cron jobs are reachable (staff and cron jobs only through Config, and only for roles allowed on Config) and render a title. They do not load or save domain data.

Evidence: the components named in workflow 10.

Implementation: **Implemented** as placeholders. A fuller booking, kanban, profile, staff, or cron product is **not implemented** and is **not documented** as a plan, except the README TODO about component approach, which does not name these screens.

### FR-008 — Logout

Requirement: Logout removes the `roles` key and returns to `/`. It leaves `userDetails` in place and does not call Cognito sign-out.

Evidence: `TopNav.js`.

Implementation: **Implemented**. Confidence: High.

## Edge cases

| Case | Behaviour in this tree | Provenance |
|---|---|---|
| Typed email or password | Change handlers replace state with `[]` | **Implemented** |
| Cognito error | Logged; browser still goes to `/app`; private shell bounces home if `roles` was not set | **Implemented** |
| Uuid API error | `{ result: "Api Error" }` stored as a missing `strRole`; `roles` key becomes the string `"undefined"` | **Inferred** |
| `strRole` is a plain word, not a JSON array | `getAllowedRoutes` throws on `JSON.parse` | **Inferred** |
| Logout then open `/app` | Redirect to `/` because `roles` is gone. `userDetails` remains. Amplify may still hold a Cognito session | **Implemented** for localStorage. Cognito session after logout is **Unknown** (no `Auth.signOut`) |
| Duplicate event create | No client-side de-dupe. Each prompt calls add | **Implemented** |
| Empty event title | Prompt cancel or empty string does not add | **Implemented** |
| Calendar write failure | `revert()` | **Implemented** |
| Users or calendar when logged in without `userDetails` | `getUserDetails()` parses `null` and throws on the calendar path. The users list does not read `userDetails` | **Inferred** |
| Missing avatar file | `User.js` requires `faces/placeholder.png`, which is not tracked | **Implemented** import, missing file |
| Task list dependencies missing | Imports packages absent from `package.json` | **Implemented** |
| Unknown URL under `/app` | `NotFound` | **Implemented** |
| Unknown URL outside `/app` while logged out | Landing page (empty path match) | **Implemented** |
| Logged-in visit to `/login` | Redirect to `/app` | **Implemented** |
| Service worker / offline | Unregistered. No offline behaviour | **Implemented** |

## Planned (comments only, not an approved backlog)

- README TODO: "Design same approach for components". **Documented**. No design is in the tree.
- `PrivateRoutesConfig.js` TODO: optional title, title as a node, child route support. Child routes for Config already exist in `Configer.js`. **Documented** TODO, partly already **Implemented**.
- `Auth.js` TODO: logged-in users should be unable to open public routes, and the logged-in check should be updated. The redirect already sends a logged-in user from public routes to `/app`. The comment calls the check temporary. **Documented** and **Implemented**.
- `Login.js` contains a commented role multi-select marked "TODO: Dynamic role options". **Documented** as a comment. Not rendered.

No issue tracker, roadmap file, or milestone date exists in the repository.
