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>
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>
Acts on the site comments from 8/3 plus the follow-ups. Foundation work first —
four of the comments all needed the project team to resolve to real user accounts.
Permissions vs project role (new)
- User.role is now the PERMISSIONS role: admin | project_admin | project_user.
project_admin may delete work packages, change a SOP after it is complete, and
delete a project; project_user may not (archiving a WP is still open to them).
Enforced by require_project_admin() server-side; the UI only hides dead ends.
- New User.project_role holds the person's JOB FUNCTION on the project. It grants
nothing — it feeds the SOP team pickers and notification routing.
- Admin console shows both columns and explains the difference. Migration rewrites
the legacy role 'user' to 'project_user'.
- Deleting a project was previously open to any member and unaudited; it now needs
project_admin and writes an audit event. ProjectData.remove no longer drops the
project from the local cache when the server refuses.
SOP project team from user accounts
- PM/APM/CM/QM and additional team members are pickers over the project's members,
storing the account id next to the display name. A name from an older SOP with no
matching account is kept and flagged rather than dropped.
- The WP Creator lists the SOP team first in the Owner picker, and a new package
defaults to whoever is creating it.
Critical constraints
- SOP constraints carry a Critical flag; buildConstraints() now copies the whole
definition through to the package (it previously reduced them to names, losing
description too), and critical rows are marked in the WP form. The email on
reopen-after-release is wave 3.
Password reset by email
- login.html gains Forgot password and a set-a-new-password view, offered only when
the server reports email is actually configured.
- Single-use signed token (AUTH_RESET_MINUTES, default 60) bound to token_version,
sent immediately rather than through the notifications outbox so a reset link is
never persisted. Identical response for unknown accounts; per-account send
cooldown; a completed reset clears any login lockout.
- Session and reset tokens are no longer interchangeable.
BIM kill-switch
- New admin Features card with bim_enabled, OFF by default. The SOP creator hides
the BIM section and the Creator treats every package as install-only while it is
off; a SOP that already has BIM keeps its data untouched.
Verified with two throwaway-database test scripts: 44 checks on the permissions
matrix and token handling, 22 on the reset flow end-to-end against a local SMTP
sink (real message captured, link extracted and used). Front-end files parse-checked
in headless Chrome. Not yet exercised in a browser against a real login.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
- Logged-in users can change their own password from a "Password" link
in the top-right pill (dialog -> POST /api/auth/password, which requires
the current password).
- Login page gains a "Forgot password?" link explaining that resets are
admin-assisted (admins reset from the console). No SMTP, so no email
reset flow yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>