Commit Graph

16 Commits

Author SHA1 Message Date
d3d2a46922 T2.2 (part 1) - S4: mount the shared drawer on the launcher, SOP wizard and creator
PARTIAL. This is the mounting half of T2.2 and it closes S4. The other half -
deleting the flat nav strip, unifying the brand treatment, and removing the T1.2
interim - is NOT in this commit. See below.

S4 is that global nav was missing on the two pages people actually live in. The
drawer already handled everything needed to fix that: it self-mounts into
.wp-appbar OR .header, gates its links by role, carries the active ?project=,
and returns early inside an iframe. So this is two tags per page, not a port -
the component was built for this and was simply never included.

Verified on all 6 pages at 390px and 1440px: the menu button and the drawer
exist, both carry the same 8 links, the drawer opens fully on screen, its first
link is hit-testable via elementFromPoint rather than merely present, aria-hidden
flips correctly, and Escape closes it. Drawer links measure 44px tall, which is
the tap-target size T2.2 asks for on the Field View.

The embedded creator correctly does NOT get a second drawer: wp-sidenav.js
returns early when framed, and the SOP wizard hosting it already has one.

Deliberately NOT done here, and still open on T2.2:
  - the flat nav strip is not deleted, so the drawer and the strip currently
    duplicate Home, Admin, Users and Sign out
  - the creator's mono wordmark is not yet unified to the logo chip
  - the T1.2 interim is still in place and must be removed, not layered on
Splitting it this way keeps a working app at every commit: mounting is additive
and regression-free, whereas deleting the strip changes where several
destinations live and wants its own diff. T2.3 (B2 breakpoints) is untouched.

browser_check 71/71. f_items unchanged: F1-F5 FIXED, F6 untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 20:46:07 -05:00
e3527a6e1d Act on the fragility audit: boot-order crash, real cache correctness, deep links
A 55-agent audit of the last few commits confirmed 32 findings. The high and medium
ones are fixed here; the ranked leftovers are listed at the end.

Boot-order crash (my regression, wave 2)
- wp-format.js loaded AFTER wp-creation-app.js on every page, but the creator boots
  synchronously at parse time and its comment renderer calls wpFormatDateTime(). With
  any review comment present that threw a ReferenceError and aborted the rest of boot.
  The formatter now parses before the app scripts on all five pages. Verified with a
  comment seeded: the date renders and boot completes.

The network-first fix didn't actually work
- `fetch(req)` inherits the request's default cache mode, so it consults the browser
  HTTP cache — the previous commit's "network-first" still allowed a page to run
  against a stale sibling. Code is now fetched with cache:'no-cache' and precached
  with cache:'reload'.
- Nothing pinned freshness on the wire either: no Cache-Control anywhere, so browsers
  applied heuristic caching (~10% of a file's age) and each file expired at a
  different moment. NGINX and the dev server now send no-cache for html/css/js/
  webmanifest; images stay cacheable. Verified on the wire.
- Non-ok responses were returned verbatim, so a 502 broke pages the cache could have
  served; they now fall back to the cache. Cache keys drop the query string, which
  fixes both the offline miss on every in-app link (?project=…&tab=…) and unbounded
  cache growth. respondWith can no longer resolve to undefined. Cache bumped to v5.

Embedded creator
- Dropped the &t=Date.now() cache-buster and made the frame's identity the PROJECT.
  The view and which package to open are now applied by calling into the loaded
  document, so switching tabs no longer reloads it — that reload discarded unsaved form
  edits, made the creator unreachable offline, and stored a fresh copy per click.
- ?view=dashboard was re-read on every tab switch, so after one deep link the
  "Work Package Creation" tab kept opening the Dashboard for the rest of the session.
  Deep-link params are consumed once now.
- ?wp=<id> — which the global search has been emitting since wave 2 — was read by
  nothing, so picking a work package in search opened a blank one. The creator now
  exposes openWpById() and the shell applies it after a new 'wp-creator-ready' event,
  because the frame's load fires before pullProject() resolves.
- Math.max(320,…) could make the frame taller than the space available while page
  scrolling was disabled, pushing content off a window that couldn't scroll. Full-bleed
  is now only used when at least 460px remains, and the SOP-incomplete gate never runs
  inside it. A ResizeObserver re-measures when wp-chrome.js grows the app bar.

Contract drift
- .field-hint and .user-pick are used on the SOP suite page but their only rules lived
  in wp-creation-styles.css, which that page doesn't link — the CM hint and the
  sign-off pickers had no styling at all. Rules added to the suite's stylesheet.
- The creator's critical floor now also hides modal overlays (a stale stylesheet
  rendered their contents inline in the form) and gives the jump bar a sane sticky top.
