Files
Project-SDE-WP-Suite/html/wp-sections.js
n.siegfried 8663d81af3 C4/D11 follow-up - the integration review's seven confirmed findings
An adversarial review (four lenses, every finding independently verified by two
skeptics told to refute it) ran over 2a5f6b3 and 8cf8c0f. Seven findings
survived; all seven are fixed here.

Against the C4 fix:
- help.js: the nav hover was renamed onto its own surface token, keeping a
  no-op T9.9 had introduced (two different grays had been mapped to one name).
  Hover is now --cds-layer-hover, the token that exists for exactly this.
- wp-creation-app.js: the drawer's critical CSS pre-painted --cds-layer-accent
  while the stylesheet paints --wp-nav-bg; now both paint --wp-nav-bg.

Against D11:
- wp-sections.js: the Assets toggle note still described the pre-D11 card
  ('Asset tags and controls.dev links') with a rationale the picker inverts.
- runAssetSearch: the result cap counted contains-matches before the exact and
  prefix tiers finished, so 500 alphabetically-early substring hits could evict
  the exact match - and Enter then added the wrong asset, ID-locked. The cap
  now bounds each tier; the scan always sees the whole catalog.
- addCatalogAsset: the one mutation in the section with no announced outcome
  was the successful pick. It now toasts (role=status), matching every sibling
  path (C1).
- assets_db.py: failures are remembered for FAIL_CACHE_SECONDS (default 30s)
  and a stale catalog is served over an error, so a Micron outage costs one
  CONNECT_TIMEOUT per window instead of one per page load stacking up in the
  shared sync threadpool until login itself stalls.
- assets_db.py: MICRON_ASSETS_CACHE_SECONDS='5m' no longer crashes the boot -
  a malformed knob on an OPTIONAL feature degrades to its default, loudly.

assets_check grows four regressions for these (27 -> 31): per-tier cap against
600 decoys, the announced pick, boot with a malformed knob, and the stable
cached 503. Battery: assets_check 31/31, color_check 5/5, sections_check ALL
PASS.

Items: C4, D11.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 12:09:40 -07:00

158 lines
6.8 KiB
JavaScript
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.

