---
name: "LUSA Back Planner"
client: L'Oréal USA
status: active
server: optical-web-1
tech: [React, TypeScript, Vite, Tailwind CSS, shadcn/ui, pdfjs-dist, jsPDF, Azure AD / MSAL]
local_path: /Users/ai_leed/Documents/Projects/Oliver/lusa-back-planner
deploy: bash deploy.sh
url: https://ai-sandbox.oliver.solutions/lusa-Back-Planner
tags: [lusa, timeline, planner, pdf, react, typescript]
created: 2026-04-14
port: 80
service: apache2
---

## Overview

LUSA Back Planner is a client-side React SPA that generates production timelines for L'Oréal USA creative teams. Users upload a PDF brief (or manually enter details), select a production lane (Rush/Express/Production/Specialist), and the app instantly generates a workback timeline with milestones calculated from the deadline by subtracting business days. It supports PDF parsing, inline timeline editing, change request workflows, and PDF export—all running entirely in the browser with no backend.

## Tech Stack

- **Frontend:** React 18 + TypeScript, Vite (build tool)
- **Backend:** None (fully client-side)
- **Database:** None (all logic in browser)
- **Infrastructure:** Apache httpd on Ubuntu server (optical-web-1); static site deployment
- **AI/ML:** N/A
- **Key libraries:**
  - `@azure/msal-react` — Azure AD SSO authentication
  - `pdfjs-dist` — client-side PDF parsing (brief & timeline uploads)
  - `jsPDF` + `html2canvas` — PDF export
  - `react-router-dom` — client-side routing
  - `date-fns` — date/time utilities
  - `shadcn/ui` — accessible UI components (built on Radix UI)
  - `tailwindcss` — styling

## Architecture

**Component Tree:**
```
main.tsx
 └── AuthGuard (MSAL login page if unauthenticated)
     └── MsalProvider
         └── App (BrowserRouter with basename)
             ├── Index.tsx (main form & state orchestration)
             ├── TimelineView.tsx (render & edit milestones, PDF export)
             ├── ChangeRequestTab.tsx (upload existing timeline, re-export)
             └── shadcn/ui components (form, dialogs, inputs, etc.)
```

**Data Flow:**

1. User lands on app → `AuthGuard` checks MSAL session state
   - If unauthenticated: shows login page with "Sign in with Microsoft" button (popup flow)
   - If authenticated: renders `App`
2. User uploads PDF brief → `parse-brief.ts` extracts due date, brand, and lane suggestion using keyword proximity scoring and multi-regex date detection
3. User confirms deadline + lane → `templates.ts:generateTimeline()` subtracts workdays (via `workdays.ts`) from deadline to create milestones
4. `TimelineView` renders milestones with inline editing; user exports as PDF via `jsPDF` + `html2canvas`
5. Change request flow: upload existing timeline PDF → `parse-timeline-pdf.ts` extracts existing milestones → user selects update reason → generate new PDF

**Key Files & Responsibilities:**

| File | Purpose |
|------|---------|
| `src/pages/Index.tsx` | Main page; owns form state (lane, deadline, revision count), orchestrates child components |
| `src/lib/templates.ts` | Lane definitions (Rush/Express/Production/Specialist); `generateTimeline()` function creates milestones |
| `src/lib/workdays.ts` | Business day math: federal holiday set, `subtractWorkdays()`, `countWorkdays()`, weekend/holiday detection |
| `src/lib/parse-brief.ts` | Extracts due date, brand, project name from uploaded PDF briefs using pdfjs-dist |
| `src/lib/parse-timeline-pdf.ts` | Parses existing timeline PDFs for change request workflow |
| `src/lib/sla-data.ts` | SLA durations for 90+ asset types |
| `src/lib/brands.ts` | 38 L'Oréal brand definitions with metadata |
| `src/lib/msal-config.ts` | MSAL instance config; reads tenant, client ID, and redirect URI from env vars |
| `src/components/TimelineView.tsx` | Renders timeline, handles inline milestone editing, PDF export |
| `src/components/ChangeRequestTab.tsx` | Change request tab: upload PDF, select update reason, re-export |
| `src/components/AuthGuard.tsx` | Wraps entire app; enforces Azure AD login, shows login page to unauthenticated users |
| `src/components/ui/*` | shadcn/ui primitives (do not edit manually) |