- login.js dereferenced ids unguarded where the old version guarded, so a cached older
  login.html would break sign-in itself. Guarded.
- The "Language & time" menu item was added only if wp-format.js had already parsed;
  the check now happens at click time.

Verified: 157 API checks across five suites on a clean database, plus 22 driven UI
checks — boot-with-comment, tab switching with a no-reload probe, short-viewport
fallback, and the search deep link landing on the right package.

Not done, ranked: ~50 dead CSS rules across three stylesheets; dead .team-pick and
.constraint-option contracts; wp-chrome.js's documented '.header' mount branch is
unreachable because the creator loads neither wp-chrome.js nor its CSS; the squeeze
half of the embed layout (.content-area.embed-full) is still CSS-only, which degrades
to the old narrow column rather than breaking; fingerprinted asset URLs would make a
mismatched pair unrepresentable rather than merely unlikely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:07:20 -07:00
917a728399 Rebuild the work-package side panel in the style of MS Planner
The auto-hiding drawer was the wrong model — a list you navigate by shouldn't appear
and disappear under the pointer, and its vertical text tab read as a stray artifact.
Replaced with a persistent side panel following the Planner reference:

- Collapse toggle at the top (the panel glyph, arrow flips), remembered across visits.
  Collapsed leaves a 56px icon rail where the coloured package badges are still
  clickable, rather than hiding the list entirely.
- One primary action: "+ New work package" with a split caret for Duplicate, Split by
  discipline and Export all.
- Icon nav with counts: My packages (owned by you), All packages, Needs attention
  (on hold or not release-ready), Dashboard. These filter the list below.
- Packages as rows with a colour-coded initial badge, number, subject and readiness
  state, still grouped by status, with a left accent bar on the current package.
  The badge colour is hashed from the WP number, so a package keeps its swatch
  instead of shuffling when another is added or deleted.
- The panel sits IN the layout: the form and the full-width chrome shift beside it
  rather than being overlaid.

Also, the reason it appeared as loose unstyled widgets in the middle of the form: the
panel's markup and its stylesheet are cached independently, so a browser can run new
markup against old CSS. Its essential layout (fixed position, width, the row/badge
flex, the collapsed rules) is now injected by wp-creation-app.js as a floor, inserted
first in <head> so the stylesheet still wins on everything it defines. Same lesson as
the iframe: a component whose CSS-missing state is "broken" rather than "plain" must
carry its own critical layout.

Verified with 25 driven checks in headless Chrome: persistence, the four nav links,
badge colours and text, view filtering, collapse/expand, the split menu, row selection
and highlighting — and, with wp-creation-styles.css removed from the page entirely, the
panel is still a fixed 288px side panel with the form shifted beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 21:47:40 -07:00
fcba74b584 Fix the WP navigator and the squeezed embedded layout; add per-project permissions
Layout — the reported "skinny scrolling windows"
- .content-area capped the whole suite at 1000px, so on a 1920 screen the embedded
  Work Package Creator ran in a ~930px column with its own scrollbar inside the
  page's. The wizard now caps at 1700px and the Creator/Dashboard tab goes
  full-bleed: the iframe fills the window below the app chrome and owns the only
  scrollbar. Needed `flex: none` on the content area — as a `flex: 1` item its
  flex-basis overrode `height`, leaving the used height indefinite so the child's
  `height: 100%` collapsed the iframe to its 150px default.
