Files
Project-SDE-WP-Suite/html/wp-creation-index.html
n.siegfried 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>
2026-08-18 14:26:27 -05:00

504 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Work Package (IWP) — Prime Controls</title>
<script src="auth-guard.js"></script>
<!-- Date/number formatting. Must parse BEFORE the app scripts: they format
timestamps during their own boot. -->
<script src="wp-format.js"></script>
<!-- Addressable state (S3). Parses before the app scripts, which read the URL
during their own boot. -->
<script src="wp-url.js"></script>
<!-- Autosave, unsaved-work guard, draft recovery (S2). -->
<script src="wp-autosave.js"></script>
<!-- Which sections this project uses (CR-006). The same file the SOP wizard
reads, so "Assets is off" means one thing in both. -->
<script src="wp-sections.js"></script>
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="manifest" href="manifest.webmanifest">
<meta name="theme-color" content="#161616">
<link rel="stylesheet" href="theme-light.css">
<!-- B7/T7.1: the app bar. This page used to load neither of wp-chrome's two
files because it was an iframe child and wp-chrome.js returned early inside
one. It is a page now. -->
<link rel="stylesheet" href="wp-chrome.css">
<link rel="stylesheet" href="wp-creation-styles.css">
<link rel="stylesheet" href="wp-sidenav.css">
</head>
<body>
<div class="loading-overlay" id="loadingOverlay"><div class="spinner"></div><div class="loading-text">Saving work package…</div></div>
<!-- APP BAR (B7 / T7.1)
This page loaded neither wp-chrome.css nor wp-chrome.js while it was an
iframe child - it was the only page in html/ without an app bar, which is
what made it read as part of the wizard rather than as a page. It has one
now, in the same .header-left / .header-right shape the suite page uses, so
wp-chrome.js inserts the project switcher and search at the same place on
both. -->
<div class="header">
<div class="header-left">
<a href="index.html" class="header-logo-chip" title="Work Package Suite home">
<span class="wp-logo-chip"><img src="prime-controls-logo.jpg" alt="Prime Controls"></span>
</a>
<div class="header-title">Work Package Suite</div>
<button id="dev-toggle" class="dev-toggle" onclick="toggleDevMode()" title="dev mode" aria-label="dev mode"></button>
</div>
<!-- wp-chrome.js inserts the project switcher and global search here. -->
<div class="header-right">
<button class="btn btn-ghost" id="comments-btn" style="padding:7px 16px" onclick="toggleComments()">Feedback <span class="cbadge-total" id="cbadge-total" style="display:none">0</span></button>
<button class="btn btn-ghost" style="padding:7px 16px" onclick="openHelp()">Help</button>
</div>
</div>
<!-- TOOL TABS (B7 / T7.1)
The tab row used to live in the parent document and the toolbar in this one,
which is the thing B7 says makes folding either into the other impossible.
Both are here now. `Dashboard` was a toolbar button; it is a tab, because
that is what it always was.
SOP Configuration is an <a> because it goes to another document; the other
two are <button>s because they switch view inside this one. wp-chrome.js
stamps ?project= onto the link once the active project is known. -->
<div class="main-nav" role="navigation" aria-label="Work package tools">
<a class="nav-tab" data-nav-href="work-package-suite.html?tab=sop">SOP Configuration</a>
<button class="nav-tab" data-tab="wp" onclick="showForm()">Work Package Creation</button>
<button class="nav-tab" data-tab="dashboard" onclick="showDashboard()">Dashboard</button>
</div>
<!-- PACKAGE TOOLBAR
What is left of the old header once the brand, the feedback panel and the
Dashboard button have found their proper homes: actions on the package or on
the SOP behind it. `Sample SOP` and `Load example` are visible here rather
than hidden by body.embedded (D1). They are still two affordances under two
names, which is S7 and is T9.4's to reduce - this task makes them reachable,
not fewer. -->
<div class="wp-toolbar" id="wp-toolbar">
<button class="btn btn-ghost" onclick="newPackage()">+ New</button>
<button class="btn btn-ghost" onclick="duplicateWP()">Duplicate</button>
<button class="btn btn-ghost" onclick="showHistoryCurrent()" title="Change history for this work package">History</button>
<span class="wp-toolbar-sep" aria-hidden="true"></span>
<button class="btn btn-ghost" onclick="openSopModal()">View SOP</button>
<button class="btn btn-ghost" onclick="document.getElementById('sop-import').click()">Import SOP</button>
<input type="file" id="sop-import" accept="application/json" style="display:none" onchange="importSOP(event)">
<button class="btn btn-ghost" onclick="loadSampleSOP()">Sample SOP</button>
<button class="btn btn-ghost" onclick="loadExample()">Load example</button>
<span class="wp-toolbar-sep" aria-hidden="true"></span>
<!-- T7.10 moves this to the admin console and deletes one of the two
implementations. It stays visible here until then rather than being
deleted by the task that dissolved the frame. -->
<button class="btn btn-ghost" onclick="showAnalytics()">Usage data</button>
</div>
<div class="dev-banner" id="dev-banner" style="display:none">⚙ DEV MODE — usage tracking paused. This session's actions are not being recorded.</div>
<div class="ctx-bar" id="ctx-bar"></div>
<!-- RELEASE READINESS BANNER -->
<div class="release-banner" id="release-banner"></div>
<!-- SECTION NAV (jump links, built from the form cards) -->
<div class="section-nav-bar" id="section-nav"></div>
<div class="wp-layout">
<!-- WORK PACKAGE NAVIGATOR
A persistent side panel (not a hover drawer): collapse toggle, a primary action,
icon nav, then the project's packages as rows with colour-coded initial badges.
Collapsing leaves a narrow icon rail so you can still see and switch packages. -->
<aside class="wp-nav" id="wp-nav" aria-label="Work packages">
<div class="wp-nav-top">
<button class="wp-nav-toggle" id="wp-nav-toggle" onclick="toggleWpNav()"
title="Collapse the panel" aria-label="Collapse the panel" aria-expanded="true">
<svg viewBox="0 0 20 20" width="18" height="18" aria-hidden="true">
<rect x="2.5" y="3.5" width="15" height="13" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/>
<line x1="7.5" y1="3.5" x2="7.5" y2="16.5" stroke="currentColor" stroke-width="1.4"/>
<path class="wp-nav-toggle-arrow" d="M14 10 H10 M11.6 8.2 L9.8 10 L11.6 11.8"
fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="wp-nav-primary">
<button class="wp-nav-cta" onclick="newPackage()" title="Start a new work package">
<span class="wp-nav-cta-plus" aria-hidden="true">+</span><span class="wp-nav-cta-label">New work package</span>
</button>
<button class="wp-nav-cta-more" id="wp-nav-more-btn" onclick="toggleWpNavMore(event)"
title="More actions" aria-label="More actions" aria-haspopup="true" aria-expanded="false"></button>
<div class="wp-nav-menu" id="wp-nav-more" hidden>
<button type="button" onclick="wpNavAction('duplicate')">Duplicate this package</button>
<button type="button" onclick="wpNavAction('split')">Split by discipline</button>
<button type="button" onclick="wpNavAction('export')">Export all (JSON)</button>
</div>
</div>
<nav class="wp-nav-links" aria-label="Views">
<button type="button" class="wp-nav-link" data-view="mine" onclick="setWpNavView('mine')" title="Packages you own">
<span class="wp-nav-ico" aria-hidden="true"></span><span class="wp-nav-link-label">My packages</span>
<span class="wp-nav-link-n" id="wp-nav-n-mine"></span>
</button>
<button type="button" class="wp-nav-link is-current" data-view="all" onclick="setWpNavView('all')" title="Every package on this project">
<span class="wp-nav-ico" aria-hidden="true"></span><span class="wp-nav-link-label">All packages</span>
<span class="wp-nav-link-n" id="wp-nav-n-all"></span>
</button>
<button type="button" class="wp-nav-link" data-view="open" onclick="setWpNavView('open')" title="Not release-ready yet">
<span class="wp-nav-ico" aria-hidden="true"></span><span class="wp-nav-link-label">Needs attention</span>
<span class="wp-nav-link-n" id="wp-nav-n-open"></span>
</button>
<button type="button" class="wp-nav-link" onclick="showDashboard()" title="Status and gating across the project">
<span class="wp-nav-ico" aria-hidden="true"></span><span class="wp-nav-link-label">Dashboard</span>
</button>
</nav>
<div class="wp-nav-sect">
<span class="wp-nav-sect-label" id="wp-nav-sect-label">Work packages</span>
<span class="wp-nav-count" id="wp-nav-count"></span>
</div>
<div class="wp-nav-filter">
<input type="search" class="wp-nav-search" id="wp-nav-search" placeholder="Filter packages…" oninput="renderWpNav()">
</div>
<div class="wp-nav-list" id="wp-nav-list"></div>
</aside>
<div class="main">
<!-- PACKAGE KIND (only shown when the project's SOP includes BIM/VDC) -->
<div class="card" id="kind-row" style="display:none">
<div class="sub-heading">Package Type</div>
<div class="notice">This project includes BIM/VDC packages. Choose what this one is — it tailors the fields below and the WP types / release gates offered.</div>
<div class="radio-group" id="kind-group" style="margin-bottom:0">
<label class="radio-pill" data-val="iwp"><input type="radio" name="pkgkind" onclick="setKind('iwp')"><span class="dot"></span>Install package (IWP)</label>
<label class="radio-pill" data-val="ewp"><input type="radio" name="pkgkind" onclick="setKind('ewp')"><span class="dot"></span>BIM package (EWP)</label>
</div>
</div>
<!-- GENERAL INFORMATION -->
<!-- CR-006 gives every toggleable section a stable id. The map lives in
wp-creation-app.js (WP_SECTION_NODES); the ids are what it addresses. -->
<div class="card" id="general-card">
<div class="section-header"><div class="section-title">General Information</div>
<div class="section-desc">Parameters in <span style="color:var(--accent)">blue</span> are inherited from the project SOP. Fill the rest for this package.</div></div>
<div class="field-grid">
<div class="field"><label>WP number <span class="auto-tag">auto</span><span class="help-tip" data-tip="Built automatically from the SOP number format — the scope fields below (e.g. Sector) plus the WP type and a sequence counter.">i</span></label><input type="text" id="wp_number" readonly class="locked-field" placeholder="auto-built"><div class="field-hint sop-hint" id="wp_number_hint"></div></div>
<div class="field"><label>Status</label>
<div class="radio-group" id="status-group" style="margin-bottom:0">
<label class="radio-pill" data-val="Draft"><input type="radio" name="status"><span class="dot"></span>Draft</label>
<label class="radio-pill" data-val="Scheduled"><input type="radio" name="status"><span class="dot"></span>Scheduled</label>
<label class="radio-pill" data-val="Issued"><input type="radio" name="status"><span class="dot"></span>Issued</label>
<label class="radio-pill" data-val="In Progress"><input type="radio" name="status"><span class="dot"></span>In progress</label>
<label class="radio-pill pill-hold" data-val="Issue"><input type="radio" name="status"><span class="dot"></span>Issue (hold)</label>
<label class="radio-pill" data-val="QC"><input type="radio" name="status"><span class="dot"></span>QC</label>
<label class="radio-pill" data-val="Closed"><input type="radio" name="status"><span class="dot"></span>Closed</label>
</div>
<div class="field-hint">Cannot move to <strong>Issued</strong> or beyond until all constraints are cleared.</div>
</div>
</div>
<div class="notice">WP number builds automatically from these scope fields + the WP type (per the SOP naming format):</div>
<div class="field-grid" id="number-dims"></div>
<div class="field field-grid col1"><div class="field"><label>Subject / title <span class="req">*</span></label><input type="text" id="wp_subject" placeholder="e.g. Utility Level 2P Inert Gas Room Wall Mount Midas/ Rack"></div></div>
<div class="field-grid">
<div class="field"><label>WP type <span class="req">*</span></label><select id="wp_type" onchange="onTypeChange()"></select><div class="field-hint sop-hint">from SOP types</div></div>
<div class="field"><label>System / facility code / UPN</label><input type="text" id="wp_system" placeholder="ties to controls.dev / COIN"></div>
<!-- Location is its own CR-006 section while still living inside General
Information's grid. CR-004 gives it structured building/floor/sector
fields of its own in wave 6; only this wrapper's contents change then. -->
<!-- CR-004. Three dependent dropdowns off the project's own taxonomy
(CR-005 / T5.4), not free text: the values are what CR-018 rolls cost
up by, and a rollup keyed on what somebody typed is not a rollup.
wp_location survives as a hidden field so a package written before
this keeps what it said — CLAUDE.md, removals are hidden not deleted. -->
<div class="field" id="location-card">
<label for="wp_building">Location<span class="help-tip" data-tip="Building, floor and sector come from this project's own location list, configured on step 11 of the SOP. Cost and progress roll up by these, which is why they are picked rather than typed.">i</span></label>
<div class="loc-picker">
<select id="wp_building" aria-label="Building"></select>
<select id="wp_floor" aria-label="Floor"></select>
<select id="wp_sector" aria-label="Sector"></select>
</div>
<div class="field-hint" id="wp_location_note"></div>
<input type="hidden" id="wp_location">
</div>
<!-- CR-002 hides these two rather than deleting them: the columns, the model
and every value already captured stay exactly as they are, and another
project can turn them back on without a code change. The ids are what
WP_FIELD_NODES addresses. -->
<div class="field" id="field-costCode"><label>Cost code</label><select id="wp_cost"></select><div class="field-hint sop-hint">Acumatica cost codes</div></div>
<div class="field" id="field-acumaticaTask"><label>Acumatica task</label><input type="text" id="wp_wbs" placeholder="Acumatica task no."></div>
</div>
<div class="field-grid">
<div class="field"><label>Owner <span class="help-tip" data-tip="The accountable owner (a user account on this project). Assigning notifies them by email if email notifications are enabled in the admin console.">i</span></label><select id="wp_assignee"><option value="">— Unassigned —</option></select></div>
<div class="field"><label>Assignees<span class="help-tip" data-tip="The crew and staff working this package. Pick from the project team named on the SOP; anyone without a user account can still be added by name.">i</span></label>
<div class="people-pick" id="pick_assignees"></div>
<input type="hidden" id="wp_assignees"></div>
<div class="field"><label>Distribution<span class="help-tip" data-tip="Who gets notified about this package. The project's Construction Manager is included by default and can be removed per package.">i</span></label>
<div class="people-pick" id="pick_distribution"></div>
<input type="hidden" id="wp_distribution"></div>
<!-- CR-003. Three levels, agreed live in the meeting, and there is no fourth.
Independent of status: a package can become Urgent after it is issued
without its status moving. -->
<div class="field"><label>Priority <span class="req">*</span><span class="help-tip" data-tip="How urgent this package is, independently of its status and its due date. Normal is the baseline; High and Urgent are exceptions and are meant to stay rare.">i</span></label>
<select id="wp_priority">
<option value="Normal" selected>Normal</option>
<option value="High">High</option>
<option value="Urgent">Urgent</option>
</select></div>
<div class="field"><label>Due date</label><input type="date" id="wp_due"></div>
<!-- CR-001. Beside the due date on purpose: a date on a work package that
is not anchored to a schedule activity is a date floating on its own,
and the meeting placed the activity next to it for exactly that reason.
Free text — a validated lookup against an imported P6 activity list is
deferred (BL-000a) partly because the Micron schedule is being reworked,
and importing it now would import churn. -->
<div class="field"><label>P6 activity ID<span class="help-tip" data-tip="The Primavera P6 schedule activity this package delivers. Free text for now — a validated lookup against an imported activity list is deferred while the schedule is being reworked.">i</span></label>
<input type="text" id="wp_p6_id" placeholder="e.g. A1234"></div>
<div class="field"><label>P6 activity description</label>
<input type="text" id="wp_p6_desc" placeholder="what that activity covers"></div>
<div class="field"><label>Specification section</label>
<input type="text" id="wp_spec" readonly class="locked-field" placeholder="set on the WP type in the SOP">
<div class="field-hint" id="spec-folder-link"></div></div>
</div>
<div class="field field-grid col1"><div class="field"><label>Description</label><textarea id="wp_desc" rows="2" placeholder="Short summary of the package"></textarea></div></div>
<div class="field field-grid col1" id="bimlink-wrap"><div class="field"><label>Enabled by — BIM package(s)<span class="help-tip" data-tip="Advanced Work Packaging traceability: link the BIM / model package(s) that enabled this install package. Paste the MWP number(s) or a link to the model package.">i</span></label><input type="text" id="wp_bimlink" placeholder="e.g. MWP07-FAB-CONDUITS, or a link to the model package"></div></div>
</div>
<!-- BIM / MODEL DETAILS (shown for BIM/VDC SOPs) -->
<div class="card" id="bim-card" style="display:none">
<div class="sub-heading">BIM / Model Details</div>
<div class="notice">For BIM/VDC work packages — the model deliverable's level of detail, area, source scan, and coordination status.</div>
<div class="field-grid">
<div class="field"><label>Model area / zone</label><input type="text" id="wp_model_area" placeholder="e.g. Fab 09 Subfab — Level 2"></div>
<div class="field"><label>Clash / coordination status</label>
<select id="wp_clash" onchange="onClashChange()"><option value=""></option><option>Not started</option><option>In coordination</option><option>Clashes open</option><option>Clash-free</option><option>Signed off (IFF)</option></select></div>
<div class="field"><label>IFF #<span class="help-tip" data-tip="Issued-For-Fabrication/Field number — the GC sign-off reference for this model package. Required once the coordination status is Signed off (IFF).">i</span></label>
<input type="text" id="wp_iff" placeholder="e.g. IFF-2026-0142" oninput="onClashChange()">
<div class="field-hint" id="iff-hint"></div></div>
<div class="field"><label>Linked scan / point cloud</label><input type="url" id="wp_scan_link" placeholder="WebShare / BIM360 / SharePoint link"></div>
</div>
</div>
<!-- ASSETS (controls.dev) -->
<div class="card" id="asset-card">
<div class="sub-heading">Assets</div>
<div class="notice">Every work package is based on one or more assets managed in <strong>controls.dev</strong>. Paste the controls.dev link for each asset this package covers. <span style="color:var(--text-dim)">A direct integration to pick assets from a list is planned — for now, link them manually.</span></div>
<div class="table-wrap"><table><thead><tr><th style="width:200px">Asset Tag / ID</th><th>Description</th><th>controls.dev Link <span class="req">*</span></th><th style="width:44px"></th></tr></thead><tbody id="asset-body"></tbody></table></div>
<button class="add-btn" onclick="addAsset()">+ Add asset</button>
</div>
<!-- DISCIPLINES -->
<div class="card" id="discipline-card" style="display:none">
<div class="sub-heading">Disciplines<span class="help-tip" data-tip="Pick every discipline this package covers. Choosing two or more turns Scope into per-discipline sections and enables Split by Discipline.">i</span></div>
<div class="notice" id="discipline-note"></div>
<div class="disc-picker" id="discipline-picker"></div>
</div>
<!-- SCOPE & WORK -->
<div class="card" id="scope-card">
<div class="sub-heading">Scope & Work<span class="help-tip" data-tip="Ordered steps the crew performs. With multiple disciplines selected, each gets its own scope section and status. Use Split by Discipline to break a large package into WP01A / WP01B / WP01C instances.">i</span></div>
<div id="flat-scope">
<div class="field"><label>Description of work (sequenced steps)</label>
<div class="notice">Enter the work as ordered steps — added in sequence, the way the crew performs them.</div>
<div id="worksteps-body"></div>
<button class="add-btn" onclick="addWorkStep()">+ Add step</button>
</div>
</div>
<div id="scope-by-discipline" style="display:none"></div>
<button class="btn btn-ghost" id="split-disc-btn" style="display:none;margin-top:10px" onclick="splitByDiscipline()" title="Break this multi-discipline package into one numbered instance per discipline">⎘ Split by discipline</button>
<div class="field-grid" style="margin-top:14px">
<div class="field"><label>Labor est. hrs.</label><input type="number" id="wp_hours" min="0" step="1" placeholder="e.g. 20" oninput="onHoursChange()"><div class="field-hint" id="size-check"></div></div>
<div class="field"><label>Predecessor work packages<span class="help-tip" data-tip="The packages that must be Closed before this one can be released. A package with an open predecessor is not release-ready — you can still release it, but the override is logged.">i</span></label>
<div class="people-pick" id="pick_predecessors"></div>
<div class="field-hint" id="pred-hint"></div></div>
<div class="field"><label>Sequence phase <span class="help-tip" data-tip="Which phase of the SOP's construction sequence this package belongs to. Descriptive — it does not gate release; predecessor packages do.">i</span></label>
<select id="wp_seq"></select><div class="field-hint sop-hint">from the SOP construction sequence</div></div>
</div>
</div>
<!-- MATERIAL LIST -->
<div class="card" id="material-card">
<div class="sub-heading">Material List<span class="help-tip" data-tip="Bill of materials — feeds kitting. On a multi-discipline package each line can be tagged to a discipline so a split routes each instance only its own materials. Import from CSV is supported.">i</span></div>
<div class="notice">Structured bill of materials. Feeds kitting and the delivery forecast. Unit is from the Acumatica unit list.</div>
<div class="table-wrap"><table><thead><tr><th style="width:90px">Qty</th><th style="width:120px">Unit</th><th>Description</th><th id="mat-disc-th" style="width:140px;display:none">Discipline</th><th style="width:44px"></th></tr></thead><tbody id="material-body"></tbody></table></div>
<div class="material-actions">
<button class="add-btn" onclick="addMaterial()">+ Add material line</button>
<button class="add-btn" onclick="document.getElementById('material-import').click()">⤒ Import from Excel/CSV</button>
<button class="add-btn" onclick="downloadMaterialTemplate()">⤓ Download template</button>
<input type="file" id="material-import" accept=".csv,.xlsx,.xls" style="display:none" onchange="importMaterials(event)">
</div>
</div>
<!-- DRAWINGS / ATTACHMENTS -->
<div class="card" id="drawings-card">
<div class="sub-heading">Drawings & Attachments</div>
<div id="sop-ref-links" class="sop-ref-links"></div>
<div class="table-wrap"><table><thead><tr><th>Document / Drawing</th><th style="width:90px">Rev</th><th>Link / Note</th><th style="width:44px"></th></tr></thead><tbody id="attach-body"></tbody></table></div>
<button class="add-btn" onclick="addAttach()">+ Add document</button>
<button class="add-btn" onclick="toggleSopFilePanel()">+ Add files from SOP folder</button>
<div id="sop-file-panel" style="display:none; margin-top:0.75rem; padding:0.75rem; border:1px dashed var(--border); border-radius:6px; background:var(--bg);">
<div id="sop-file-folders"></div>
<div class="field-hint" style="margin-top:0.5rem;">2) Paste the file links here, one per line:</div>
<textarea id="sop-file-links" rows="4" placeholder="https://primecontrolsdallas.sharepoint.com/:b:/r/.../Drawings/EE-1YA-2P.pdf?csf=1&amp;web=1&amp;e=..." style="width:100%; font-size:12px; padding:0.5rem; border:1px solid var(--border); border-radius:4px; box-sizing:border-box;"></textarea>
<div style="margin-top:0.5rem; display:flex; gap:0.5rem;">
<button class="add-btn" onclick="addPastedFileLinks()">Add to attachments</button>
<button class="add-btn" onclick="toggleSopFilePanel()" style="background:transparent;">Cancel</button>
</div>
</div>
</div>
<!-- KITTING & MIMO -->
<div class="card" id="mimo-card">
<div class="sub-heading">Kitting & Material Movement (MIMO)</div>
<div class="field-grid">
<div class="field"><label>Kitting status</label>
<select id="wp_kit_status"><option value=""></option><option>Open</option><option>In Progress</option><option>Kitted</option><option>Delivered</option></select></div>
<div class="field"><label>Warehouse owner</label><input type="text" id="wp_kit_owner" placeholder="name"></div>
<div class="field"><label>Kitting need date</label><input type="date" id="wp_kit_date"></div>
<div class="field"><label>MIMO sch. time</label><input type="datetime-local" id="wp_mimo_time"><div class="field-hint">scheduled material-move date &amp; time</div></div>
<div class="field"><label>MIMO location</label><input type="text" id="wp_mimo_loc" placeholder="staging / move location"></div>
</div>
</div>
<!-- CONSTRAINTS / RELEASE READINESS -->
<div class="card" id="constraint-card">
<div class="sub-heading">Constraints — Release Readiness<span class="help-tip" data-tip="A package can't be Issued until every constraint is Cleared or N/A. If one reopens after release, the package drops to Issue (Hold).">i</span></div>
<div class="notice">Per AWP, a package is not released to the field until every constraint is <strong>Cleared</strong> or <strong>N/A</strong>. If a constraint reopens after release, status drops to <strong>Issue (Hold)</strong>.</div>
<div class="table-wrap"><table><thead><tr><th>Constraint</th><th style="width:230px">Status</th><th>Comment</th></tr></thead><tbody id="constraint-body"></tbody></table></div>
</div>
<!-- QUALITY & HOLD POINTS -->
<div class="card" id="quality-card">
<div class="sub-heading">Quality, Inspection & Hold Points</div>
<div class="field-grid">
<div class="field"><label>QC required</label><input type="text" id="wp_qc" placeholder="from SOP" readonly>
<div class="lock-row"><button type="button" class="lock-edit" onclick="editQuality('wp_qc')">🔒 Edit (reason required)</button><span class="override-note"></span></div></div>
<div class="field"><label>Photo documentation</label><input type="text" id="wp_photo" placeholder="from SOP" readonly>
<div class="lock-row"><button type="button" class="lock-edit" onclick="editQuality('wp_photo')">🔒 Edit (reason required)</button><span class="override-note"></span></div></div>
</div>
<div class="field field-grid col1"><div class="field"><label>Witness / hold points</label><textarea id="wp_hold" rows="2" readonly placeholder="from SOP"></textarea>
<div class="lock-row"><button type="button" class="lock-edit" onclick="editQuality('wp_hold')">🔒 Edit (reason required)</button><span class="override-note"></span></div>
<div class="field-hint">Inherited from the SOP. A <strong>Hold Point</strong> stops work until inspection sign-off; a <strong>Witness Point</strong> is offered for inspection but work may proceed if declined.</div></div></div>
</div>
<!-- SIGN-OFFS -->
<div class="card">
<div class="sub-heading">Approvals & Sign-offs</div>
<div class="notice">Per the AWP IWP checklist. A package should be signed by these roles before release.</div>
<div class="table-wrap"><table><thead><tr><th style="width:220px">Role</th><th>Name</th><th style="width:150px">Date</th><th style="width:80px;text-align:center">Signed</th></tr></thead><tbody id="signoff-body"></tbody></table></div>
</div>
<!-- CLOSEOUT -->
<div class="card" id="closeout-card">
<div class="sub-heading">Closeout</div>
<div class="notice">Completed at QC / Closed — captures as-built reality and lessons learned.</div>
<div class="field-grid">
<div class="field"><label>Actual hrs.</label><input type="number" id="wp_actual_hrs" min="0" step="1"></div>
<div class="field"><label>Installed quantity</label><input type="text" id="wp_installed_qty" placeholder="e.g. 42 of 42 tags"></div>
</div>
<div class="field field-grid col1"><div class="field"><label>Redlines / as-built notes</label><textarea id="wp_redlines" rows="2"></textarea></div></div>
<div class="field field-grid col1"><div class="field"><label>Lessons learned</label><textarea id="wp_lessons" rows="2"></textarea></div></div>
</div>
<div class="nav-row"><button class="btn btn-ghost" onclick="newPackage()">↺ Clear</button>
<div style="display:flex;gap:10px">
<button class="btn btn-ghost" onclick="savePackage(false)">Save draft</button>
<button class="btn btn-generate" onclick="savePackage(true)">⚡ Save &amp; view</button>
</div></div>
<!-- DASHBOARD -->
<div id="dashboard-view" style="display:none">
<div class="output-toolbar">
<button class="btn btn-ghost" onclick="showForm()">← Back to form</button>
<div style="font-weight:700;font-size:15px">Work Package Dashboard</div>
<div style="display:flex;gap:10px;margin-left:auto">
<button class="btn btn-ghost" onclick="newPackage()">+ New WP</button>
<button class="btn btn-ghost" onclick="renderDashboard()">↻ Refresh</button>
<button class="btn btn-ghost" onclick="exportPackages()">⤓ Export (JSON)</button>
</div>
</div>
<div class="ctx-bar" style="margin:0 0 14px"><div class="field-hint">Status, metrics and gating across all saved work packages on this device. <span style="color:var(--text-dim)">Reads local data now; wires to the shared SQL database in Phase 2.</span></div></div>
<div id="dash-body"></div>
</div>
<!-- OUTPUT -->
<div id="pkg-output" style="display:none">
<div class="output-toolbar">
<button class="btn btn-ghost" onclick="showForm()">← Back to form</button>
<button class="btn btn-primary" onclick="printPackage()">⎙ Print / save PDF</button>
<button class="btn btn-ghost" onclick="exportPackages()">⤓ Export (JSON)</button>
</div>
<div class="output-doc" id="pkg-doc"></div>
</div>
<!-- SAVED -->
<div class="card" id="saved-card" style="display:none">
<div class="sub-heading">Saved Work Packages <span id="saved-count"></span></div>
<div class="table-wrap"><table><thead><tr><th>WP #</th><th>Type</th><th>Subject</th><th>Status</th><th style="width:110px">Ready?</th><th style="width:120px"></th></tr></thead><tbody id="saved-body"></tbody></table></div>
<button class="add-btn" onclick="exportPackages()">⤓ Export all (JSON)</button>
<button class="add-btn" onclick="clearSaved()">Clear all</button>
</div>
</div>
</div>
<!-- HOLD LOG MODAL (comment 7) -->
<div class="modal-overlay" id="hold-modal">
<div class="modal">
<div class="modal-head"><div class="modal-title">Log Hold — On-Hold Constraint</div><button class="cmt-x" onclick="cancelHold()" title="Cancel"></button></div>
<div class="modal-body">
<div class="notice">Moving a package to <strong>Issue (Hold)</strong> requires logging the constraint that blocked it.</div>
<div class="field"><label>Constraint type <span class="req">*</span></label><select id="hold-constraint"></select></div>
<div class="field"><label>Details <span class="req">*</span></label><textarea id="hold-details" rows="3" placeholder="What reopened / blocked this package?"></textarea></div>
<div class="field"><label>Supporting document link</label><input type="text" id="hold-doclink" placeholder="link to RFI, photo, email, etc. (optional)"></div>
<div class="field"><label>Supporting photo</label><input type="file" id="hold-photo" accept="image/*" onchange="holdPhotoChange(event)"><div class="hold-photo-preview" id="hold-photo-preview"></div></div>
</div>
<div class="modal-foot"><button class="btn btn-ghost" onclick="cancelHold()">Cancel</button><button class="btn btn-generate" onclick="submitHold()">Log hold</button></div>
</div>
</div>
<!-- VIEW SOP MODAL (comment 2) -->
<div class="modal-overlay" id="sop-modal">
<div class="modal" style="max-width:640px">
<div class="modal-head"><div class="modal-title">Project SOP — Reference</div><button class="cmt-x" onclick="closeSopModal()" title="Close"></button></div>
<div class="modal-body" id="sop-modal-body"></div>
<div class="modal-foot"><button class="btn btn-primary" onclick="closeSopModal()">Close</button></div>
</div>
</div>
<!-- COMMENTS DRAWER -->
<div class="cmt-overlay" id="cmt-overlay" onclick="toggleComments()"></div>
<aside class="cmt-drawer" id="cmt-drawer" aria-hidden="true">
<div class="cmt-head"><div class="cmt-title">Review Comments</div><button class="cmt-x" onclick="toggleComments()" title="Close"></button></div>
<div class="cmt-namebar"><label>Your name</label><input type="text" id="cmt-author" placeholder="e.g. J. Park" oninput="cmtSaveAuthor(this.value)"></div>
<div class="cmt-compose"><div class="cmt-compose-label">Comment on <strong id="cmt-cur-step">this form</strong></div>
<textarea id="cmt-input" rows="3" placeholder="Add feedback…"></textarea>
<button class="btn btn-primary cmt-add" onclick="addComment()">Add comment</button></div>
<div class="cmt-list" id="cmt-list"></div>
<div class="cmt-foot"><div class="cmt-note">Comments are saved in your browser. Use <strong>Export</strong> to send feedback back; the owner can <strong>Import</strong> each file.</div>
<div class="cmt-foot-btns"><button class="btn btn-ghost" onclick="exportComments()">⤓ Export</button>
<button class="btn btn-ghost" onclick="document.getElementById('cmt-import').click()">⤒ Import</button>
<button class="btn btn-ghost cmt-clear" onclick="clearMyComments()">Clear mine</button>
<input type="file" id="cmt-import" accept="application/json" style="display:none" onchange="importComments(event)"></div></div>
</aside>
<!-- STICKY SAVE BAR (always-visible save + release status) -->
<div class="sticky-save" id="sticky-save" style="display:none">
<span class="sticky-status" id="sticky-status"></span>
<div class="sticky-actions">
<button class="btn btn-ghost" onclick="savePackage(false)">Save draft</button>
<button class="btn btn-generate" onclick="savePackage(true)">⚡ Save &amp; view</button>
</div>
</div>
<script src="feedback-config.js"></script>
<script src="project-data.js"></script>
<!-- The Help button in the app bar is this page's entry point, the same as the
suite page. help.js used to skip its floating button here because it saw an
iframe; that test is gone, so say so explicitly instead. -->
<script>window.WP_HELP_NO_FAB = true;</script>
<script src="help.js"></script>
<script src="wp-creation-app.js"></script>
<script src="wp-chrome.js"></script>
<script src="wp-sidenav.js"></script>
</body>
</html>