## Dev Commands

```bash
# Start dev server (http://localhost:5173)
npm run dev

# Build for production (outputs to dist/)
npm run build

# Build in development mode
npm run build:dev

# Preview production build locally
npm run preview

# Run linter
npm run lint

# Run tests (single run)
npm run test

# Run tests in watch mode
npm run test:watch

# Run a specific test file
npm run test -- src/test/example.test.ts
```

## Deployment

- **Server:** optical-web-1 (Apache httpd)
- **Deploy:** `bash deploy.sh` (run on server at `/opt/lusa-back-planner`; script builds, clears Vite cache, copies dist/ to `/var/www/html/lusa-Back-Planner`)
- **URL:** https://ai-sandbox.oliver.solutions/lusa-Back-Planner
- **Port:** 443 (HTTPS via Apache)
- **Service:** Apache httpd (no custom systemd service)
- **Local path:** /Users/ai_leed/Documents/Projects/Oliver/lusa-back-planner

**Deploy steps:**
1. SSH to optical-web-1
2. Navigate to `/opt/lusa-back-planner`
3. `git pull origin main` (or your branch)
4. `bash deploy.sh`
5. Script builds, clears `.vite/` cache, copies `dist/` to `/var/www/html/lusa-Back-Planner`

**Apache config** (in `apache.conf`):
- VirtualHost on port 443 (HTTPS)
- DocumentRoot: `/var/www/html/lusa-Back-Planner`
- SPA fallback: `FallbackResource /index.html` (routes client-side via `BrowserRouter` with `basename="/lusa-Back-Planner"`)
- Cache headers: immutable for JS/CSS/images

## Environment Variables

All env vars use the `VITE_` prefix to be accessible in the browser at build time.

| Variable | Description | Default / Example |
|----------|-------------|-------------------|
| `VITE_AZURE_TENANT_ID` | Azure AD tenant ID | `e519c2e6-bc6d-4fdf-8d9c-923c2f002385` |
| `VITE_AZURE_CLIENT_ID` | Azure AD app registration client ID (public) | `9079054c-9620-4757-a256-23413042f1ef` (production) or `15c0c4e2-bac0-4564-a3a6-c2717f00a6d9` (local dev) |
| `VITE_AZURE_REDIRECT_URI` | OAuth redirect URI after SSO | `https://ai-sandbox.oliver.solutions/lusa-Back-Planner` (production) or `http://localhost:8888/lusa-Back-Planner` (dev) |

**Note:** Tenant and client IDs are public (embedded in built JS); the sensitive secret is the Azure app registration's client secret, which is NOT in this repo.

## API / Endpoints

None. This is a fully client-side SPA with no backend API. All data processing (PDF parsing, timeline generation, date math) runs in the browser.

## Known Issues

- **No SSH to optical-web-1 without explicit user instruction** — always ask before connecting
- **PDF parsing limitations:** `parse-brief.ts` uses keyword proximity scoring, which can fail on non-standard brief layouts. Multi-regex date detection handles common US date formats but may not catch all edge cases.
- **PDF export:** avoid DOM mutations during canvas capture in `TimelineView` that could break `html2canvas` rendering
- **No offline support:** requires network access for Azure AD authentication
- **MSAL interaction_in_progress errors:** historically resolved in git commits 03fd4be–ed638ff; uses popup flow instead of redirect to avoid localStorage iteration issues

## Git

- **Remote:** git@bitbucket.org:zlalani/lusa-back-planner.git
- **Branch:** main (production-ready)
- **Recent commits:** Fix MSAL SSO, Apache config, deploy script executable bit, BrowserRouter basename for server subdirectory

## Sessions
### 2026-04-14 – Project catalogued
**Done:** Added to Obsidian second brain.

---
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 2026-04-14 | Initial setup | Note created | — |