- The SOP wizard's fields were one per row; they now flow into ~340px columns.

Navigator — now an auto-hiding drawer
- It was a fixed 262px column that stole width from the form AND was hidden below
  1100px, so embedded (the normal path) it never appeared at all — that's the
  "broken side menu". It's now an overlay drawer behind a slim always-visible edge
  handle: hover or tap to open, move away / Escape / pick a package to close, or pin
  it to keep it open (pinned shifts the form and the page chrome across, and is
  remembered). A gutter keeps the handle off the section-nav chips.

Bugs found while checking the site over
- collectStepData() still read the SOP team fields as text inputs, but wave 1 made
  them account pickers — so it wrote a user ID into state.team.pm where the display
  NAME belongs, and the SOP would print `user_ab12…` as the PM. Now synced properly
  from the pickers.
- loadSampleData() set .value on those selects with fictional names; setting an
  unmatched value on a <select> silently does nothing, so the sample lost its team.
  It now stores them as names without an account, which the picker shows as
  "(no account)".
- My earlier CSS block replacement had deleted the SOP-chip, people-picker and
  critical-tag styles. Restored.

Same picker everywhere the SOP names someone
- Sign-off roles (step 3, required and optional) are account pickers now, storing
  userId alongside the name, so a signature belongs to an account that can be
  notified. Titles stay free text.

Per-project permissions (asked for: "change project permissions for individual users")
- project_members.role overrides the account's role on that project, so a PM on one
  job can be a Project User on another. Empty = inherit; app admin is admin
  everywhere. effective_role() feeds require_project_admin, so WP delete, completed-
  SOP edits and project delete are all judged per project.
