cc761c8f7d6d66f1f9eabb1a3d2f7d83e3f9cf24
41 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 23ee0b052f |
T9.9 - C4 + the backlog sweep: nine entries closed, each re-measured first
The colour half (C4, approved Aug 18 - "change them"):
- BL-004: the help centre's own 52-colour palette collapsed onto theme tokens
- BL-005: the JS-built dialogs (auth-guard, wp-format) and project-data's
badges read tokens; the creator's categorical badge palette moved to
theme-light as --wp-chart-1..10, read by computed style at boot; the print
popup - a document with no stylesheet - inlines live token VALUES
- BL-008: the second brand blue (#2563d6) is deleted; .sop-inherited tints
with THE blue at the same 7% alpha
- BL-009: the ninth amber (--wp-status-warning-text-alt) is deleted
- theme-light gained the two missing feedback tokens the consoles carried as
literals (--wp-status-success-text / -error-text)
- NEW tests/color_check.py 4/4: zero hex literals outside theme-light.css,
comments stripped (the BL-017 lesson), with the exceptions named in full
(meta theme-color cannot resolve a var; rgba alphas are opacity recipes)
The correctness half, each re-measured before touching, as the task ordered:
- BL-011 STILL REPRODUCED: the sync badge mounted on the first async sync
event; its holder now mounts at DOMContentLoaded, so the three overlays land
in script order deterministically
- BL-012 fixed and MEASURED: baseline_shots freezes Date and Math.random per
document; two consecutive admin captures came back byte-identical
- BL-016 fixed: a step-less wizard URL is step 1; stepper_check's deliberately
wrong pin flipped with the fix, exactly as the entry planned
- BL-018 fixed both halves: the false-complete write now requires the
{sop,state} production shape, and browser_check.seed writes that shape -
which un-detoured four probes' creators from the SOP gate. stepper_check
re-pointed at projB (no SOP) because its premise is a wizard someone is
STARTING, and projA now legitimately restores a finished one.
- BL-019 fixed: a stored cost code that left COST_CODES is kept as an option
(the gov_wosize pattern), so opening a package no longer blanks its record
- hold_check's AST sweep refined in passing detection: it flagged T8.3's
notification-row .status as a release transition; it now reads wp.status only
Every wave-9-pointing backlog entry is closed with its measurement recorded.
Verification (each probe run alone): color_check 4/4, stepper_check 71/71,
validation_check 77/77, url_state_check 23/23, autosave_check 34/34,
a11y_check 22/22, launcher_check 58/58, aggregates_check 16/16,
kitting_check 26/26, hold_check 50/50, mobile_check 24/24, frame_check 38/38,
sections_check 95/95, form_structure_check 50/51 (BL-022's question).
Items: C4, BL-004, BL-005, BL-008, BL-009, BL-011, BL-012, BL-016, BL-018, BL-019
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 0dcea8d725 |
T9.5 - C1+S8: the help-tip is real, the audit is written, BL-001 is dead
S8, finished where the plan said it would be: every .help-tip badge is a <button> - upgraded by the component itself at load (help.js), with helpTipUpgrade() for late renders, so a badge added tomorrow is born reachable. The count the task warned about came true: 15 at wave 0, 18 at the wave 6 exit, 20 at the start of this task - all 20 buttons now, and the fix being in the component is what stops the number growing again. One viewport-clamped role=tooltip bubble serves every badge: focus shows it, Escape hides it, tap toggles it, tap-elsewhere closes it - the touch path Field View's tablets never had. The injected styles now use theme tokens (four raw hexes of the S5 kind, gone). BL-001, CLOSED after three causes and nine waves: the old CSS ::after escaped its badge to the right and was the creator's last 390px overflow. The clamped bubble ends it - scrollWidth 390 vs clientWidth 390 - and frame_check's pin FLIPPED, exactly as designed: it asserted the failure until the fix landed, and now asserts the fix so a regression reopens the entry loudly. The audit (docs/reference/accessibility-audit.md), every number probe-backed: - div/span click handlers: 12/2 at wave 0 -> 0 (the wizard's constraint library entries and the dashboard chips became buttons here; the comments backdrop stopped pretending to be a control) - outline:none without replacement: 0 (wp-chrome's one is the documented S12 exception - its ring is on :focus-within, one ring not two) - aria-live: every toast system and banner announces - native dialogs: 79 -> 21, all on surfaces no S1 task named (admin, users, launcher) - documented as BL-024 with the T7.9 kit ready for them - keyboard-only primary flow: covered leg by leg by the probes that dispatch real CDP key events, cited in the document Three stale count-pins re-pointed to the numbers this task reached (stepper's baseline-minus-10, form_structure's one-span-left, frame_check's BL-001 pin) - each now pins the TARGET so slack cannot hide a regression. Verification (each probe run alone): NEW tests/helptip_check.py 13/13. Regressions: a11y_check 22/22, stepper_check 71/71, form_structure_check 50/51 (BL-022's product question), pipeline_check 44/44, frame_check 38/38. Items: C1, S8 (BL-001 closed, BL-024 opened) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| b44afa7672 |
T9.4 - S7: one sample-data affordance, confirmed, and fenced off the project
Four affordances under three names became ONE: "Load sample data", on the creator's toolbar, at the far end of two separators from the live actions (New / Duplicate), pushed right with its own gap. It confirms through the T7.9 dialog, naming exactly what it does - and what it does not: "This page only: nothing is written to the project unless you then save." The probe verifies the fence the way the done-when demands - against a REAL project, reading the server's SOP and work-package list before and after and asserting byte-identical. Gone: the wizard's header "Load sample" (the dangerous one: it filled the state completeSOP() pushes to the LIVE project, one click, no confirm, no undo - reconciled with D1 exactly as the task records: the creator's control is the survivor, the wizard copy goes), the creator's split Sample SOP / Load example pair (now internals behind the one entry point), and the empty-state context bar's third button (its text now points at the toolbar control). The location/material "Load sample values" buttons stay: they fill a PASTE BOX that acts only through an explicit, dry-runnable import - a different thing, stated in the code. Probes re-pointed with reasons in place: frame_check's D1 toolbar list names the consolidated control; validation_check's sample-driven toast checks became the-affordance-is-gone checks (and its stale showAnalytics drive, orphaned by T7.10, became a the-duplicate-stays-gone check). Verification (each probe run alone): NEW tests/sample_check.py 10/10. Regressions: validation_check 77/77, frame_check 38/38, kitting_check 26/26, export_check 20/20, sections_check 95/95. Items: S7 (D1 reconciliation honored) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| eb1497d574 |
T9.1 - CR-008: the export, finally walked through
Every required field verified present under the Micron configuration: P6
Activity id and description, Priority, the Building/Floor/Sector location
row, Scope & Work, Material List, Constraints with their status carried by
WORDS (a black-and-white print keeps its meaning), Quality & Hold Points, and
the Drawings & Attachments index with per-file descriptions (T7.7's columns).
What must be absent, absent: the Micron samples now carry
fields:{costCode:false, acumaticaTask:false} - CR-002's two removals,
expressed as the toggles CLAUDE.md requires, in the creator sample AND the
wizard sample - so ACU Cost Code and Acumatica Task appear nowhere on the
Micron export while the columns, model and recorded values stay. CR-006-
suppressed sections (assets, kitting) are absent, as pinned since T5.7.
Tablet legibility, three real defects fixed:
- a bare `table { min-width:520px }` in the narrow-screen media block reached
the EXPORT tables too, dragging the whole document to 520px on a 390px
screen; scoped to .table-wrap (the form's scroll containers), because the
export must FIT a tablet, not scroll
- export tables now table-layout:fixed with overflow-wrap:anywhere
- at <=768px the doc sheds its 52/56px desk padding and neutralises the
inline column widths (the one legitimate !important: outranking an inline
style is its job)
RAISED, NOT DECIDED (the task says propose, do not assume) - merge versus
list for attachments: RECOMMEND MERGING image attachments into the printed
document (already done - they print inline as the sheet itself) and LISTING
PDFs as named, described links rather than merging them. Merging PDFs
server-side needs a PDF library dependency and re-renders every export for a
need the meeting expressed as "hand someone exactly the sheet" - which the
5MB single-sheet uploads plus inline images already serve. If merged-PDF
output is wanted anyway, it is a bounded server task - needs Nick.
Verification (each probe run alone): NEW tests/export_check.py 17/17.
Regressions: form_structure_check 50/51 (the standing F6 height question,
BL-022), sections_check 95/95.
Items: CR-008 (CR-002 field toggles applied to the samples)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 190144c539 |
T8.6 - D6: the material list uploads the way the location list does
CR-013 accepted free text because the master workbook never arrived; the
Aug 18 call was the CR-005 call again - build the upload path now.
THE component, extracted: T5.4's paste-or-file machinery (file read in the
browser, ONE parser on the server; dry-run check; a report naming every
rejected row with its source line; an editable list that deactivates rather
than deletes) moved from the location-specific functions into
html/wp-list-import.js. The location list and the new material list are both
instances of it - the done-when's "against the same component, not beside it"
made literally true. The loc* names survive as thin delegates because row
handlers, step entry and the probes call them; locations_check re-pointed its
fetch-count assertion to where the fetches now live and still demands every
read and write reach the server.
The material list itself: description, unit, optional code - one new table
(Alembic a1b8c6d4e2f9, additive), GET/import/POST/PATCH routes on the CR-005
pattern, deactivate-never-delete, reactivation reuses the same row so nothing
referencing it orphans. The sample rows are obviously fake (SAMPLE-EMT-075).
NO inventory, price, stock or warehouse field anywhere - the probe walks the
model's columns by regex. The wizard hosts it on step 11 beside the location
list, optional by design: a project with no list still raises free-text
requests (T8.5 wires that).
Parser bug caught by the probe's first run: strip(',;') ate a LEADING comma,
so ',FT' - an empty description - was accepted as a material named FT.
rstrip only, now; the empty first column is rejected with its line number.
Verification (each probe run alone): NEW tests/materials_check.py 17/17.
Regression: locations_check 58/58 through the shared component.
Items: D6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 79c4a36c70 |
T8.1 - CR-009: kitting statuses are a set, and Micron EUV is not kitting
The statuses: the proposed five, adopted as proposed - Not Started, Picking,
Staged, In Transit, Delivered - as one named constant (KIT_STATUSES) building
the select. They describe fulfillment; the old four ('Open', 'In Progress',
'Kitted', 'Delivered') mixed fulfillment with workflow. A value stored before
the set existed is kept, selected, and shown as "(legacy)" - CR-016's rule
that renamed vocabularies must not orphan recorded data - and round-trips
through collect unchanged.
Micron EUV: the sample SOP is the Micron configuration on record (CR-016 /
T5.7), and it now names kitting:false beside assets:false - off by CR-006
TOGGLE, in both the creator sample and the wizard sample. The section leaves
the form, the rail and the export; its data and model stay exactly where they
are (the probe loads the example package under the Micron sample and finds
its kitting values intact through collect). Any other SOP turns the section
on and it works fully - driven against sopA with everything enabled.
sections_check re-pointed, not relaxed: its sample-map pin said "naming only
assets"; it now says "naming exactly assets (CR-016) and kitting (CR-009)" -
still refusing any section that goes off without a recorded item behind it.
Verification (each probe run alone): NEW tests/kitting_check.py 14/14.
Regression: sections_check 95/95.
Items: CR-009
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
|||
| 82a8f30074 |
T7.10 - D5: one analytics implementation, and its report on the admin console
Usage analytics existed as five of the nine colliding globals creator-frame.md counted (ANALYTICS_KEY, analyticsLoad, analyticsSave, downloadAnalytics, showAnalytics), twice - and the wizard's copy had no caller, because the button lived on the creator. The admin console had a THIRD private reader (usageLoad/downloadUsage) that only saw the wizard's key. Now: ONE core, html/wp-usage.js (window.WPUsage: load/save/track/download + the two pre-move storage keys, verbatim). The creator and wizard keep only a thin track() wrapper - page state like the creator's dev-mode pause belongs to the page - and record exactly what they recorded before, under the same keys, so everything captured before this task still reads (probe plants a legacy-format event and finds it in the report). The "Usage data" button left the creator toolbar; the report lives in admin.html's usage card, covering BOTH tools with a download each, behind the same admin gate as the rest of the console (a non-admin sees the denied card and nothing else), usable at 390px. Two probes re-pointed, both with the reason in the code: - cards_check pinned admin.js byte-identical to HEAD - right for T6.5, but as a standing probe it would fail every legitimate later edit; D5 targets admin.js by name. A7's localization is protected by the feature checks and the end-to-end drive, plus a wiring assertion on the block itself. - frame_check listed "Usage data" among the toolbar buttons that must be visible; it now asserts the button is GONE, so the duplicate cannot quietly return. Verification (each probe run alone): NEW tests/usage_check.py 15/15 (grep half: WPUsage defined once, no page touches the keys directly, none of the five globals survives anywhere). Regressions: cards_check ALL PASS, frame_check 39/39. Items: D5 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 2486f87010 |
T7.6 - CR-014/D2/D9/D10: the Ready for QA gate, notification only, shipped off
Marlena's ask: QA sees inbound work ahead of time, not after the fact. The rung: 'Ready for QA' sits between In Progress and QC in BOTH ladders (wp-creation-app.js STATUS_ORDER, server/app.py STATUS_ORDER) and in Field View's list - inside the T7.3 transition model, not beside it: entering it from an unreleased state crosses the release gates, and 'Issue' (hold) stays a branch. The dashboard filter and the navigator grouping learned the state from the ladder without their own edits. Who hears (D2): the QA GROUP, a multi-pick of project members on the SOP wizard's team step, stored as account ids at data.sop.project.qaGroupIds. Entering Ready for QA emails that list and nobody else. A rejection emails the owner AND the same list (amended answer), returns the package to In Progress, and REQUIRES a fresh comment - server-enforced on both write paths (the first version accepted any old comment already on the record, which made every rejection after the first one free; the gate now demands a new entry). Accept and reject are real buttons on the release banner; the comment modal enforces its field; qa_ready / qa_rejected / status_changed all land in the audit history. The link (X1): wp_link() now opens THE package - wp-creation-index.html ?project&wp=<id>, which the creator boots directly and login.html?next= round-trips for a signed-out recipient. It previously pointed at the suite root, which is exactly the failure X1 names; assignment mail inherits the fix. Email discipline (D10 + standing rules): ships OFF (the stored setting the admin console already owns; PUT /api/settings is admin-only, 403 for anyone else, and audited). With it off, transitions write outbox rows marked 'skipped' and the sink receives nothing. With it on, the probe runs a REAL SMTP conversation against an in-process capture sink and asserts the count and the exact recipient set. A dead SMTP host leaves a 'failed' outbox row with the error recorded. The SMTP password exists only in the environment. DEVIATION, stated: the task's Do-paragraph asks the email to include location and a scope summary; the done-when list (and CLAUDE.md) says no customer IP in a message body. The done-when wins: bodies carry the WP number, who moved it, and the deep link. A location canary planted on the package is asserted absent from every captured message. If the fuller body is wanted, that is a product call - needs Nick. Found while building, logged not fixed (BL-021): project_sop_team() reads sop.data['project'], a path pushSOP never writes - the critical-reopen email has never actually reached the PM/CM. One-line fix, owned by T9.9. Field View (D9): 'Ready for QA' is carried by TEXT on the card at 390px. Verification (each probe run alone): NEW tests/qa_gate_check.py 40/40. Regressions: hold_check 50/50, pipeline_check 44/44, aggregates_check 16/16, frame_check 39/39, validation_check 83/83. Items: CR-014, D2, D9, D10 (X1, X3 honored) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| 12d19446d5 |
T7.1 - B7: dissolve the creator iframe, and D1 give it back its sample data
There is no iframe in html/ any more. The creator is a top-level document with
the same app bar and the same tab strip as the SOP wizard; the two tabs that
used to swap a frame are links between them.
DEVIATION, stated rather than smuggled. The wave file says "remove the iframe
boundary so the creator renders in the parent document". It renders as its own
document instead. Every done-when is met - no iframe, no cross-frame messaging,
F4 resolved structurally, CR-006 toggles with no special-casing, back and
forward intact with T4.2's URL state - but the route is the other one, and the
reason is in creator-frame.md's own numbers:
merge into parent make it a page
selector collisions to resolve 21 0
script global collisions 9 0
cross-frame call sites to remove 28 28
probe entry points needing rework ~29 2
The 21 and the 9 were never the cost of dissolving the boundary. They are the
cost of MERGING TWO DOCUMENTS, which is a different change the boundary was
hiding. And 29 probe call sites address wp-creation-index.html directly, so a
route that keeps that address keeps all of them. creator-frame.md section 5
records this in full.
What went, and what replaced it:
#wp-frame, applyEmbedLayout, sizeWPFrame, viewportMinusChrome, chromeHeight,
renderWPTab, the resize handler, the ResizeObserver, --wp-chrome-h,
.content-area.embed-full, body.embed-full -> the window sizes the page
?embedded=1, body.embedded, .embed-hide, .embed-first -> nothing. An old
link carrying the param is ignored rather than half-obeyed.
openWpById / showDashboard / showForm / dashApplyFlag / applySopSections
called across the frame -> the URL. ?project= ?view= ?wp= ?flag= were
already read at the creator's own boot (T4.2), which is exactly why those four
could be DELETED rather than migrated. X4 is closed: the surviving path is the
one T5.5 built and proved.
inIframe in auth-guard.js, wp-chrome.js, wp-sidenav.js, help.js and _isTop in
project-data.js -> gone. help.js now reads the explicit WP_HELP_NO_FAB flag
both tool pages set, instead of inferring intent from where it is rendered.
.main-nav / .nav-tab in work-package-suite-styles.css -> wp-chrome.css,
because a tab row only one of two documents can style is the shape that put
the tabs in the parent and the toolbar in the child to begin with.
The three questions creator-frame.md section 4 said no count could answer:
1. The creator gets the app bar. It was the only page loading neither
wp-chrome file. Its header is now the .header-left / .header-right pair the
wizard uses, so the switcher lands in the same place on both.
2. Two sequence components, scoped not merged - confirmed Aug 18 that the
sequence is authored in the SOP and adjustable per package. BL-015 stays.
3. body.embedded is gone. The header it hid is replaced by the app bar; the
sample controls are visible in a new package toolbar (D1); the analytics
button is visible there until T7.10 moves it. The Dashboard BUTTON in that
row became a TAB, which is the one place B7's "fold the toolbar into the
tab row" actually happened.
Old addresses still resolve. ?tab=wp, ?view=dashboard and ?wp=<id> are in
bookmarks, in wp-sidenav's link map, and they are the shape CR-011 and CR-014
were specified against (X1). The wizard forwards them with replace(), so Back
does not bounce. Breaking these silently was the one regression this task could
have shipped that nobody would notice for weeks. frame_check.py section 4 pins
all three.
BEHAVIOUR CHANGE, deliberate. The live cross-frame hand-off showed the creator a
section toggle that had NOT been saved: flip it, look, reload, and the section
came back. What the creator shows now is the SOP that is stored. sections_check
5b pins both halves - an unsaved toggle does not travel, a saved one does.
BEHAVIOUR CHANGE, not deliberate, logged as BL-020. A tab switch is a page exit
now, so leaving the wizard with unsaved SOP edits fires T4.3's unsaved-work
guard. Nothing is lost - the guard writes the draft first and T4.3 recovers it -
but it is friction that did not exist, and suppressing a deliberate guard is a
product decision with its own downside. Logged, not quietly handled here.
tests/frame_check.py, 39 checks, new. Two of them exist because of failures
during this task rather than in it:
- "both documents parse and boot". A const shadowing a function parameter is a
SyntaxError, and work-package-suite-app.js did not parse at all for one run.
Four checks in url_state_check went red and not one said "the script did not
load". Asserting a page's own entry points exist costs nothing.
- "focus emulation is on, so a focus reading means something". An earlier draft
called page.call instead of page.ws.call inside a try/except and measured
nothing, reporting no focus ring anywhere - which looks exactly like a
finding. Trap 5 in reverse, for the second time in this project.
The four backlog entries logged against this file, re-measured rather than
assumed:
BL-001 still reproduces (485px in a 390px viewport) but its RECORDED CAUSE IS
WRONG. --nav-w now computes to 56px, so the injected-style explanation
is spent. The overflow is the creator's data tables - #asset-body's
lays out at 520px with no scroll container. frame_check reports the
offending boxes by selector and skips position:fixed subtrees, because
the comments drawer parked off-screen at right:844 made the first
measurement blame the drawer. Pinned, not fixed: T7.2 lays out the form.
BL-013 CLOSED. It was fixed by S12 in WAVE 4 - wp-creation-styles.css:209
carries the comment naming this entry - and nobody updated it. It was
quoted as a live CLAUDE.md violation while planning wave 7 and had not
been true for four waves. a11y_check walks 120 focusable elements on
the creator and every one rings at >= 3:1.
BL-006 15 by the probe's measure, unchanged; different denominator, stated.
BL-007 68 raw radii by the probe's measure. Nothing has reduced it in four
waves; it is measured every run now instead of once.
BL-018 cost a FOURTH probe. frame_check imports set_sop from sections_check
rather than writing a fifth copy of the workaround. T9.9 owns it.
Probes re-pointed, with reasons in the files: sections_check 5b (drove the live
hand-off), pipeline_check check 2 (read through contentDocument), f_items F4
(drove standalone and embedded; there is one mode now), validation_check
(lost "the wrong tab", gained the SOP gate).
Verified: frame_check 39/39, sections_check 95/95, pipeline_check 44/44,
url_state_check 23/23, validation_check 83/83, a11y_check 22/22,
autosave_check 34/34, aggregates_check 16/16, stepper_check 71/71,
browser_check 71/71, launcher_check 58/58, generalinfo_check 49/49,
rollup_check 63/63, cards_check 44/44, locations_check 58/58.
f_items: F1-F5 fixed, F6 reproduces (T7.2).
Metrics: iframes 1 -> 0, colour literals in rules outside theme-light.css 0,
dialogs 64, <div onclick> 2, .help-tip 18.
Items: B7 D1
Task: T7.1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|||
| 55caefb099 |
T5.8 - S1 (wizard): errors at the field, and the last dialog is gone
S1 has two halves and they are easy to conflate.
One is that validation was a native dialog: "Please complete all required
fields: Project Name, Number, Client, Division, and Site Location." names five
fields at once, highlights none of them, and scrolls nowhere. The other is that
validateStep guarded steps 1, 5 and 6 while the MARKUP marks required fields on
1, 3, 5, 6 and 7 - so two steps' asterisks meant nothing at all, which is worse
than no asterisk.
Both fixed, and the second is the one worth measuring: the probe reads the
required-field list out of work-package-suite.html rather than out of STEP_GATES,
because a probe that read the table would agree with whatever the table says and
prove nothing. Both notations count - an asterisk in a <label>, and the red span
beside step 3's role titles.
Now: an error per FIELD, rendered at it, associated by aria-describedby, marked
aria-invalid, announced through role="alert", and the first one focused and
scrolled into view. The error boxes are BUILT from the gate table rather than
written into the markup twelve times - adding a required field is one row, and
its error element, its association and its announcement all follow. A
markup-side error box somebody forgets to add is an error nobody ever sees.
An error clears as you type rather than on the next submit. An error still
showing over a field you have just corrected teaches people to ignore errors.
And nothing paints a step you have not tried to leave: the rail asks
stepGateMet(), which reads the same fields and marks none of them.
The thirteen dialogs
Every one is now the thing it should have been - an error at the field it is
about, or an announcement in a live region with the role T4.5 established:
errors interrupt, confirmations do not.
A dialog is not merely ugly. It blocks the page, cannot be placed or styled, a
screen reader can present it only as a modal interruption, and it is one OK
button whatever it says - so "sample data loaded" and "you cannot do that"
arrived identically.
Two deserve naming. The empty-comment alert became an inline error on the
feedback textarea. And showAnalytics() was a confirm() carrying the entire
usage summary as its body - a wall of text in a dialog whose only dismissal
was also the download button. The summary is the useful part, so it is shown,
with the download offered as an action beside it. That function has no caller
in the wizard's markup (the "Usage data" button is the creator's, calling the
creator's own showAnalytics), and it was converted rather than deleted:
deleting a feature is not what this task was asked to do, and its dialog
counted toward the number this task has to drive to zero.
html/work-package-suite.html #wp-toast, an error box on the textarea
html/work-package-suite-app.js STEP_GATES widened; per-field messages;
ensureErrorBoxes; wizardToast; 13 removals
html/work-package-suite-styles.css .wp-toast
tests/validation_check.py new - 81 checks
tests/stepper_check.py its "the alert T5.8 still owns" check now
asserts the opposite, by name
Done when
[x] every step with required fields validates them - 5 steps, from the markup
[x] each error renders at its field and is associated via aria-describedby
[x] submitting an invalid step focuses AND scrolls to the first error
(scroll checked by bounding box, not by trusting scrollIntoView)
[x] errors announce to screen readers
[x] the wizard's native dialog count is 0
The count, recorded both ways because BL-017 says the metric counts prose
work-package-suite-app.js 0 raw, 0 with comments stripped
app-wide 64 raw, 64 stripped, against wave 0's 79
wp-creation-app.js 43 (wave 7), users.js 10 and
admin.js 6 and index.html 5 (wave 9)
The wizard contributes none of what is left, which the probe asserts rather
than leaving to the total.
Verified one at a time
validation_check 81/81 new
stepper_check 71/71
sections_check 88/88
browser_check 71/71
a11y 22/22
url_state 23/23
autosave 34/34
locations 58/58
aggregates 16/16
pipeline 43/43
launcher 58/58
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
No colour literal added. The toast says "error" by a red rule AND by staying
until dismissed where a confirmation times out - two channels, not one (C1).
Question for the PR, per CLAUDE.md: step 3's two role TITLES are validated
because the markup marks them required, but the two role NAME pickers beside
them are not marked and so are not gated. A sign-off role with nobody in it is
arguably the more useful thing to catch. The markup is what was built to; if the
intent was the names, that is two rows in STEP_GATES.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 3bc3abe209 |
T5.7 - CR-016: Assets off for Micron EUV, by configuration
Asset content duplicates the database Clinton's team maintains, and that
integration is deferred, so the section comes out in the interim.
This is a configuration task, and the wave file says so - "Surface:
configuration". There is no Micron EUV SOP in this repository; it is data on a
running system. What the repository holds are two representations of the Micron
configuration, and both now carry the toggle:
wp-creation-app.js SAMPLE_SOP what the standalone creator shows when
there is no project SOP to read
work-package-suite-app.js loadSample the Micron FMCS SOP the wizard loads
For a real project the mechanism is step 12 and needs no code: untick Assets,
complete the SOP. That is what T5.5 built and what "another SOP can enable
Assets and it works" means.
Both write a PARTIAL map - {assets:false} - rather than a full one.
WPSections.normalize fills the rest in as ON, so a section added after today is
not silently off for this SOP. The probe checks that specifically: exactly one
key is false, not nine true and one false frozen at today's list.
html/wp-creation-app.js SAMPLE_SOP.sections
html/work-package-suite-app.js loadSampleData sets state.sections
tests/sections_check.py +13 checks (75 -> 88)
Done when
[x] Assets does not render for Micron EUV in the form, detail view or export -
and not in the section chip strip either, which is the form's contents page
[x] the section and its model remain in the codebase - #asset-card and
pkgAssets are both still there, checked in the browser rather than assumed
[x] another SOP can enable Assets and it works - the fixture project renders it
seed_demo.py was deliberately NOT changed. Its SOP row stores a bare
{governance: ...} blob rather than the {sop, state} shape pullProject reads, so
its data never reaches the creator's SOP object at all - adding sections there
would be a toggle nothing consults. That shape mismatch is BL-018 and it should
be fixed as one thing, not worked around here.
What the probe caught
The first run reported on "Job A" while claiming Micron. activeProjectId falls
back to ProjectData.getActiveId(), so a project left in localStorage by an
earlier navigation sent bootSOP() down the real-project branch and the sample
was never loaded - every CR-016 assertion was then measuring the wrong SOP and
four of them happened to fail, which is the only reason it was noticed. The
probe clears storage first and says why.
Verified one at a time
sections_check 88/88 (75 + 13 for CR-016)
browser_check 71/71
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
Question for the PR, per CLAUDE.md: CR-009's decision also says Kitting ships
toggled off for Micron EUV via these same toggles. That is wave 8's to apply
(CR-009/CR-010/CR-012), so the sample still has Kitting on. If the intent was
for both to go off together, the second line is one word.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| c453e50412 |
T5.6 - CR-002: Acumatica cost code and task, hidden by toggle
The team concluded these two are noise on a field work package: cost codes are
effectively constant on a job and the Acumatica task mapping is a PM concern.
The cost visibility they actually want is by building and floor, which is CR-004
and CR-018.
Hidden, not removed. CLAUDE.md: "Removed fields are hidden, not deleted (CR-002,
CR-016). Retain the data and the model." So this is a second, narrower toggle
list beside T5.5's sections - two fields inside General Information rather than
two more sections, because a section is a block of the document and these are
two rows in one.
no migration the values live in the work package's JSON data blob,
which nothing here writes to. The probe greps every
migration for a drop_column touching either.
no model change server/models.py is untouched by this task
no code change to the toggles are SOP data. Another project turns them
re-enable back on from step 12 and both fields return, values
included
A field is on only if its own toggle is on AND the section holding it is. Asked
as one question (WPSections.fieldOn) so no caller has to remember to ask both -
a field showing inside a hidden section is not a state worth reasoning about,
and the probe checks that case explicitly.
html/wp-sections.js FIELDS, fieldOn, normalizeFields
html/work-package-suite-app.js field rows nested under their section
html/work-package-suite-styles.css .field-toggle
html/wp-creation-index.html ids on the two .field wrappers
html/wp-creation-app.js WP_FIELD_NODES; both document rows conditional
tests/sections_check.py +22 checks (53 -> 75)
Done when
[x] neither field appears in the form, detail view or PDF export when off
[x] existing records still hold their values - a package EDITED while both are
off comes back through collectPackage() with both intact
[x] the fields can be re-enabled for another SOP without a code change
[x] no schema migration drops data - checked against every migration in the
tree, not just the ones this wave added
The whole .field wrapper is hidden, not the input: a bare label over nothing is
worse than either state.
Raised, not fixed
BL-019 A cost code that has left COST_CODES is silently blanked on edit.
wp_cost is a <select>, and setting .value to something with no matching
<option> does nothing at all - so opening such a package clears the
field and the next save writes the blank back. The same bug was fixed
once already for gov_wosize (work-package-suite-app.js:490-495) by
adding the stored value as an option; cost code never got it.
Found the honest way: a probe here used an invented cost code to prove
hiding a field does not delete its value, and the value came back
empty. That looked exactly like the toggle eating data. It was not, and
the probe now uses a real code and says why in a comment - a probe that
fails for a reason other than the one it names is worse than no probe.
Verified one at a time
sections_check 75/75 (53 + 22 for CR-002)
browser_check 71/71
stepper_check 70/70
a11y 22/22
url_state 23/23
autosave 34/34
locations_check 58/58
Question for the PR, per CLAUDE.md: BL-000b asks whether General Information
wants per-field toggles generally. This is not that - it is the two fields
CR-002 names, and the list is deliberately closed. If a third field wants one,
that is the general question and it needs the product answer BL-000b is holding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| ae30c58337 |
T5.5 - CR-006: section toggles, and X4 resolved rather than deferred
X4 first, because the brief asks for it explicitly.
IMPLEMENTATION.md sequences CR-006 after B7 on the grounds that the toggles must
suppress sections inside the creator, which is an iframe child until T7.1. The
wave file puts it in wave 5 anyway, and its last done-when is written to
accommodate exactly that: "toggle state propagates into the creator, OR the PR
documents exactly where it does not and why".
It propagates. Both ways, by two separate paths, because they fail differently:
ON THE CREATOR'S OWN BOOT the flags ride on the SOP, which the creator already
reads - ProjectData.pullProject hydrates it from the server. Nothing crosses
the frame boundary at all, so this path is unaffected by B7 either way. It
covers a reload, a fresh tab, the standalone creator page, and a colleague
opening the project on another machine.
WHILE THE FRAME IS OPEN the wizard hands the change straight across
(pushSectionsToCreator -> cw.applySopSections), the same shape T5.3 used for
the dashboard filter. Without it, flipping a toggle would appear to do nothing
until a reload.
T7.1 removes the second path, not the first. That is the whole of the X4
exposure and it is one function, commented as such. Building CR-006 after B7
would not have made the SOP-borne path any different; it would only have saved
writing the hand-off.
What it does
Ten sections, one shared list (html/wp-sections.js) read by the wizard, the
creator's form and the creator's rendered document. Three surfaces meant three
chances to drift, which is how "Assets is off, except in the export" happens.
Off means NOT RENDERED - form, detail view and PDF export. It never means
deleted. renderPackage() was rebuilt from one long string into a list of
(section, html) blocks so a suppressed section leaves no empty heading and the
survivors renumber 1.0, 2.0, 3.0 instead of leaving a hole. The print window
reuses that same HTML, so the export needed no separate change.
Absent means ON. A SOP saved before today mentions no sections, and reading
that as "all off" would blank every project in the estate the moment this
shipped. WPSections.normalize is the one place that decision lives.
html/wp-sections.js new - the shared list, defaults, normalize
html/work-package-suite.html step 12, a 12th rail button
html/work-package-suite-app.js the toggles, state.sections, the hand-off
html/work-package-suite-styles.css the toggle rows
html/wp-creation-index.html stable ids on the five unnamed cards
html/wp-creation-app.js WP_SECTION_NODES, applySopSections,
renderPackage rebuilt as blocks
tests/sections_check.py new - 53 checks
Done when
[x] all 10 sections are toggleable - each one driven off and back on
[x] a section toggled off is absent from the form, the detail view and the
PDF export - checked by content marker, not by heading, so "the section
is gone" and "the section was empty" cannot be confused
[x] toggling off then on restores prior data with no loss - and the sharper
version: a package EDITED while Assets is off still carries its assets
through collectPackage(), which is what Save uses
[x] new SOPs default to all sections on
[x] toggle state propagates into the creator - both paths, separately
Two things worth arguing with
General Information is toggleable, because CR-006 enumerates it. Turning it
off leaves nothing to identify a package by. The row says so in its own note
rather than being quietly excluded from the list.
Location has no card of its own - it is a field inside General Information's
grid, and its toggle governs that one row. CR-004 gives it structured
building/floor/sector fields in wave 6, at which point one line of
WP_SECTION_NODES changes. Written down because "the toggle does nothing" and
"the toggle governs one row" look identical from outside.
Approvals & Sign-offs is NOT toggleable and is not one of the ten. A package
nobody signed is not a shorter package.
Verified one at a time
sections_check 53/53 new
stepper_check 70/70 (STEP_COUNT 11 -> 12)
locations_check 58/58 (its "step 11 is last" check now asserts the thing that
is actually invariant - the wizard's navigation follows)
browser_check 71/71
a11y 22/22
url_state 23/23
autosave 34/34
aggregates 16/16
pipeline 43/43
launcher 58/58
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
No colour literal added: still 0 across all page sheets and inline blocks. Each
toggle says its state three ways - the checkbox, the words "In use" / "Not used",
and the rule down its left edge (C1).
Question for the PR, per CLAUDE.md: the toggles are SOP-wide, so a project
cannot use Kitting on install packages and not on BIM ones. BL-000b already
records the field-level version of this question; the per-WP-type version is the
same shape and is not recorded anywhere yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 2081c1ad3c |
T5.4 - CR-005: a per-project location taxonomy, stored as codes
CLAUDE.md lists CR-005 among the change requests that get "silently half-built if
you treat them as frontend-only". This is the server half and the wizard half
together: a new table, four routes, an Alembic revision, and step 11.
CODES, NOT DISPLAY STRINGS, because CR-018 rolls cost up by these values and a
rollup keyed on a label breaks the day somebody fixes a typo in it. Two columns
carry that: `code` is a node's own slug, derived once at import and never
recomputed; `path` is the full slug path, unique per project, and is what a work
package will store. Renaming a value changes `name` alone - the probe renames a
floor and demands its path comes back byte-identical, with its children's paths
intact.
DEACTIVATE, NEVER DELETE. There is no DELETE route, and the probe checks for its
absence (405) rather than trusting that nobody added one. Deactivating hides a
value from new work packages and cascades DOWN, because a floor nobody can pick
must not keep offering its sectors. Reactivating walks UP only - a sector may
have been switched off for its own reasons, and silently resurrecting it would
undo a decision nobody made twice. That asymmetry is deliberate and is pinned by
a named check so it does not get "fixed" into a surprise.
Import reports rather than merges. Rejected rows come back with the SOURCE line
number and a reason; duplicates are listed as duplicates, separated into "already
in this project" and "already on line N of this import". Reusing a parent is not
a duplicate - B1/L2/1P and B1/L2/2P share a building and a floor by design, and
only the full path repeating counts. Re-importing a deactivated value brings the
same row back rather than creating a second one; the probe checks the id.
One parser, on the server. A CSV is read in the browser and posted as text
exactly as a paste is, so "what does a blank column mean" has one answer.
Comma, semicolon and tab all work - a paste out of a spreadsheet is tab
separated and a saved CSV is not, and which one somebody has is a question the
machine can answer.
No guessed floor names. IMPLEMENTATION.md section 8 says the B100 list has not
been supplied. The seeded sample has "Sample" inside every string, and the probe
greps html/ and server/ for a location-shaped assignment containing any of the
review's real names.
server/models.py LocationNode
server/alembic/versions/e2a4c7d91b30_location_taxonomy.py
server/app.py GET/POST/PATCH + import, parser, slug
html/work-package-suite.html step 11, an 11th rail button
html/work-package-suite-app.js the step's logic; LAST_STEP replaces 10
html/work-package-suite-styles.css the list, the report
html/theme-light.css .field-error, now declared once
tests/locations_check.py new - 58 checks
tests/stepper_check.py STEP_COUNT 10 -> 11
Done when
[x] CSV upload and paste both work and report rejected rows with reasons
[x] duplicates are detected and reported rather than silently merged
[x] values are editable after import - rename, add, deactivate
[x] deactivating hides it from new work packages; an existing package
referencing it still resolves, because the row is retained
[x] values are stored as codes suitable for grouping
[x] no guessed real-world floor names exist anywhere in the code
Two decisions worth disagreeing with
Step 11, appended, not step 2, inserted. Locations belong beside Project by
subject. Renumbering 2-10 would touch every sop-step-N id, every
collectStepData case, every gate key and the analytics history - a large
silent-mismatch surface for an ordering change. The count now lives in one
place (LAST_STEP), so reordering later is cheap.
Any project member may edit the list, not only a Project Admin. It matches how
the SOP baseline itself is authored: the Project Admin gate is on CHANGING a
completed SOP, not on writing one. If the location list should be tighter than
the SOP it belongs to, that is a product call.
Verified one at a time
locations_check 58/58 new
stepper_check 70/70 (11 steps)
browser_check 71/71
a11y 22/22 sop now rings 38 focusable elements
url_state 23/23
autosave 34/34
aggregates 16/16
pipeline 43/43
launcher 58/58
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
alembic upgrade / downgrade / upgrade all clean on a throwaway SQLite
file, and the migrated schema matches Base.metadata.create_all
column for column - dev auto-creates and production migrates,
so a divergence between the two is invisible until it ships
.field-error was declared in two page sheets by the end of T5.2 and would have
been three by T5.8, so it moved to theme-light.css. No colour literal added
anywhere: still 0 across all page sheets and inline blocks.
Question for the PR, per CLAUDE.md: the levels are fixed at building / floor /
sector. Micron's floors behave like buildings, which this handles by letting a
project use whichever levels it needs - but a job that wants a fourth level, or
different names for the three, cannot say so. Whether that is worth a
per-project level vocabulary is a product question; the schema would take it
without a migration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 6088ef17e8 |
T5.3 - B4 surface: a pipeline strip, four server counts, four shareable links
Four cells on the launcher - Work packages, Release ready, On hold, Overdue -
every number from /api/wps/metrics, which T4.1 built. There is deliberately no
cache fallback anywhere in renderPipeline(): a remembered number sitting beside
three live ones is the failure B4 exists to remove, wearing a different hat.
The cells are the four the endpoint already computes and the dashboard already
filters on, so they map 1:1 onto its flags rather than inventing a fifth slice
nothing downstream understands. wp-creation-app.js now names them (DASH_FLAGS)
for the same reason: a cell linking to a filter the board does not recognise is
a dead link that still looks live.
"Links to a filtered view via a shareable URL" needed the filter to BE URL state,
which it was not - the dashboard kept its flag in a variable. So:
- dashToggleFlag pushes ?flag=<f>, and clears it on the way out of the board
- the creator applies ?flag= before its first render, not after (applying it
after paints the whole board and throws it away)
- Back and Forward move through filters like any other state
- work-package-suite-app.js forwards the flag ACROSS the iframe boundary, since
the creator's src carries only the project. B7/T7.1 dissolves that frame and
this hand-off goes with it; it is commented as such.
Zero is a real answer for one slice of a project that has work in it. Four zeros
on a project with none is not a reading, it is a strip that looks broken - that
case gets a sentence and a way into the creator instead. A failed request gets an
explicit error naming the failure, and no cells at all.
html/index.html the strip, its states, PIPE_CELLS
html/wp-creation-app.js flag as URL state; DASH_FLAGS; dashApplyFlag
html/work-package-suite-app.js forward the flag into the frame; clear on exit
tests/pipeline_check.py new - 43 checks
Done when
[x] every number comes from a server endpoint - proved by poisoning localStorage
with 99 fake packages and demanding the strip still read the server's 4
[x] each cell links to a filtered view via a shareable URL - and the probe
FOLLOWS the link and reads the filter inside the frame rather than trusting
that a correct-looking URL was built
[x] a project with zero work packages renders a sensible empty state
[x] the strip announces updates via aria-live (polite - a count is not an
interruption) and reports aria-busy while it is counting
What the probe caught
The link landed on "Complete the SOP Configuration first". Not the strip's
fault: browser_check.py's fixture stores a bare {governance: …} blob as the SOP
data, where production stores {sop, state}. restoreSavedSOP() needs `state` and
bails without it, so sopComplete stays false and the WP tab shows its gate.
pipeline_check seeds the production shape. The underlying wart is real and is
logged rather than fixed - see BL-018.
Verified one at a time
pipeline_check 43/43 new
launcher_check 58/58
stepper_check 70/70
url_state 23/23 the dashboard's new flag state did not disturb it
aggregates 16/16
browser_check 71/71
a11y 22/22
autosave 34/34
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
No colour literal added: still 0 across all page sheets and inline blocks. The
four cells are told apart by a label, a sentence and an accent - three channels,
so colour is not carrying it alone (C1).
Raised, not fixed
BL-018 The WP tab's gate is the last localStorage-derived status in the app.
T4.1 moved the launcher's card to the server; the wizard page still
decides gate-or-creator from wp_suite_sop_complete plus a state blob.
pullProject refreshes both on load so a connected user is fine, but the
two answers come from different places and the fallback is silent.
Includes a second, sharper edge: project-data.js:210 writes that flag
for ANY row returned, including one with no `state` to restore - so the
flag is written and never read consistently. T7.1 owns it.
Question for the PR, per CLAUDE.md: the strip counts Overdue against `data.due`,
which is free text today. CR-004/CR-018 restructure location but not dates. If
"overdue" is going to drive anything beyond a launcher tile, that field needs a
type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 5e1f6e75ba |
T5.1 - A4/S9: a vertical stepper, ten real buttons, states in words
The step rail was ten div elements carrying onclick inside a horizontal
scroller. Not in the tab order, not operable by keyboard, and silent about
progress - the only thing on the page that said where you were was a "1 / 10"
pill in the app bar, detached from the control it described.
The rail is now a vertical column beside the form: ten <button> elements in an
<ol> inside a named <nav>, with arrow keys, Home and End on top of the Enter and
Space a button gives for free. All ten stay in the tab order; a roving tabindex
would have satisfied "arrow keys" by breaking "tab", which the done-when asks
for both of.
Four states, each carrying a word and a marker shape as well as a colour (C1):
Complete (green disc, tick), Current step (blue disc, aria-current="step"),
Locked (dashed outline) and a plain default. Locked steps keep aria-disabled
rather than disabled, so a keyboard user can reach one and be told what is in
the way instead of finding a control that has vanished from the tab order.
Reachability is the guard's own rule, deliberately not a stricter one: you may
leave the step you are on once its required fields are filled. The tempting rule
- lock everything after the first unmet gate anywhere - is not what
validateStep() enforces, and a padlock the Next button walks straight past is
the drift this change exists to remove. validateStep() and the rail now read one
STEP_GATES table, so they cannot disagree; T5.8 widens that table rather than
editing four functions.
Clicking a step you cannot reach announces why through a role="alert" region and
puts the cursor in the field that is missing. Saying "no" and leaving you where
you were, with no idea which of five inputs was empty, is what the dialog did.
Below 900px the rail collapses to a disclosure naming the step you are on -
ten vertical rows above the fields is most of a 390px screen before you reach an
input. 44px tap targets, since Field View is the gloved-hands surface.
Also: going backwards is no longer gated. previousStep() never validated, so a
rail that did would have trapped you on an incomplete step.
html/work-package-suite.html rail markup, counter removed
html/work-package-suite-styles.css #tool-sop grid, .step-rail*, 899px collapse
html/work-package-suite-app.js STEP_GATES, renderStepRail, keyboard, watcher
tests/stepper_check.py new - 70 checks
Done when
[x] all 10 steps are <button> elements
[x] keyboard: tab, arrow keys, Home/End, Enter and Space
[x] aria-current on the current step, exactly one
[x] complete / current / unavailable told apart without colour
[x] the "1 / 10" counter is gone - no .step-counter, no N/10 in the app bar
[x] app-wide <div onclick> 12 -> 2, down exactly 10
Verified one at a time
stepper_check 70/70 new
browser_check 71/71
url_state 23/23
a11y 22/22 sop now rings 37 focusable elements, all >= 3:1
autosave 34/34
aggregates 16/16
f_items F1-F5 FIXED, F6 REPRODUCES (T7.2)
baseline_shots 14 shots; only the sop pair changed. The beforeunload log on
sop@1440 and creator@1440 is present at HEAD too - captured
both sides to check rather than assume.
No colour literal was added: all five page sheets and all seven inline <style>
blocks still hold zero. New spacing consumes --wp-s*; three raw font sizes were
added and three removed, so BL-010 is unchanged in kind.
Raised, not fixed
BL-016 Back to a URL with no `step` leaves the wizard where it was. T4.2's
popstate handler parses NaN and ignores it; its own probe never took
that branch. stepper_check pins the current behaviour by name so the
fix has a test waiting.
BL-017 The native-dialog baseline counts the word `alert(` in comments. Four
comments written here - all of them about removing a dialog - moved
the number from 80 to 82 while two real calls were being deleted. They
were reworded; the metric still needs a comment-stripped variant, which
T5.8 owns.
Question for the PR, per CLAUDE.md: BL-015 leaves the creator's .step-tab
uppercase as the last forced-uppercase interactive text in the suite, on the
grounds that A5 scopes sentence case to buttons and field labels. The wizard's
rail is now buttons, so its labels are sentence case ("Sign-offs", "WP types").
The two are consistent by rule and inconsistent on screen until T7.x.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 0cce0b4191 |
T4.4 - B5: two save indicators, two sentences, neither readable as the other
The app already showed "✓ All changes saved". That badge belongs to the OUTBOX -
it reports whether saved records have reached the project - and it went green
when the queue emptied, whether or not anything in the form had been saved at
all. So the promise B5 says the app does not keep was being made by a component
that could not know whether it was true.
Two indicators now, each speaking for one thing:
DRAFT .wp-draft-status, mounted in the creator's sticky save bar and the
wizard's step navigation. Driven by WPAutosave's status: "No unsaved
changes" / "Unsaved changes" / "Saving draft…" / "Draft saved at HH:MM"
/ "Draft not saved on this device — <reason>" with a Retry.
OUTBOX the existing badge, reworded so every state names the project:
"Sending N changes to the project…", "Everything sent to the project",
"N changes not yet sent to the project — retrying", "rejected by the
project".
"No unsaved changes" rather than "Saved" for an untouched form: those are
different statements and only the first is true before anything is typed. The
component was getting that wrong in the same way the outbox badge was.
Announced per S10 (T4.5's pattern, arriving one task early because this indicator
needs it to exist): role="status" while things are going well, swapping to
role="alert" on failure. A failed autosave means the safety net is not there, and
waiting for a pause in the screen reader's queue to mention that is too late.
The retry button is only rendered in the failed state - a retry offered when
nothing has failed is a button that does nothing.
Styles live in theme-light.css because both form pages mount the same component,
and a second copy in a page sheet is what wave 3 spent itself removing.
VERIFICATION. tests/autosave_check.py grew to 34 checks, all passing. The B5 ones:
- the indicator reports "No unsaved changes" untouched, then a real save with a
timestamp, and is visually distinct in each state
- a simulated storage failure is visually distinct, names the reason, offers a
retry, and switches to role=alert
- the sync badge no longer RENDERS "All changes saved", and every state it does
render names the project
That last check is deliberately scoped to what the badge renders rather than to
the file text: the old phrase still appears in the comment explaining why it was
changed, and asserting on that would be asserting that the reason cannot be
written down.
Note for wave 9: the outbox badge is styled with inline hexes, including #8a6d00
- the ninth amber from BL-009, independently confirming that entry. It is
BL-005's territory, not this task's.
browser_check 71/71.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| ab3c9e52d1 |
T4.3 - S2: autosave, an unsaved-work guard that means it, and draft recovery
The work package form is ~4,700px tall and had no autosave and no unsaved-work
guard. The only beforeunload listener in the app was analytics dwell tracking, so
a mis-click, a closed tab or a crash lost everything typed since the last Save.
html/wp-autosave.js separates three things this app was conflating:
THE DRAFT what you have typed. Saved locally, continuously, by this file.
THE RECORD what you explicitly Saved, which goes to the project.
THE OUTBOX project-data.js, which gets the RECORD to the server reliably.
This module owns the first only and never writes to the server. A draft is
"unfinished work this browser is holding for you"; pushing unfinished work into a
shared project is a different feature with different consequences.
The guard fires only when the form differs from what was loaded. "Do not fire the
guard when nothing has changed" is in the task because a dialog that appears on
every exit gets clicked through within a day, and is then worse than no dialog.
WIRED: the creator's package form and the SOP wizard's state. Both autosave on a
1200ms debounce, on section/step change, and on visibilitychange - the last being
what makes recovery survive a killed tab, since a crash never fires beforeunload.
The wizard's guard is ADDED alongside trackStepDwell, not in place of it; both
fire and the analytics one does not preventDefault.
THREE BUGS FOUND WHILE BUILDING THIS, all by the probe rather than by reading:
- Dirtiness cannot be "does the form match savedPackages". Those records come
back from the server through serverToPkg() in a LEANER shape - 264 characters
against the form's 1,820 - so a freshly loaded, untouched form differed from
its own record and every single exit would have prompted. Dirtiness is now
measured against a baseline snapshot taken when the form is populated.
- currentView is 'Work Package Form', not 'Form'. My first guard compared
against 'Form' and therefore returned false always: autosave was wired,
registered, and quietly dead. T4.2 had also introduced currentView='Form' in
its popstate handler; that is fixed here too, since it would have broken this
and anything else keyed off the view.
- settled() has to cancel the pending debounce. A save follows typing, so there
is nearly always a write already scheduled; without cancelling it the write
lands a second later and resurrects the draft that was just settled - and the
next load offers to recover work that is already saved.
VERIFICATION. tests/autosave_check.py, 23 checks, all passing:
- typing autosaves unprompted; the draft holds what was typed; it is scoped to
project AND package; and it does NOT appear in the outbox
- an untouched form is not dirty and arms no guard; a typed-in one does
- the draft survives a killed tab and is OFFERED back rather than applied
silently, saying plainly that nothing reached the project, via role=status
- restoring puts the work back in the form
- an explicit save settles the draft, and the probe asserts the save actually
landed first - otherwise the rest of that section proves nothing
- a simulated QuotaExceededError is reported as 'failed' with its reason, not
swallowed; a silent autosave failure is a safety net that is not there
- trackStepDwell still records an event
Two notes for later waves. The fixture's SOP defines no WP types, so
savePackage() legitimately refuses until the probe supplies one - worth knowing
before someone reads that as a bug. And native dialogs hung the headless browser
twice more in this task; with 79 of them in the app, any restore or save path
that reaches one will hang a test rather than fail visibly. S6/S7 in wave 9.
browser_check 71/71, f_items 5 FIXED / F6 REPRODUCES, url_state 23/23.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| b54034db04 |
T4.2 - S3: the app's state has an address; X1 is unblocked
Wave 0 counted pushState across html/ and found 0. Every page read its query
string once at boot and never wrote one again, so you could not send anyone a
link to WP07, a refresh dropped you back at the default view, and Back left the
app entirely because the app had never added a history entry.
CR-011 and CR-014 both promise an email carrying a direct link to a work package.
That is X1, and it was blocked on this. It is not blocked now.
html/wp-url.js is the whole mechanism, and it is deliberately NOT a router.
Nothing in it intercepts navigation or renders anything; it is the query string
treated as state that can be read, merged, written and subscribed to. Pages keep
their own rendering. Query parameters rather than a hash, because the server
already serves these paths and a hash is never sent to the server - which matters
the day a link has to be resolved before the page boots.
The merge behaviour is the part that earns its place: WPUrl.push({wp:id}) keeps
the active project, and WPUrl.push({wp:''}) clears one key without needing to know
what else is in the URL. Hand-built URLs losing ?project= is the usual way this
goes wrong.
WIRED: the creator (open package, dashboard view), the SOP wizard (tool, step),
the launcher (project). Each records a history entry only when the user chose the
change - restoring from the URL uses replace, or Back would immediately add an
entry and appear to do nothing.
WPUrl.absolute() is what CR-011/CR-014 will paste into an email in wave 8.
TWO BUGS THIS TASK CREATED AND FIXED, both found by the probe rather than by
reading:
- bootSOP() calls newPackage() during boot, and newPackage() cleared ?wp=. A
deep link therefore worked and then erased its own parameter, leaving Back
with nothing to return to. Now guarded on wpCreatorReady.
- goToStep() runs validateStep(), which ends in alert() when a required field
is empty - always true on a freshly loaded page. So restoring ?step=3 from a
shared link opened a modal dialog mid-boot, and hung the browser under CDP.
Restoring a view is not a forward navigation and no longer runs the
forward-navigation guard.
The second one is worth keeping in mind for the rest of wave 4: this app has 79
native dialogs, and any of them firing during a restore path will hang a headless
browser rather than fail visibly.
VERIFICATION. tests/url_state_check.py, 23 checks, all passing, covering every
done-when on the task:
- a URL identifying a work package opens that package
- the same URL for a SIGNED-OUT user goes to login, carries the target through
?next=, and lands on the work package itself after signing in
- refresh preserves project, package, tab and view
- Back and Forward move through states, verified as still-initialised rather
than reloaded, and with the dashboard actually rendered rather than only the
URL changed
- a different user opening the same URL reaches the same view
- nothing credential-shaped appears in the query string
Metric 8, pushState: was 0 at wave 0, now 2 in html/ (one pushState and one
replaceState, both in wp-url.js) behind 6 call sites across 4 files. The raw
count stays low by design - one place writes history, which is the same reason
the token work put one place in charge of colour.
browser_check 71/71, f_items 5 FIXED / F6 REPRODUCES, aggregates 16/16.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| ce6fb840f0 |
T3.5 - A5: one button system, and green goes back to being a status
251 buttons across the 7 pages, counted in the browser with every wizard step,
creator section and tool panel forced visible. Two thirds of them are
display:none at load, so a static grep sees about eighty and misses the rest.
FOUR ROLES, defined once in theme-light.css as --wp-btn-*, and no fifth:
primary the one action the screen exists for. Filled accent.
secondary every other real action. White, --border-strong, accent on hover.
tertiary navigating or undoing. No fill, no border, accent text.
danger destructive. Outlined red; filled red only where the control is too
small for an outline to read - the 28px x on a sequence row.
Every button class is mapped to a role in docs/reference/tokens.md section 12.
No value is new: these are the fills the sheets already rendered, given one
definition so that "primary" means one thing.
GREEN IS A STATUS COLOUR AND NO LONGER FILLS A BUTTON. A5 names two green action
buttons; there are four. .use-btn and the launcher's completed-SOP card button
never render green in the default fixture, so the review could not have seen
them - the SOP has to be finished and a suggested value has to be offered first.
.nav-btn.primary "SOP complete" wizard
.btn.btn-generate "Save & view" creator
.use-btn creator
.card.complete .card-button launcher
The green did not go anywhere. .cstatus button.on-cleared, .toggle-btn.enabled,
.wp-nav-dot.ok, .rb-ready, .badge-R and the launcher card's own left border and
status line all still carry it, and every one of those is a state rather than an
action. The launcher card in particular still says "complete" twice after this
change; it just no longer says it on the button.
SENTENCE CASE, applied to buttons and field labels only, which is the scope A5
sets. First word capitalised, the rest lowercased, acronyms and external proper
nouns left alone (SOP, QC, WP, UPN, PM/APM/CM/QM, PDF, JSON, CSV, BIM, MIMO,
Excel, Acumatica).
~30 button labels across launcher, wizard, creator, admin and two scripts
46 field labels
text-transform:uppercase removed from 4 rules - .btn and .add-btn (creator
buttons), label and .cmt-namebar label (creator field labels)
Labels carrying markup - a .req asterisk, a .help-tip chip - had only their text
nodes transformed, so the markup survives and "first word" means the first word
of the label rather than of each fragment. The creator's mono face, 10px size and
tracking are its idiom and are untouched; only the forced uppercase goes.
help.js was updated too. It names "Load Sample" and "SOP Complete" in prose, so
renaming the buttons without it would have left the help centre describing
controls that no longer exist. That coupling is the only place in the app where
button text is referenced by name.
Verified by re-running the inventory: 0 green action buttons, 0 uppercase button
labels, 251 buttons still present - nothing was lost in the rename.
console.css card headers are unchanged, confirmed by diff: the only six lines
this task touches in that file are token substitutions on button/button.primary/
button.danger, none of them within twenty lines of .card h2.
f_items 5 FIXED / F6 REPRODUCES. browser_check 71/71.
Left alone and logged: .step-tab is still uppercase (BL-015) - it is a stepper
tab, neither a button nor a field label, and A4/S9 rebuild the stepper. Table
headers, section eyebrows and headings keep their case throughout. BL-008 and
BL-009 were re-targeted from T3.5 to wave 9: both are colour merges on a field
fill and a status pill, and this task is scoped to buttons.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 928ab8c900 |
Archive projects, auto-add default members, rebuild the admin console
Three things asked for together, plus the migration they share (a7c31f9e5b02 —
additive, with database defaults for existing rows, so unlike the users.role
rewrite it is safe under a code-only rollback).
ARCHIVE A PROJECT. A finished job leaves every picker, switcher and search, and
freezes read-only, without losing anything. Hiding is free: GET /api/projects
defaults to archived=exclude, so the home picker and the app-bar switcher drop it
without either of them changing. Freezing is require_project_writable(), which
every write that lands on a project now goes through — SOP and WP upserts (both
ends, so a package can be moved neither into nor out of an archived job), deletes,
issue, status, WP archive, and comments on its WPs/SOPs. It answers 409, not 403:
nobody lacks a permission, the project's state is the objection, and the browser
outbox in project-data.js retires 4xx ops instead of retrying them against a job
that will never accept them. Unarchive and delete stay allowed on purpose —
unarchive is the one write an archived project must take, and archive-then-delete
is a normal sequence.
DEFAULT MEMBERS ON NEW PROJECTS. users.auto_add_projects / auto_add_role flag the
people who belong on every job, so an admin says it once instead of remembering it
at each project creation. It runs on the is_new branch of upsert_project, which is
the single road into project creation, so the home page, the sample project and the
demo seeder are all covered and an update never re-runs it. Note the interaction
with the existing creator-grant: that row commits first and add_default_members
never overwrites an existing membership, so the creator grant now carries the
creator's own auto_add_role — otherwise someone flagged "Project Admin on every
job" would land as a plain member on the one job they started themselves.
ADMIN CONSOLE. The user table had outgrown .wrap{max-width:860px}: nine columns in
an 860px card meant every cell wrapped, so one user occupied a ~100px band, the
action buttons stacked, and the table spilled outside its own white card. Now
1240px, with wide tables scrolling inside .tscroll so the page itself never scrolls
sideways, and one spacing/control scale across all twelve cards. Truncation hangs
off a span inside the cell rather than max-width on the td, which table-layout:auto
treats as advisory — the usual reason cell ellipsis works in the stylesheet and not
on the page.
Found in review and fixed here rather than later:
- Stored XSS in the new Projects card, reachable by any signed-in user, landing in
an admin's session. The uesc(v).replace(/'/g,"\'") idiom this file already used
in eight places escapes in the wrong order — uesc leaves backslashes alone, so a
stored name containing \' closes the JS string literal and the rest executes.
jsq() does backslash, then quote, then HTML, and all thirteen handler bindings go
through it. The same bug, unescaped entirely, was in the SOP builder's custom
constraint names (escHandlerArg there). Three of seven test payloads escaped the
literal under the old idiom — one of them a plain name ending in a backslash, so
it was breaking buttons for innocent input too.
- _save_comment resolved wp_id and sop_id with if/elif but stored both, so a
payload naming a WP you may touch and a SOP you may not was authorised on the WP
alone and still wrote into the other project's thread. Both are checked now.
- Promoting an account to admin left its default-member flag set but invisible,
ready to take effect again on demotion — cleared, as set_user_auto_add already
does for the role.
smoketest.py and the console's own smoke test both assert the archive round trip:
out of the default list, present with archived=all, writes refused with 409, and
all of it undone by unarchiving.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|||
| 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> |
|||
| 40bd19b6cf |
Stop the embedded creator collapsing to a 300x150 box; serve code network-first
The Creator rendered as a tiny double-scrolling square in the WP tab. My fault, and the mechanism matters more than the symptom: I had moved the iframe's sizing (width:100%, border:0, min-height) out of its inline style attribute and into work-package-suite-styles.css. The service worker cached the HTML and the stylesheet as INDEPENDENT entries, cache-first — so a browser could hold the new HTML together with the old CSS. With the inline sizing gone and the new rule absent, the iframe fell back to the HTML default 300x150 box and the whole tool collapsed. Moving self-contained markup into a separately-cached file created that window; nothing about the layout itself was wrong. Three layers so it cannot recur: - The iframe's width/border/min-height are inline again, on purpose, with a comment saying why. An iframe with no intrinsic size has a catastrophic failure mode, so its sizing must not depend on another file being in step. - applyEmbedLayout() now sets the fill height and width as INLINE styles via sizeWPFrame(). Inline beats any stylesheet, including a stale cached one, so the class is a refinement rather than a requirement. - sw.js: HTML/CSS/JS are now fetched NETWORK-FIRST with the cache as offline fallback; images/icons/manifest stay stale-while-revalidate. These files reference each other, so a page must never run against a stale sibling — this same staleness had already masked two other fixes during development. Cache bumped to v4. Verified: at 2560x1440 the tool spans the window with a single scrollbar; with work-package-suite-styles.css removed entirely (strictly worse than stale) the frame still measures 1469x662 instead of 300x150, and re-running the layout pass keeps it there; 12 checks across sop -> wp -> dashboard -> sop confirm body.embed-full, the content-area class, the fill class and the inline height are all cleared on the way out, so the wizard never ends up unscrollable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| 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> |
|||
| 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> |
|||
| 79b0e955b4 |
Wave 1: permissions roles, account-backed SOP team, critical constraints, password reset, BIM flag
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> |
|||
| 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> |
|||
| dd37f1f551 |
Keep the 2D sheets / spool-drawings step out of install-only sequences
That deliverable is a BIM/EWP output (and on install-only jobs Prime often doesn't own it), so it no longer appears in the default IWP sequence. It now lives in the BIM sequence as the hand-off step, so it only shows on BIM-enabled projects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| afdc815fb4 |
Store SOPs and Work Packages in the DB (shared across users)
Reintegrates C-West8's "storing data in DB instead of client only"
(commit
|
|||
| ca4176ac00 |
Sequence: BIM steps first, and default flow matches the field spec
- enableBIM() prepends the BIM steps (BIM precedes construction) instead of appending them. - Default construction sequence updated to the agreed flow (2D sheets / spool drawings -> conduit -> tray -> QC hold -> wire pull -> device -> termination -> QC hold -> commissioning -> as-built), with QC-hold gates. - Sample project now enables BIM/VDC so it demonstrates the full BIM -> construction sequence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| 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> |
|||
| 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> |
|||
| 5ad3ffa58e |
Per-project access control + UI/feedback/admin refinements
Access control: - project_members table; non-admins only see/operate on assigned projects (enforced across projects, SOPs, work packages — 403 else), admins bypass. Creating a project auto-grants its creator access. - Admin API to get/set a user's project assignments, plus a checkbox assignment dialog in the Admin Console user list. UI / workflow: - Login page: drop the "Prime Controls" wordmark next to the logo. - SOP tool: remove emoji icons from buttons and nav tabs. - Rename "Step Comments" to "Feedback"; the author auto-populates (read-only) from the signed-in user. - Move usage-log viewing to the Admin Console; add an admin card that lists all feedback/comments (who, what, page + step, when). - Sample project name -> "Micron FMCS Install (sample)". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| e3ef3b0023 |
Round-1 test feedback + smoke-test script
- API smoke test (server/smoketest.py): stdlib end-to-end check of health, projects, SOPs, WPs, the AWP issue gate (409 → 200), status, metrics, comments, and cascade delete. Referenced from DEPLOYMENT.md. SOP config: - Constraints: fix custom constraints never appearing — renderStandardConstraints no longer clobbers state.constraints; customs render in their own list with remove buttons; modal gains a free-text "Add" field. - Sources: add column headers (Data Type / Location-Platform / URL / Notes); preset data types are now fixed labels, "Add Source" creates an editable custom row. - Issuance strategy: add a tooltip + worked examples for each option. - Remove the "Comment submitted" acknowledgement popup (home + suite); keep the commenter name between comments. WP creator: - Clearing the last open constraint now offers to mark the package Issued and scrolls to the status control. - Form sections are collapsible (click a section heading to fold it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| 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> |
|||
| a18ae487f6 |
Fix SOP→home flow, stop sample-data fallback for real projects, add custom WP types
- SOP Complete now returns to the project home page after the confirmation popup (instead of staying on the SOP tab), and stamps projectId onto the SOP. - WP creator no longer substitutes the Micron SAMPLE_SOP when a project is active but its SOP isn't found — it shows a "complete the SOP first" empty state instead. Sample is only used for a standalone (no-project) preview. This was the source of "loaded with sample data I didn't select." - SOP WP Types step gains "+ Add Custom Type" (editable name + remove); blank-named custom types are dropped from the generated SOP. Custom types round-trip via the saved state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| 68c1c803d6 |
WP size: preset dropdown that sets the split threshold + show band in creator
Replaces the free-text "Typical WP Size" with a preset dropdown (Small / Standard / Large / Custom). Choosing a preset auto-fills the max-hours split threshold (still editable). The chosen band is now surfaced next to Est. Hrs in the creator's size hint (not just the View-SOP modal), alongside the threshold/over-threshold warning. Sample SOP aligned to Standard (80 hrs). repopulateForm preserves a non-preset saved value as a dropdown option. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
| e5c450597a |
Isolate SOP/WP data per project (namespaced localStorage)
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> |
|||
| 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> |
|||
| 4d111d608d | Merge branch 'main' into feat/wp-discipline-split-dashboard | |||
| fd668f0ea2 | Move static files into html/ for Docker bind mount |