/* Work package section toggles — CR-006 / T5.5.
---------------------------------------------------------------------------
The structural fix behind most of the removal requests in the plan. Rather
than deleting fields globally, each project turns on only the sections it
uses: it is what lets Micron drop Kitting and Assets while another project
keeps them, and it is why CR-002 and CR-016 are toggles rather than deletions.
THE ONE RULE: toggling a section OFF never deletes anything. It stops the
section rendering — in the creation form, in the detail view and in the PDF
export — and that is all. Whatever was captured stays on the package, and
toggling back on shows it again, intact. Everything in this file is about
what is DISPLAYED; nothing here writes to a package.
This list lives in its own file because three surfaces read it and they must
not drift: the SOP wizard renders the toggles, the creator applies them to
its form and its printed output, and a work package's detail view honours
them. A fourth copy is how "Assets is off" and "Assets is off, except in the
export" happen.
IDS ARE PERMANENT. They are written into every SOP that has ever been saved,
so renaming one silently turns that section back on for every existing
project. Change `label` freely; never change `id`.
*/
(function (window) {
'use strict';
var LIST = [
{ id: 'general', label: 'General Information',
note: 'Holds the WP number, subject and type. Turning this off leaves nothing to identify a package by — it is listed for completeness, not as a suggestion.' },
{ id: 'location', label: 'Location',
note: 'Where the work happens. CR-004 gives this its own structured fields; today it is the location field inside General Information.' },
{ id: 'scope', label: 'Scope of Work',
note: 'The ordered steps the crew performs, and the labour estimate.' },
{ id: 'assets', label: 'Assets',
note: 'Asset IDs picked read-only from the Micron DB (D11), with manual entry for anything not listed. Off for Micron EUV — the customers own database stays the source of truth; this section only references it (CR-016).' },
{ id: 'materials', label: 'Materials',
note: 'The bill of materials that feeds kitting.' },
{ id: 'kitting', label: 'Kitting',
note: 'Kitting status, warehouse owner and MIMO. Off for Micron EUV, which is not kitting today (CR-009).' },
{ id: 'drawings', label: 'Drawings and Attachments',
note: 'Drawing references and attachment links.' },
{ id: 'constraints', label: 'Constraints',
note: 'Release-readiness items. A package cannot be issued while one is open.' },
{ id: 'qaqc', label: 'QA/QC',
note: 'Quality requirements, photo standard and hold points.' },
{ id: 'closeout', label: 'Closeout',
note: 'Actual hours, installed quantity, redlines and lessons learned. Actual Hours stays here — its removal was proposed and rejected (CR-017).' },
];
var IDS = LIST.map(function (s) { return s.id; });
/* Individual fields that can be switched off inside a section — CR-002.
A second, narrower list rather than more sections, because a section is a
block of the document and these are two rows inside one. BL-000b asks
whether General Information wants per-field toggles generally; this is not
that. It is the two fields CR-002 names, expressed as toggles because
CLAUDE.md says removals are expressed through toggles and the data is
retained — the columns and the model stay exactly as they are.
Same rule as sections: an id is permanent, absent means ON. */
var FIELDS = [
{ id: 'costCode', section: 'general', label: 'Acumatica cost code',
note: 'Effectively constant on a job, so it is noise on a field work package (CR-002). The value stays on every package that has one.' },
{ id: 'acumaticaTask', section: 'general', label: 'Acumatica task',
note: 'A PM concern rather than a field one (CR-002). The cost visibility the team actually wants is by building and floor — CR-004 and CR-018.' },
];
var FIELD_IDS = FIELDS.map(function (f) { return f.id; });
function fieldDefaults() {
var out = {};
FIELD_IDS.forEach(function (id) { out[id] = true; });
return out;
}
function normalizeFields(stored) {
var out = fieldDefaults();
if (stored && typeof stored === 'object') {
FIELD_IDS.forEach(function (id) {
if (Object.prototype.hasOwnProperty.call(stored, id)) out[id] = stored[id] !== false;
});
}
return out;
}
function fieldsFor(sectionId) {
return FIELDS.filter(function (f) { return f.section === sectionId; });
}
function defaults() {
// A new SOP has everything on. A project opts OUT of what it does not use;
// it does not have to discover and opt in to what it does.
var out = {};
IDS.forEach(function (id) { out[id] = true; });
return out;
}
/* Fill in anything a stored SOP does not mention.
This is what makes adding an eleventh section safe: every SOP saved before
it existed says nothing about it, and "says nothing" has to mean ON. The
alternative — absent meaning off — would switch a brand-new section off for
every project in the estate the moment it shipped. */
function normalize(stored) {
var out = defaults();
if (stored && typeof stored === 'object') {
IDS.forEach(function (id) {
if (Object.prototype.hasOwnProperty.call(stored, id)) out[id] = stored[id] !== false;
});
}
return out;
}
function isOn(stored, id) {
if (IDS.indexOf(id) < 0) return true; // not a section we govern
return normalize(stored)[id];
}
function offList(stored) {
var s = normalize(stored);
return LIST.filter(function (x) { return !s[x.id]; }).map(function (x) { return x.label; });
}
/* A field is on only if its own toggle is on AND the section holding it is.
Asked as one question so no caller has to remember to ask both — a field
showing inside a hidden section is not a state anyone wants to reason
about. */
function fieldOn(sections, fields, id) {
var f = FIELDS.filter(function (x) { return x.id === id; })[0];
if (!f) return true;
if (!isOn(sections, f.section)) return false;
return normalizeFields(fields)[id];
}
function offFieldList(fields) {
var s = normalizeFields(fields);
return FIELDS.filter(function (x) { return !s[x.id]; }).map(function (x) { return x.label; });
}
window.WPSections = {
LIST: LIST,
IDS: IDS,
defaults: defaults,
normalize: normalize,
isOn: isOn,
offList: offList,
FIELDS: FIELDS,
FIELD_IDS: FIELD_IDS,
fieldDefaults: fieldDefaults,
normalizeFields: normalizeFields,
fieldsFor: fieldsFor,
fieldOn: fieldOn,
offFieldList: offFieldList,
};
})(window);