- Project access is now its own column in the admin console (it was buried among the
  action buttons, which is why it couldn't be found), showing the project count per
  account; the dialog sets access plus the role on each project.
- The members endpoint reports each person's effective role on that project.

Verified: 157 API checks across five suites on clean databases (44 permissions +
22 password reset + 34 search/localization + 39 gates/notifications + 18 new
per-project permission checks), 16 drawer-behaviour + 4 pinned-mode UI checks driven
in headless Chrome, and probes confirming the team/sign-off pickers populate and no
longer corrupt state.team on step navigation. Screenshots reviewed at 1920x1080.

Service-worker cache bumped to v3 so browsers pick up the new shell.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 17:22:19 -07:00
b38348e6ae Wave 3: predecessor references with a release gate, and critical-constraint reopen alerts
Predecessors are real references now
- data.predecessors holds work-package ids, replacing a free-text SOP phase label
  that couldn't express "WP04 waits on WP02" and gated nothing. The SOP phase
  survives beside it as the descriptive "Sequence phase" field.
- readiness() has two gates: constraints clear AND every predecessor Closed. The
  banner, sticky bar, left rail, dashboard Gates column and the ready counters all
  reflect the second one.
- Enforced server-side by enforce_release_gates() on every path that sets a status
  — the plain upsert included, since that's how the browser and the offline outbox
  save. /issue and /status would otherwise have been ways around it.
- Cycles are refused directly and through a chain, with a message naming the
  package that already waits on this one. The Creator's picker also hides itself
  and its own descendants, so a cycle is hard to build in the first place.
- A deleted predecessor does not block: it would freeze everything downstream of a
  package someone removed.
- The gate is refusable, on purpose. Planners release ahead of upstream close-out,
  so an explicit reason (data.gateOverride) allows it, gets a gate_overridden audit
  event naming what was skipped, and prints on the package. A blank reason is not
  an override, and changing the predecessor set clears it. The dashboard won't
  release a blocked package at all — it points at the form where the reason is
  captured.

Critical constraints reopened after release
- Reopening a SOP-critical constraint on a released package emails the owner, PM,
  CM and the package's distribution list (minus whoever did it) and writes a
  constraint_reopened audit event.
- Detected by diffing the incoming constraints against the stored ones inside the
  normal upsert rather than via a new endpoint: the sync outbox only replays
  POST /api/wps, so a dedicated route would be lost offline. It fires only on a
  real cleared→open transition, so re-saving an already-open constraint doesn't
  re-announce, and never before release or for a non-critical constraint.
- Bodies carry the constraint name, WP number and a link — never package contents.

Verified: 139 API checks on one fresh database (44 permissions + 22 password reset
+ 34 search/localization + 39 gates/notifications), including every bypass path,
cycle shapes, the deleted-predecessor case, blank-reason overrides, and the four
recipients confirmed both in the outbox and on the wire against a local SMTP sink.
27 driven UI checks against the real Creator page in headless Chrome covering the
picker, the override prompt (accept and cancel), override invalidation, the cycle
exclusions and the dashboard refusal. Screenshots reviewed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 16:15:36 -07:00
61d1cf4bff Wave 2: form cleanups from the site comments, plus localization, project switcher and global search
Site comments (8/3)
- BIM card: LOD removed, IFF # added next to the coordination status, and required
  once that status is "Signed off (IFF)" — an unnumbered sign-off isn't traceable.
  A LOD already stored on a package is preserved and shown as legacy, not blanked.
- The blue "from SOP types" subtext under a field is now a SOP chip on the label
  with the detail in a tooltip. The chip stays visible rather than hover-only:
  field tablets have no hover, and "this came from the SOP" is the part that
  matters. The hint elements stay in the DOM (hidden) so the code writing to them
  keeps working; an observer mirrors their text into the tooltip.
- Specification Section is no longer typed per package. Each WP type carries a
  spec section on the SOP; the field is read-only in the Creator and follows the
  type, with the SOP's spec folder linked underneath. This reads both spec
  comments as one intent — stop typing it, derive it.
- Assignees and Distribution are multi-selects over the SOP project team, showing
  each person's job function, with the CM pre-added to Distribution (removable per
  package) and a free-text option for people with no account. The stored display
  strings are unchanged so print/export/dashboard keep working; account ids ride
  alongside for the notification work in wave 3.

Localization + time
- Per-user locale/timezone (Language & time in the user menu), an app-wide default
  in the admin console, then the browser. Timezones are validated against the
  server's zoneinfo and the picker is fed from it. Calendar dates are formatted
  from their parts so a due date never reads a day early in another zone.
- Every displayed timestamp now goes through the shared helpers.

Top-bar chrome
- Project switcher beside the logo and a centered global search, injected into
  either generation of top bar; skipped in an iframe so the embedded Creator
  doesn't get a second one. Ctrl/Cmd-K focuses search.
- GET /api/search covers work packages, projects and SOPs, scoped to the caller's
  projects, hiding archived packages, with LIKE wildcards escaped.

Fixed along the way: showForm() cleared every card's inline display, which undid
applyKind() — so the Package Type and BIM cards reappeared on an install-only
project. Split out applyKindVisibility() and re-apply it there.

Verified: 100 API checks on a fresh database (44 permissions + 22 password reset +
34 search/localization), 24 driven UI checks against the real Creator page in
headless Chrome (SOP chips, both people pickers, spec auto-fill, critical tags,
BIM suppression), and the chrome harness on both bar styles. Screenshots reviewed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 15:17:51 -07:00
1d004cab75 Widen the IWP screen and add a left work-package navigator
The Work Package form was capped at a 1000px column, which wasted most of a
desktop screen, and the only way to reach another package was to scroll to the
Saved table at the bottom.

- Put the form in a wide two-column shell (max 1760px); ctx-bar, mode-wrap and
  the release banner widened to match.
- Above 1200px the two-up field grids flow to 3-4 columns instead of stretching
  two fields across the whole card. Narrow layouts are unchanged.
