SOP and Work Package localStorage keys are now namespaced by the active project via ProjectData.key(base) -> base+'__'+<projectId> (SK() in the suite, wpKey() in the creator), so switching projects shows that project's own SOP and packages. project-data.js runs a one-time discard of the legacy un-namespaced keys (guarded by wp_ns_migrated_v1), per the chosen approach. Active project is resolved before the store loads in both the suite and the creator so namespaced keys resolve correctly, including standalone deep-links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
199 lines
12 KiB
Markdown
199 lines
12 KiB
Markdown
# Project Handoff & History
|
|
|
|
A curated record of where this project is, the decisions behind it, and what's
|
|
left — so a teammate (or a fresh Claude Code session) can pick up cleanly. This
|
|
is a summary of the working sessions, not a verbatim chat transcript.
|
|
|
|
_Last updated: 2026-06-15._
|
|
|
|
## TL;DR — current status
|
|
|
|
The **Work Package Suite** is an internal tool for Prime Controls construction
|
|
projects: configure a project **SOP**, then author **Work Packages** against it.
|
|
|
|
- **Front end:** static HTML/CSS/JS, no build step. Two tools surfaced from the
|
|
home page — *SOP Configuration* and *Work Package Creator* — both living in one
|
|
app ([work-package-suite.html](work-package-suite.html)) as two tabs. The
|
|
Creator is embedded via `<iframe>` ([wp-creation-index.html](wp-creation-index.html)).
|
|
- **Back end (NEW, Phase 1 done):** a Python/FastAPI API + PostgreSQL under
|
|
[`server/`](server/) for shared storage of SOPs, Work Packages, and comments.
|
|
- **Hosting target:** internal, behind the company firewall. **NGINX** serves the
|
|
static site and proxies `/api/` to the Python API. No outbound internet calls.
|
|
- **Next up (Phase 2, NOT started):** rewire the front end to read/write the API
|
|
instead of browser `localStorage`.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
browser → NGINX ──serves──> static site (index.html, work-package-suite.html, …)
|
|
└─proxy /api/─> Python API (FastAPI, uvicorn/gunicorn :8000) → PostgreSQL
|
|
```
|
|
|
|
Today the front end still stores data in `localStorage`; Phase 2 moves it to the
|
|
API. The API is already built and testable on its own.
|
|
|
|
## Key decisions (and why)
|
|
|
|
| Decision | Choice | Rationale |
|
|
|----------|--------|-----------|
|
|
| Tool structure | One app, two tabs (SOP + WP Creator) | Shared state; SOP flows straight into the Creator. Standalone SOP tool was deleted as a duplicate. |
|
|
| WP Creator integration | Embedded as an `<iframe>` | The real Creator is a separate, complex app; iframing avoids JS global collisions and reuses it as-is. |
|
|
| Discipline on WP types | Removed everywhere | Per request — WP types now carry Enabled / Special Rules / WO Complete Approval only. Also removed the derived Discipline field + trade-code in the Creator. |
|
|
| Hosting | Internal **NGINX**, behind firewall | Company requirement. Considered IIS (dropped) and SharePoint (can't host a multi-file app). |
|
|
| Database | **PostgreSQL** | Site admin is provisioning it; good JSON support for SOP/WP documents. Code uses SQLAlchemy, so it's portable. |
|
|
| Identity | **Name field only** | Trust the internal network; users type their name. No login. |
|
|
| Client data model | **Server-backed** (Phase 2 goal) | SOPs/WPs/comments shared across users; `localStorage` becomes a draft cache. |
|
|
| Comments transport | API → Postgres | Replaces an earlier Power Automate plan; comments now persist to SQL directly. |
|
|
|
|
## Timeline (by commit)
|
|
|
|
| Commit | What it delivered |
|
|
|--------|-------------------|
|
|
| `c583daa` | Initial import of the WP Suite files. |
|
|
| `2af4b58` | Consolidated to two tools (one app, two tabs); restored Rev 0 features — white header, single title, Step Comments in header, usage logs, sample data, APM in Team + Sign-offs, drag-and-drop sequencing, pre-loaded deletable sources, two-card home page, embedded WP Creator. |
|
|
| `d2dc6ff` | Removed discipline from WP types and the Creator; removed external Google-Fonts call (firewall hardening); added Export/Import + a central-feedback hook + `DEPLOYMENT.md`. |
|
|
| `7186d46` | First pass wiring feedback through an IIS reverse proxy to Power Automate. |
|
|
| `3d4402c` | Switched that reverse proxy from IIS to **NGINX**. |
|
|
| `a33b777` | **Phase 1 backend**: FastAPI + SQLAlchemy + PostgreSQL (`server/`); NGINX now proxies `/api/` to it; comments persist to SQL; docs rewritten. |
|
|
|
|
## Repo map
|
|
|
|
- [index.html](index.html) — home page (two tool cards; SOP card turns green/"Review" when complete).
|
|
- [work-package-suite.html](work-package-suite.html) / [work-package-suite-app.js](work-package-suite-app.js) / [work-package-suite-styles.css](work-package-suite-styles.css) — the SOP Configuration tool + WP tab host.
|
|
- [wp-creation-index.html](wp-creation-index.html) / [wp-creation-app.js](wp-creation-app.js) / [wp-creation-styles.css](wp-creation-styles.css) — the embedded Work Package Creator.
|
|
- [feedback-config.js](feedback-config.js) — `FEEDBACK_ENDPOINT` (`/api/feedback`) + `postFeedback()` helper.
|
|
- [`server/`](server/) — FastAPI API (`app.py`), ORM models (`models.py`), DB setup (`db.py`), `requirements.txt`, `.env.example`, and [server/README.md](server/README.md).
|
|
- [nginx-wp-suite.conf](nginx-wp-suite.conf) — NGINX site config (static + `/api/` proxy).
|
|
- [DEPLOYMENT.md](DEPLOYMENT.md) — end-to-end deploy guide.
|
|
|
|
## How to run / deploy
|
|
|
|
- **API + database:** see [server/README.md](server/README.md) (Postgres setup, dev `uvicorn`, prod `gunicorn` + systemd). API docs at `/api/docs`.
|
|
- **Front end + proxy:** see [DEPLOYMENT.md](DEPLOYMENT.md) and [nginx-wp-suite.conf](nginx-wp-suite.conf).
|
|
- Locally the API runs against a SQLite file with zero config, so it can be tried without Postgres.
|
|
|
|
## What's done vs. pending
|
|
|
|
**Done**
|
|
- Two-tool consolidation, all Rev 0 feature restorations, discipline removal.
|
|
- Firewall hardening (no external calls).
|
|
- Export/Import feedback on every surface.
|
|
- Phase 1 backend (API + schema) + NGINX proxy + deploy docs.
|
|
- **Discipline strategy, sizing, split & dashboard** (branch `feat/wp-discipline-split-dashboard`) — see below.
|
|
|
|
## Discipline strategy, WP sizing, Split-by-Discipline & Dashboard
|
|
|
|
Discipline came back — but as a *project policy* set in the SOP, not a fixed
|
|
field. The Governance step (SOP config, step 5) now also captures:
|
|
|
|
- **Disciplines** (default Mechanical / Electrical / Tech) — comma list.
|
|
- **Discipline strategy** (`governance.discMode`): `single` (one discipline per
|
|
WP), `multi` (one WP bundles disciplines, scope split per discipline), or
|
|
`choice` (planner decides per package — build big, split later).
|
|
- **Split threshold** (`governance.sizeHoursMax`) — the Creator warns when a
|
|
package's est. hours exceed it.
|
|
- `governance.instanceSuffix` = `letter` (instances get A/B/C suffixes).
|
|
|
|
In the **WP Creator** ([wp-creation-app.js](wp-creation-app.js)):
|
|
|
|
- A **Disciplines** picker (hidden unless the SOP defines disciplines). Pick 2+
|
|
and the single flat work-step list becomes **per-discipline scope sections**,
|
|
each with its own steps and its own status (e.g. *Issued — Electrical* while
|
|
*Mechanical* is still *In Progress*). Overall WP status rolls up to the
|
|
least-advanced discipline.
|
|
- **⎘ Split by Discipline** turns a multi-discipline package into one instance
|
|
per discipline: `WP01-…` → `WP01A` (Mech), `WP01B` (Elec), `WP01C` (Tech).
|
|
Each instance is a single-discipline WP linked to the master via `instanceOf`
|
|
/ `parentNumber`; the master is kept as a roll-up (`split:true`, `children:[]`).
|
|
- New per-WP fields on the saved object: `disciplines`, `scope` (`{discipline:
|
|
[steps]}`), `discStatus`, `instanceOf`, `instanceLabel`, `parentNumber`,
|
|
`split`, `children`.
|
|
|
|
The **Dashboard** (📊 in the Creator header; home card *Work Package Dashboard*;
|
|
deep-link `work-package-suite.html?view=dashboard` or `…?view=dashboard#…`):
|
|
metrics (total / release-ready / on-hold / overdue / est vs actual hrs),
|
|
breakdowns by status & discipline, a **gating panel** (what's blocking each
|
|
package), and a filterable board with **view / edit / issue** per package.
|
|
Masters are excluded from counts so split hours aren't double-counted.
|
|
|
|
Data source today is `localStorage` via the `WPData` adapter in
|
|
[wp-creation-app.js](wp-creation-app.js) — swap `list()`/`issue()`/`setStatus()`
|
|
to `fetch('/api/wps…')` in Phase 2 and the UI is unchanged.
|
|
|
|
**Backend** ([server/](server/)) gained the matching endpoints:
|
|
`POST /api/wps/{id}/issue` (refuses if constraints are open — the AWP gate),
|
|
`POST /api/wps/{id}/status`, and `GET /api/wps/metrics`. `work_packages` gained
|
|
`parent_id` and `issued_at` columns.
|
|
|
|
> **Migration caveat:** tables are still auto-created on startup, so the new
|
|
> `parent_id` / `issued_at` columns appear on a **fresh** DB only. Before there's
|
|
> real data this is fine; once there is, add Alembic (see open question #2) and
|
|
> migrate rather than relying on `create_all`.
|
|
|
|
## Multi-project support
|
|
|
|
> **Note on layout:** the IT admin moved all static files into **`html/`** and
|
|
> added a Docker/NGINX deployment (`Dockerfile`, `docker-compose.yml`, `nginx/`).
|
|
> Front-end paths below are under `html/`. `server/` stayed at the repo root.
|
|
|
|
The suite is now multi-project. **Projects are the top-level container**; every
|
|
SOP and Work Package belongs to one.
|
|
|
|
- **Backend:** new `projects` table + CRUD (`/api/projects`). `sops` gained
|
|
`project_id` (FK, cascade) and `work_packages` gained `project_id`; list/latest/
|
|
metrics endpoints accept a `project_id` filter.
|
|
- **Project layer:** [html/project-data.js](html/project-data.js) — a shared,
|
|
**API-first** `ProjectData` adapter (`list/get/save/remove` hit `/api/projects`)
|
|
that **falls back to a localStorage mirror** (`wp_projects`) when the API is
|
|
unreachable, plus active-project helpers (`getActive`/`setActive`, stored in
|
|
`wp_active_project` / `wp_active_project_obj`).
|
|
- **Home page** ([html/index.html](html/index.html)): "About This Suite" removed;
|
|
a **Project** picker added. With no projects it offers *Create Project* / *Use
|
|
Sample Project*; otherwise a dropdown to select. The tool cards stay hidden
|
|
until a project is active and then carry `&project=<id>`; the hero shows the
|
|
active project.
|
|
- **Suite** ([html/work-package-suite-app.js](html/work-package-suite-app.js)):
|
|
reads `?project=<id>`, resolves it via `ProjectData`, shows it in the header,
|
|
and **prefills the SOP project fields** (step 1) from the project record when
|
|
empty. Passes `&project` into the WP-creator iframe.
|
|
- **WP creator:** stamps `projectId` onto every saved package (for API sync).
|
|
|
|
**Per-project isolation (done, local):** SOP/WP localStorage keys are now
|
|
namespaced per active project via `ProjectData.key(base)` →
|
|
`base + '__' + <projectId>` (`SK()` in the suite, `wpKey()` in the creator).
|
|
So each project keeps its own `wp_suite_sop` / `wp_suite_state` /
|
|
`wp_suite_sop_complete` / `wp_iwp_v1`. On first load after this change,
|
|
`project-data.js` runs a **one-time discard** of the legacy un-namespaced keys
|
|
(guarded by `wp_ns_migrated_v1`) — chosen over migrating, since the local data
|
|
was throwaway demo content.
|
|
|
|
**Still ahead (true Phase 2):** move SOP/WP reads+writes to the API filtered by
|
|
`project_id` (`GET /api/sops/latest?project_id=…`, `GET /api/wps?project_id=…`)
|
|
so projects are shared across users, not just isolated per browser. The
|
|
endpoints already accept the `project_id` filter; the front end still reads
|
|
localStorage.
|
|
|
|
**Pending — Phase 2: wire the front end to the API**
|
|
- SOP: on *SOP Complete*, `POST /api/sops`; on load, `GET /api/sops/latest` to hydrate the Creator (currently uses `localStorage` key `wp_suite_sop`).
|
|
- WP Creator: save packages via `POST /api/wps`; list/load via `GET /api/wps`
|
|
(currently `localStorage`). It reads the active SOP from `GET /api/sops/latest`.
|
|
- Comments already post to `/api/feedback` → the API; the *list* views could be
|
|
switched from `localStorage` to `GET /api/comments`.
|
|
- Keep `localStorage` as an offline draft cache / fallback.
|
|
|
|
## Open questions for whoever continues
|
|
|
|
1. **Projects:** the DB supports many SOPs, but the UI assumes one active SOP
|
|
("latest"). Do we need a project picker?
|
|
2. **Migrations:** tables are auto-created on startup. Before real data, decide
|
|
on Alembic for future schema changes.
|
|
3. **Power App view:** if still wanted, point it at the Postgres `comments` /
|
|
`work_packages` tables via the on-prem data gateway (no app change needed).
|
|
4. **Backups / retention** for the Postgres database (ask the site admin).
|
|
|
|
## Continuing with Claude Code
|
|
|
|
Open this repo in Claude Code and start with: *"Read ONBOARDING.md and DEPLOYMENT.md,
|
|
then help me with Phase 2 — wiring the front end to the API."* The commit messages
|
|
above are a reliable trail of what changed and why.
|