From eb1497d574bea1ff574bb6bfa573f56f58cf1e35 Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Wed, 19 Aug 2026 12:51:44 -0700 Subject: [PATCH] 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 --- docs/reference/file-map.md | 6 ++ html/work-package-suite-app.js | 1 + html/wp-creation-app.js | 4 + html/wp-creation-styles.css | 19 +++- tests/export_check.py | 157 +++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 tests/export_check.py diff --git a/docs/reference/file-map.md b/docs/reference/file-map.md index a21fc38..384a2f8 100644 --- a/docs/reference/file-map.md +++ b/docs/reference/file-map.md @@ -296,6 +296,12 @@ python tests/materials_check.py # D6 - material list, the CR-005 pattern python tests/mreq_check.py # CR-013 - lightweight request, end to end 19 checks ``` +Wave 9 adds these: + +```bash +python tests/export_check.py # CR-008 - the export, walked field by field 17 checks +``` + **Three probes were re-pointed at `T7.1`.** `sections_check.py` 5b drove the live cross-frame toggle hand-off, which no longer exists; it now pins the surviving path and the fact that an UNSAVED toggle correctly does not travel - a behaviour change, recorded diff --git a/html/work-package-suite-app.js b/html/work-package-suite-app.js index 8d05f80..74b14df 100644 --- a/html/work-package-suite-app.js +++ b/html/work-package-suite-app.js @@ -456,6 +456,7 @@ function loadSampleData(){ // section added after today is not silently off for this SOP. if(typeof WPSections !== 'undefined'){ state.sections = WPSections.normalize({assets: false, kitting: false}); // CR-009: Micron EUV is not kitting today + state.fields = WPSections.normalizeFields({costCode: false, acumaticaTask: false}); // CR-002, mirrored on the export (T9.1) } // The Micron FMCS sample includes BIM/VDC — enable it so the sequence shows the diff --git a/html/wp-creation-app.js b/html/wp-creation-app.js index 84ba7bb..a687c42 100644 --- a/html/wp-creation-app.js +++ b/html/wp-creation-app.js @@ -16,6 +16,10 @@ const SAMPLE_SOP = { // Written as a partial map, not a full one: WPSections.normalize fills the rest // in as ON, so a section added later is not silently off for this SOP. sections:{assets:false, kitting:false}, // kitting off: Micron EUV is not kitting today (CR-009/T8.1) + // CR-002 / T9.1: the two fields the review removed, expressed as the toggles + // CLAUDE.md requires - hidden on the form AND absent from the export, while + // the columns, the model and every recorded value stay exactly where they are. + fields:{costCode:false, acumaticaTask:false}, project:{name:'Micron — INC Construction Work Packages', number:'26-67-008', client:'Micron Technology, Inc.', division:'Semiconductor', pm:'Nick Siegfried', cm:'K. Boyd', qm:'D. Nguyen', site:'Boise, ID — Fab'}, roles:[{role:'General Foreman',name:'M. Torres'},{role:'Superintendent',name:'K. Boyd'},{role:'Safety Manager / Lead',name:'A. Reyes'},{role:'Quality Manager',name:'D. Nguyen'},{role:'Planner',name:'L. Graver'}], governance:{ issuance:['By Sector / Area','By Discipline'], woSize:'Standard — 3–5 days (≈40–80 hrs)', woFormat:'WP##-[Sector]-[TYPE]', disciplines:['Mechanical','Electrical','Tech'], discMode:'choice', instanceSuffix:'letter', sizeHoursMax:'80' }, diff --git a/html/wp-creation-styles.css b/html/wp-creation-styles.css index 0150ce4..440b56e 100644 --- a/html/wp-creation-styles.css +++ b/html/wp-creation-styles.css @@ -344,7 +344,19 @@ .output-doc p { margin-bottom: 10px; } .output-doc ul { padding-left: 20px; margin-bottom: 10px; } .output-doc li { margin-bottom: 3px; } - .output-doc table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; border: 1px solid var(--border); } + .output-doc table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12px; border: 1px solid var(--border); + /* CR-008 / T9.1: the export opens on tablets. Fixed layout + wrap-anywhere + keeps every table inside the screen instead of laying out at its content's + natural 520px and dragging the whole document sideways. */ + table-layout: fixed; } + .output-doc td, .output-doc th { overflow-wrap: anywhere; } + /* On a phone/tablet the inline column widths the desktop layout carries + (width:200px on header cells) exceed the available line; !important is the + one CSS mechanism that outranks an inline style, which is exactly the job. */ + @media (max-width: 768px) { + .output-doc { padding: 20px 14px; } + .output-doc th, .output-doc td { width: auto !important; } + } .output-doc th { background: var(--surface2); border: 1px solid var(--border); padding: 6px 10px; font-family: var(--mono); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; text-align: left; color: var(--text-muted); } .output-doc td { border: 1px solid var(--border); padding: 7px 10px; vertical-align: top; } .output-doc .badge { display: inline-block; padding: 1px 8px; border-radius: 3px; font-family: var(--mono); font-size: 10px; font-weight: 600; } @@ -659,7 +671,10 @@ .modal { max-width:100% !important; } .output-toolbar { flex-wrap:wrap; } .material-actions .add-btn { flex:1 1 auto; } - table { min-width:520px; } /* keep columns legible; .table-wrap scrolls */ + /* Scoped to the FORM's scroll containers only. This was a bare `table` and + it reached the export document too, forcing every printed table to 520px + inside a 390px screen - the export must FIT a tablet, not scroll (CR-008). */ + .table-wrap table { min-width:520px; } /* keep columns legible; .table-wrap scrolls */ .cstatus button { padding:6px 8px; } } @media (max-width: 480px) { diff --git a/tests/export_check.py b/tests/export_check.py new file mode 100644 index 0000000..6d0f883 --- /dev/null +++ b/tests/export_check.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Does the export print what the field needs, and nothing it does not? — CR-008, T9.1. + +The export was never walked through in the meeting. This is the walk: every +required field present, CR-006-suppressed sections absent, the two CR-002 +fields (ACU Cost Code, Acumatica Task) appearing NOWHERE under the Micron +configuration, status carried by words (a black-and-white print keeps its +meaning), and the whole document legible on a tablet. + +Exit 0 all passed, 1 a failure, 2 could not run. +""" +import json +import os +import re +import sys +import tempfile +import time + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import cdp # noqa: E402 +from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402 +from sections_check import set_sop # noqa: E402 +from stepper_check import dismiss_dialogs # noqa: E402 + + +def ascii_(v, n=280): + return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n] + + +def settle(seconds=0.5): + time.sleep(seconds) + + +def wait_creator(page, tries=40): + for _ in range(tries): + if page.eval("!!window.wpCreatorReady"): + return True + time.sleep(0.3) + return False + + +def main(): + exe = cdp.find_browser() + if not exe: + print("no headless-capable browser found; set WP_BROWSER.") + return 2 + + tmpdir = tempfile.mkdtemp(prefix="wpsuite-export-") + db_path = os.path.join(tmpdir, "check.db") + server = None + browser = None + try: + tok = seed(db_path) + set_sop(db_path, {}) + port = cdp.free_port() + base = "http://127.0.0.1:%d" % port + server = start_server(port, db_path) + + browser = cdp.Browser(exe) + page = browser.page() + page.clear_cookies() + page.set_cookie("wp_session", tok["root"]) + page.viewport(1440, 900) + page.goto(base + "/wp-creation-index.html?project=projA") + dismiss_dialogs(page) + chk("the creator boots", wait_creator(page)) + settle(1.6) + page.eval("window.alert=()=>{}; window.confirm=()=>false; window.prompt=()=>null;") + + # The Micron configuration and its example package - the real shape. + page.eval("loadSampleSOP()") + settle(1.0) + page.eval("loadExample()") + settle(1.0) + doc = page.eval("(() => { renderPackage(collectPackage()); " + "return document.getElementById('pkg-doc').innerHTML; })()") + text = page.eval("(document.getElementById('pkg-doc')||{textContent:''}).textContent") + + print("\n1. every listed field, present") + for label, needle in [ + ("P6 Activity id + description", "P6 Activity"), + ("Priority", "Priority"), + ("scope of work", "Scope & Work"), + ("materials", "Material List"), + ("constraints", "Constraints — Release Readiness"), + ("QA/QC", "Quality & Hold Points"), + ]: + chk("the export carries %s" % label, needle in text or needle in doc, needle) + chk("...and the location row (Building / Floor / Sector is a row of " + "General Information)", "Location" in text) + chk("constraint status prints as WORDS - a black-and-white print keeps " + "its meaning", "Cleared" in text and "Open" in text) + + print("\n2. what must be absent, absent") + chk("ACU Cost Code appears nowhere", "Cost Code" not in text) + chk("Acumatica Task appears nowhere", "Acumatica Task" not in text) + chk("the toggled-off sections are absent (assets, kitting - the Micron " + "sample's CR-006 state)", + "Kitting & MIMO" not in text and "Asset" not in text.replace("Assets is off", "")) + + print("\n3. the attachment index") + chk("every attachment is listed", "Drawings & Attachments" in text) + chk("...uploads would carry their descriptions (the columns exist)", + "Focus area" in text or "Link / Note" in text) + + print("\n4. the tablet") + page.viewport(390, 844, mobile=True) + settle(0.8) + page.eval("renderPackage(collectPackage())") + settle(0.8) + # scrollWidth vs clientWidth is the wrong ruler for a padded overflow-y + # container: Chrome reports scrollWidth as content+padding even with + # nothing protruding, so the naive comparison is off by exactly the + # padding+border. What legibility actually needs: the BOX fits the + # screen, and no element inside pokes past the box. + fits = page.eval("""(() => { + const d = document.getElementById('pkg-doc'); + const dr = d.getBoundingClientRect(); + const cs = getComputedStyle(d); + let poke = 0; + d.querySelectorAll('*').forEach(el => { + const r = el.getBoundingClientRect(); + if (r.right > dr.right + 2) poke++; + }); + return {boxFits: dr.width <= 390, poke, font: parseFloat(cs.fontSize)}; + })()""") + chk("390px: the document fits the screen and nothing inside pokes past it", + fits["boxFits"] and fits["poke"] == 0, ascii_(fits)) + chk("390px: the base type is readable (>= 12px)", fits["font"] >= 12, ascii_(fits)) + + js_errors = [e for e in page.js_errors() if "beforeunload" not in e] + chk("no JavaScript errors anywhere in this run", not js_errors, + ascii_(js_errors[:2])) + + finally: + if browser is not None: + try: + browser.close() + except Exception: + pass + if server is not None: + try: + server.terminate() + except Exception: + pass + + print("\n" + "-" * 54) + print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL))) + for f in _FAIL: + print(" - " + f) + return 1 if _FAIL else 0 + + +if __name__ == "__main__": + sys.exit(main())