- New sticky left rail listing every saved package, grouped by status in field
  order, with WP number, subject, readiness dot and type. Click to open it in
  the form; the package being edited is highlighted. Filter box, + New and
  Dashboard shortcuts, collapsible (state persisted), hidden under 1100px where
  the Saved table still covers navigation.
- The rail re-renders from renderSavedList(), so saves, deletes, splits,
  archive/restore and the project pull all keep it current.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 12:59:34 -07:00
39b48055ff Productionize WP Suite: auth, security hardening, sync, dashboard, PWA, email
Brings the Work Package Suite from a browser-local prototype to a
multi-tenant, SQL-backed deployment hardened for customer IP.

Auth & access control
- Local username/password login (bcrypt + JWT in an HttpOnly cookie),
  admin-managed users, per-project membership, and project-scoped API access.
- Admin console: change user roles, view the audit trail, manage settings.

Security hardening
- CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via
  X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping.
- Login lockout, token_version session revocation, stronger password policy,
  fail-closed secret loading, encrypted (AES-256) database backups.

Persistence & schema
- SOPs and Work Packages are now DB-backed and shared across users, written
  through a durable client sync outbox that queues offline edits.
- Alembic migrations applied automatically on container start.

New capabilities
- Phase 2 dashboard (progress, gating, pagination, archive).
- Phase 3 PWA "Field View" with offline caching and auth fallback.
- WP owner assignment with OPTIONAL email notifications, OFF by default and
  toggled from the admin console. SMTP password is read only from the
  SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link,
  never customer IP.

Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:51:15 -07:00
0e698438a2 BIM as a per-package kind; one project flows BIM -> construction
Replace the whole-project BIM 'mode' with an opt-in capability + a
per-package kind, so a single project can produce both model and
install packages (and install-only projects are unaffected).

SOP tool:
- Step 4 "Include BIM / VDC work packages" checkbox (state.bimEnabled).
  Enabling adds BIM package types + BIM release gates (flagged bim) plus
  BIM roles/sources/process steps alongside the construction defaults;
  disabling strips the bim-flagged items.
- Generated SOP carries bimEnabled and a per-type / per-constraint bim flag.
- Required sign-off role titles stay editable (no longer force-renamed).

Work Package Creator:
- Shows a Package Type selector (Install IWP / BIM EWP) only when the
  SOP has bimEnabled; kind is saved per package and labeled in the output.
- WP types and release gates are filtered by kind (BIM types+gates for
  EWP, install types+gates for IWP).
- EWP reveals the BIM Details card and hides controls.dev Assets /
  Materials / Kitting-MIMO; IWP shows those plus the "Enabled by - BIM
  package" traceability link.

Supersedes the earlier whole-project BIM mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 17:07:08 -07:00
566ace9969 Add BIM/VDC work packages, platform links, and AWP traceability
SOP tool:
- "Load BIM / VDC template" (Step 4): one click sets BIM deliverable
  types, install-phase disciplines, BIM release-gate constraints (with
  EN06 citations), the BIM process sequence, reference sources, BIM
  sign-off roles, and MWP##-[Area]-[PHASE] numbering. Tags the SOP mode
  as 'bim'.
- Step 3 required sign-off role titles are now editable (default
  Superintendent/Foreman); the BIM template sets them to BIM Coordinator
  and Construction Lead (CRS).
- Step 7: capture a project-homepage link for the chosen tracking and
  commissioning platforms.

Work Package Creator:
- BIM mode (SOP.mode==='bim'): hides controls.dev Assets, Material List,
  and Kitting/MIMO; shows a BIM Details card (LOD, model area, clash /
  coordination status, linked scan).
- Project tracking/commissioning links are copied onto every WP and
  shown in the WP output.
- "Enabled by - BIM package(s)" field links a field IWP back to the BIM
  package that enabled it (EWP -> IWP traceability).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:54:57 -07:00
20afb0e565 Add secure username/password login portal
Gate the suite behind a self-contained login (no external IdP):

- User model with bcrypt-hashed passwords; admin/user roles
- /api/auth endpoints: login, logout, me, change-password, and
  admin-only user management (list/create/delete/reset/enable)
- Stateless JWT session in an HttpOnly, SameSite=Lax, auto-Secure
  cookie; middleware refuses every /api data route without a session
- login.html + auth-guard.js: login page and per-page guard with a
  top-right "name / Admin / Sign out" pill
- Admin Console now gated on admin role (passphrase gate removed) with
  a User administration card
- manage_users.py CLI to bootstrap the first admin
- Rebuilt help.js into a searchable, multi-topic help center
- Local-dev convenience: app serves html/ so the site + API share one
  origin under uvicorn (inactive in the prod container)
- Docs/env: AUTH_SECRET_KEY, requirements (bcrypt, PyJWT), README

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:55:54 -05:00
e5f77846ad GUI polish: Help section, tooltips, sticky save bar + section nav, dashboard filters
- Help: shared help.js injects a Help modal (workflow + key concepts) and the
  .help-tip tooltip component. " Help" added to the suite header and home nav.
- Tooltips: ⓘ hover hints on the trickiest fields (WP number auto-build,
  disciplines, scope/split, constraints, materials-by-discipline, discipline
  strategy, split threshold).
- WP creator: sticky section-nav jump chips at the top and an always-visible
  sticky save bar (Save Draft / Save & View) showing live release readiness.
- Dashboard: metric cards (Release-ready / On hold / Overdue / Total) and the
  status chips are now clickable filters for the board.
- Consistent colored status pills in the dashboard board and the saved list.

Theme unification (home Carbon vs tools palette) intentionally left for a
separate pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:37:01 -07:00
561d4f2408 Menu/UX cleanup: cost codes, Duplicate WP, condensed toolbars, Dashboard tab
- Cost codes: drop everything after 4990 (material/quality/admin codes removed).
- WP creator: add "Duplicate" (asks how many copies; each a clean Draft with
  unique number/subject, approvals & closeout cleared). When embedded the WP
  toolbar now shows only New + Duplicate.
- Condense duplicated toolbars: the embedded creator's Usage Data, Load Example/
  Sample SOP, Comments, View SOP and Dashboard buttons are hidden; the suite's
  top-right "Usage Logs" and "Load Sample" are the single instances.
- Load Sample is context-aware: SOP tab loads the sample SOP, WP/Dashboard tab
  loads the example Work Package in the creator.
- Dashboard moved to a nav-tab next to SOP Configuration / Work Package Creation.
- Remove "Prime Controls" branding from the embedded WP menu.
- Remove "Bill Clarida" from the example WP distribution and the step-comments
  name placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:36:14 -07:00
3c40b58ff8 Add multi-project support: projects entity, picker home page, project context
Projects become the top-level container; SOPs and Work Packages belong to one.

Backend:
- New projects table + CRUD (/api/projects).
- sops.project_id (FK, cascade) and work_packages.project_id added;
  list/latest/metrics endpoints accept a project_id filter.

Front end (now under html/):
- project-data.js: shared API-first ProjectData adapter with localStorage
  fallback + active-project helpers.
- Home page: removed "About This Suite"; added a Project picker (create /
  use sample / select). Tool cards stay hidden until a project is active and
  carry &project=<id>; hero shows the active project.
- Suite reads ?project, resolves it, shows it in the header, and prefills the
  SOP project fields; passes &project into the WP-creator iframe.
- WP creator stamps projectId onto saved packages.

SOP/WP localStorage is not yet namespaced per project (next step).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 15:39:45 -07:00
4d111d608d Merge branch 'main' into feat/wp-discipline-split-dashboard 2026-06-15 15:28:35 -07:00
fd668f0ea2 Move static files into html/ for Docker bind mount 2026-06-15 14:47:24 -05:00