/* FORGE · pump tenant — Act 4 (production floor + station consoles)
   8 screens registered at end of file. No imports / exports. Babel-standalone.
   Globals expected: React, getActiveTenant(), useActiveTenant(), navigate(),
   registerPumpRoute(), Icons, plus UI primitives (Panel, Pill, Btn, I, ...).
*/

const { useState: uSA4, useEffect: uEA4, useMemo: uMA4, useRef: uRA4 } = React;

/* ════════════════════════════════════════════════════════════════════
   PUMP-LINE INFRASTRUCTURE (week-1 fixture-bound)
   - localStorage persistence layer for PUMP_TENANT.travelers + work_orders
   - useTenantState hook (mutate + setState + persistence)
   - forgeApi stub: traveler.checkin / hold / advance + wo.issue
   - Derived station_progress helper

   Integration team swaps the forgeApi stubs in for real APIs in week 2.
   ──────────────────────────────────────────────────────────────────── */
(function _initPumpLine() {
  if (typeof window === "undefined") return;
  if (window.__pumpLineInit) return;
  window.__pumpLineInit = true;

  const LS_KEY = "forge.pump.tenantOverlay.v1";

  // Read persisted overlay
  function readOverlay() {
    try {
      const raw = localStorage.getItem(LS_KEY);
      return raw ? JSON.parse(raw) : {};
    } catch (e) {
      return {};
    }
  }
  function writeOverlay(o) {
    try { localStorage.setItem(LS_KEY, JSON.stringify(o)); } catch (e) { /* quota / private mode */ }
  }

  // Hydrate PUMP_TENANT.travelers + work_orders from overlay on first read.
  // Called lazily so script load order doesn't matter (PUMP_TENANT defined in fixtures-pump.jsx).
  function hydratePumpTenant() {
    if (typeof PUMP_TENANT === "undefined") return;
    if (PUMP_TENANT.__hydrated) return;
    PUMP_TENANT.__hydrated = true;
    const o = readOverlay();
    if (o.travelers && Array.isArray(o.travelers)) {
      // Merge by serial — overlay wins, but fall back to fixture for new serials.
      const overlayBySerial = {};
      o.travelers.forEach(function(t) { overlayBySerial[t.serial] = t; });
      // Replace fixture entries; keep fixture entries not in overlay.
      const newList = PUMP_TENANT.travelers.map(function(orig) {
        return overlayBySerial[orig.serial] || orig;
      });
      // Append any overlay travelers not in fixture (e.g. WO-issue generated).
      o.travelers.forEach(function(t) {
        if (!newList.find(function(x) { return x.serial === t.serial; })) newList.push(t);
      });
      PUMP_TENANT.travelers = newList;
    }
    if (o.work_orders && Array.isArray(o.work_orders)) {
      const overlayById = {};
      o.work_orders.forEach(function(w) { overlayById[w.id] = w; });
      const newList = PUMP_TENANT.work_orders.map(function(orig) {
        return overlayById[orig.id] || orig;
      });
      o.work_orders.forEach(function(w) {
        if (!newList.find(function(x) { return x.id === w.id; })) newList.push(w);
      });
      PUMP_TENANT.work_orders = newList;
    }
  }

  // Persist the mutable subset (travelers + work_orders only — everything else is fixture).
  function persistPumpTenant() {
    if (typeof PUMP_TENANT === "undefined") return;
    writeOverlay({
      travelers: PUMP_TENANT.travelers,
      work_orders: PUMP_TENANT.work_orders,
    });
  }

  function notifyTenantMutated() {
    try {
      window.dispatchEvent(new CustomEvent("forge:tenant-mutated"));
    } catch (e) { /* older browsers */ }
  }

  // useTenantState — returns [tenant, mutate].
  // mutate(fn) runs fn(PUMP_TENANT), persists, dispatches event so subscribers re-render.
  function useTenantState() {
    const tenant = useActiveTenant();
    if (tenant && tenant.tenant && tenant.tenant.id === "tritan") hydratePumpTenant();
    const [, bump] = React.useState(0);
    React.useEffect(function() {
      const onMutate = function() { bump(function(b) { return b + 1; }); };
      window.addEventListener("forge:tenant-mutated", onMutate);
      return function() { window.removeEventListener("forge:tenant-mutated", onMutate); };
    }, []);
    function mutate(fn) {
      if (typeof PUMP_TENANT !== "undefined") {
        fn(PUMP_TENANT);
        persistPumpTenant();
        notifyTenantMutated();
      }
    }
    return [tenant, mutate];
  }
  window.useTenantState = useTenantState;
  window.hydratePumpTenant = hydratePumpTenant;
  window.persistPumpTenant = persistPumpTenant;

  // Derived station_progress: returns { s1: 0..1, s2: 0..1, ... } for a given WO.
  function deriveStationProgress(woId) {
    if (typeof PUMP_TENANT === "undefined") return {};
    const wo = PUMP_TENANT.work_orders.find(function(w) { return w.id === woId; });
    if (!wo) return {};
    const stations = (PUMP_TENANT.routing || []).map(function(r) { return r.id; });
    const travs = PUMP_TENANT.travelers.filter(function(t) { return t.wo === woId; });
    const total = wo.qty || travs.length || 1;
    const out = {};
    stations.forEach(function(sid) {
      const passed = travs.filter(function(t) {
        return (t.completed_stations || []).indexOf(sid) >= 0;
      }).length;
      out[sid] = total > 0 ? passed / total : 0;
    });
    return out;
  }
  window.deriveStationProgress = deriveStationProgress;

  // forgeApi stub — week-1 fixture-bound implementation.
  window.forgeApi = window.forgeApi || {};
  window.forgeApi.traveler = window.forgeApi.traveler || {
    checkin: function(serial, body) {
      hydratePumpTenant();
      const t = PUMP_TENANT.travelers.find(function(x) { return x.serial === serial; });
      if (!t) return Promise.reject(new Error("serial not found: " + serial));
      t.checkins = t.checkins || [];
      t.checkins.push(Object.assign({}, body, { ts: Date.now() }));
      t.current_station = body.station_id;
      if (body.dimensions) t.dimensions = Object.assign({}, t.dimensions || {}, body.dimensions);
      if (Array.isArray(body.photos)) t.photos = (t.photos || []).concat(body.photos);
      if (body.result === "pass") {
        t.completed_stations = t.completed_stations || [];
        if (t.completed_stations.indexOf(body.station_id) < 0) {
          t.completed_stations.push(body.station_id);
        }
        t.status = "ok";
        // auto-advance to next station id (lexicographic on s<n>)
        const routing = (PUMP_TENANT.routing || []).slice().sort(function(a, b) { return a.seq - b.seq; });
        const idx = routing.findIndex(function(r) { return r.id === body.station_id; });
        if (idx >= 0 && idx + 1 < routing.length) {
          t.current_station = routing[idx + 1].id;
        } else if (idx === routing.length - 1) {
          t.status = "done";
        }
      } else if (body.result === "hold") {
        t.status = "hold";
        t.hold_reason = body.measurement_notes || body.hold_reason || "Hold pending dispo";
      }
      persistPumpTenant();
      notifyTenantMutated();
      return Promise.resolve(t);
    },
    hold: function(serial, reason, dispo) {
      hydratePumpTenant();
      const t = PUMP_TENANT.travelers.find(function(x) { return x.serial === serial; });
      if (!t) return Promise.reject(new Error("serial not found: " + serial));
      t.status = "hold";
      t.hold_reason = reason;
      t.dispo_path = dispo;
      t.checkins = t.checkins || [];
      t.checkins.push({ station_id: t.current_station, type: "hold", reason: reason, dispo_path: dispo, ts: Date.now() });
      persistPumpTenant();
      notifyTenantMutated();
      return Promise.resolve(t);
    },
    advance: function(serial) {
      hydratePumpTenant();
      const t = PUMP_TENANT.travelers.find(function(x) { return x.serial === serial; });
      if (!t) return Promise.reject(new Error("serial not found: " + serial));
      const routing = (PUMP_TENANT.routing || []).slice().sort(function(a, b) { return a.seq - b.seq; });
      const idx = routing.findIndex(function(r) { return r.id === t.current_station; });
      // Mark current station complete, advance to next.
      t.completed_stations = t.completed_stations || [];
      if (t.completed_stations.indexOf(t.current_station) < 0) {
        t.completed_stations.push(t.current_station);
      }
      if (idx >= 0 && idx + 1 < routing.length) {
        t.current_station = routing[idx + 1].id;
        t.status = "ok";
        t.hold_reason = null;
      } else if (idx === routing.length - 1) {
        t.status = "done";
      }
      persistPumpTenant();
      notifyTenantMutated();
      return Promise.resolve(t);
    },
  };

  window.forgeApi.wo = window.forgeApi.wo || {
    issue: function(woSpec) {
      hydratePumpTenant();
      // woSpec: { sku, qty, due_date, serial_prefix }
      const newWoId = "WO-" + (44222 + (PUMP_TENANT.work_orders.length - 4) + 1);
      const wo = {
        id: newWoId,
        sku: woSpec.sku,
        customer: woSpec.customer || "Internal · pilot lot",
        qty: woSpec.qty,
        status: "released",
        scheduled_start: woSpec.scheduled_start || new Date().toISOString().slice(0, 10),
        scheduled_end: woSpec.due_date,
        serial_pool_prefix: woSpec.serial_prefix,
      };
      PUMP_TENANT.work_orders.push(wo);
      // generate serials
      const firstStation = ((PUMP_TENANT.routing || []).slice().sort(function(a, b) { return a.seq - b.seq; })[0] || { id: "s1" }).id;
      for (let i = 1; i <= woSpec.qty; i++) {
        const serial = woSpec.serial_prefix + "-" + String(i).padStart(4, "0");
        if (!PUMP_TENANT.travelers.find(function(t) { return t.serial === serial; })) {
          PUMP_TENANT.travelers.push({
            serial: serial,
            wo: newWoId,
            current_station: firstStation,
            completed_stations: [],
            dimensions: {},
            photos: [],
            status: "ok",
            checkins: [],
          });
        }
      }
      persistPumpTenant();
      notifyTenantMutated();
      return Promise.resolve(wo);
    },
  };

  // Current-user helper (for op signatures). Per-tenant default — picks the first
  // station-owning operator from the active tenant's people roster. Avoids
  // hardcoding Tritan's Rohit B. for non-Tritan tenants. Wave 6.5A.3.
  function _resolveDefaultOperator() {
    if (window.__forgeCurrentUser) return;
    var tenant = (typeof window.getActiveTenant === "function") ? window.getActiveTenant() : null;
    var people = (tenant && Array.isArray(tenant.people)) ? tenant.people : [];
    if (people.length === 0) return; // leave undefined; will retry on tenant-change
    var op = people.find(function(p) { return p.station && /s[1-9]|machine|assy|line|final/i.test(p.station + " " + p.role); }) || people[0];
    window.__forgeCurrentUser = { id: op.id, name: op.name, role: op.role, station: op.station || null };
  }
  _resolveDefaultOperator();
  if (typeof window.addEventListener === "function") {
    window.addEventListener("forge:tenant-change", function(ev) {
      // Skip user-switch events (SwitchUserPill / UserSwitcher dispatched).
      // Without this guard we clobber __forgeCurrentUser back to the default
      // operator on every actor swap, breaking the hero journey.
      var reason = ev && ev.detail && ev.detail.reason;
      if (reason === "user-switch" || reason === "actor-switch") return;
      // Clear so the next resolver picks a person from the new tenant
      window.__forgeCurrentUser = null;
      _resolveDefaultOperator();
    });
  }
  window.getCurrentUser = function() { return window.__forgeCurrentUser; };
  window.setCurrentUser = function(person) {
    window.__forgeCurrentUser = person;
    notifyTenantMutated();
  };

  // Reset overlay (for /reset-demo button)
  window.resetPumpLineOverlay = function() {
    try { localStorage.removeItem(LS_KEY); } catch (e) { /* ignore */ }
    if (typeof PUMP_TENANT !== "undefined") PUMP_TENANT.__hydrated = false;
    notifyTenantMutated();
  };
})();

/* ────────────────────────────────────────────────────────────────────
   Tiny helpers shared by Act 4 screens
   ──────────────────────────────────────────────────────────────────── */

function useA4Tenant() {
  // hydrate-on-read keeps fixture-bound state aligned with persisted overlay
  if (typeof window !== "undefined" && typeof window.hydratePumpTenant === "function") {
    window.hydratePumpTenant();
  }
  return useActiveTenant();
}

function _a4_initials(n) {
  if (!n) return "—";
  const parts = String(n).replace(/[^A-Za-z. ]/g, "").split(/\s+/).filter(Boolean);
  if (!parts.length) return "—";
  const a = parts[0][0] || "";
  const b = parts[1] ? parts[1][0] : (parts[0][1] || "");
  return (a + b).toUpperCase();
}

/* Extracted styles (react-doctor: no-inline-exhaustive-style). */
function _a4AvatarStyle(size, tone) {
  return {
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    width: size, height: size,
    borderRadius: "50%",
    background: "color-mix(in oklch, " + tone + " 16%, var(--bg-2))",
    color: tone,
    border: "1px solid color-mix(in oklch, " + tone + " 30%, var(--line))",
    fontFamily: "var(--font-mono)",
    fontSize: 12,
    letterSpacing: "0.04em",
    fontWeight: 600,
  };
}
function _a4StatStyle(pass) {
  return {
    display: "inline-flex",
    alignItems: "center",
    gap: 4,
    fontFamily: "var(--font-mono)",
    fontSize: 12,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    color: pass ? "var(--ok)" : "var(--err)",
  };
}
const _A4_INPUT_STYLE = {
  width: "100%", padding: "6px 8px",
  border: "1px solid var(--line)", background: "var(--bg-0)",
  color: "var(--ink)", borderRadius: 3, fontSize: 12,
};
const _A4_INPUT_MONO_STYLE = {
  width: "100%", padding: "6px 8px",
  border: "1px solid var(--line)", background: "var(--bg-0)",
  color: "var(--ink)", borderRadius: 3, fontSize: 12,
  fontFamily: "var(--font-mono)",
};
const _A4_REV_INPUT_STYLE = {
  flex: 1, padding: "5px 8px",
  border: "1px solid var(--line)", background: "var(--bg-0)",
  color: "var(--ink)", borderRadius: 3, fontSize: 12,
  fontFamily: "var(--font-mono)",
};
const _A4_MODAL_BACKDROP_STYLE = {
  position: "fixed", inset: 0, background: "color-mix(in oklch, var(--ink) 38%, transparent)",
  display: "flex", alignItems: "center", justifyContent: "center", zIndex: 50,
};
const _A4_MODAL_PANEL_STYLE = {
  background: "var(--bg-0)",
  border: "1px solid var(--line)",
  borderRadius: 4,
  boxShadow: "0 18px 48px rgba(0,0,0,0.18)",
  width: 460,
  padding: 16,
};

function _a4_avatar(name, size = 22, tone = "var(--accent)") {
  return (
    <span style={_a4AvatarStyle(size, tone)}>
      {_a4_initials(name)}
    </span>
  );
}

function _a4_dot(tone) {
  const map = {
    ok: "var(--ok)",
    warn: "var(--warn)",
    err: "var(--err)",
    info: "var(--info)",
    mute: "var(--ink-4)",
  };
  return (
    <span style={{
      display: "inline-block",
      width: 7, height: 7, borderRadius: "50%",
      background: map[tone] || tone || "var(--ink-4)",
      boxShadow: "0 0 0 2px color-mix(in oklch, " + (map[tone] || tone || "var(--ink-4)") + " 18%, transparent)",
      flexShrink: 0,
    }} />
  );
}

function _a4_kvRow(k, v, mono) {
  return (
    <div style={{
      display: "flex",
      justifyContent: "space-between",
      alignItems: "baseline",
      padding: "4px 10px",
      borderBottom: "1px dashed var(--line-soft)",
      fontSize: 12,
    }}>
      <span className="label" style={{ color: "var(--ink-3)" }}>{k}</span>
      <span className={mono ? "mono tnum" : ""} style={{ color: "var(--ink-2)" }}>{v}</span>
    </div>
  );
}

function _a4_severityTone(s) {
  if (s === "critical") return "err";
  if (s === "high")     return "warn";
  if (s === "med")      return "info";
  return "mute";
}

function _a4_severityRank(s) {
  if (s === "critical") return 4;
  if (s === "high")     return 3;
  if (s === "med")      return 2;
  if (s === "low")      return 1;
  return 0;
}

function _a4_severityColor(s) {
  const t = _a4_severityTone(s);
  if (t === "err")  return "var(--err)";
  if (t === "warn") return "var(--warn)";
  if (t === "info") return "var(--info)";
  return "var(--ink-3)";
}

/* Toolbar — small grey bar of buttons / chips */
const _A4_REPORT_BAR_STYLE = {
  padding: 12,
  border: "1px solid var(--line)", borderRadius: 4,
  background: "var(--bg-0)",
  display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12,
};
function _a4DimCaptureStyle(p) {
  return {
    width: "100%", padding: "6px 8px",
    fontFamily: "var(--font-mono)",
    fontSize: 14, fontWeight: 600,
    background: "var(--bg-1)",
    border: "1px solid var(--line)",
    borderRadius: 3,
    color: p ? "var(--ink)" : "var(--err)",
  };
}
const _A4_SHORT_NOTE_STYLE = {
  gridColumn: "1 / -1", marginTop: 4, padding: "4px 8px",
  background: "var(--bg-2)", border: "1px dashed var(--err)",
  borderRadius: 3, fontSize: 12, color: "var(--ink-2)",
  display: "flex", justifyContent: "space-between", alignItems: "center",
};
const _A4_SCAN_INPUT_WRAP = {
  display: "flex", alignItems: "center", flex: 1, padding: "6px 10px",
  border: "1px solid var(--line)", background: "var(--bg-0)",
  borderRadius: 3, gap: 8,
};
const _A4_SCAN_INPUT_STYLE = {
  flex: 1, background: "transparent", border: 0, outline: "0 solid transparent",
  color: "var(--ink)", font: "inherit", fontFamily: "var(--font-mono)", fontSize: 12,
};
function _a4LotRowStyle(isHighlighted, state) {
  return {
    display: "grid", gridTemplateColumns: "1.1fr 1fr 1fr 70px 110px 110px 200px",
    padding: "8px 12px",
    borderBottom: "1px solid var(--line-soft)",
    alignItems: "center",
    fontSize: 12,
    background: isHighlighted ? "color-mix(in oklch, var(--accent) 10%, transparent)" :
                state === "quarantined" ? "color-mix(in oklch, var(--err) 8%, transparent)" :
                state === "rejected"    ? "color-mix(in oklch, var(--err) 5%, transparent)" :
                state === "passed"      ? "transparent" :
                state === "held"        ? "color-mix(in oklch, var(--warn) 6%, transparent)" :
                "transparent",
  };
}
const _A4_LOT_HEADER_STYLE = {
  display: "grid", gridTemplateColumns: "1.1fr 1fr 1fr 70px 110px 110px 200px",
  padding: "5px 12px",
  borderBottom: "1px solid var(--line)",
  background: "var(--bg-2)", position: "sticky", top: 0, zIndex: 2,
};
const _A4_BOM_HEADER_STYLE = {
  display: "grid",
  gridTemplateColumns: "60px 1fr 60px 60px 80px",
  padding: "5px 12px",
  borderBottom: "1px solid var(--line-soft)", background: "var(--bg-2)",
  position: "sticky", top: 0, zIndex: 2,
};
function _a4StationDotStyle(i) {
  return {
    width: 18, height: 18, borderRadius: "50%",
    background: i < 5 ? "var(--ok)" : i === 5 ? "var(--accent)" : "var(--bg-3)",
    border: "1px solid " + (i < 5 ? "var(--ok)" : i === 5 ? "var(--accent)" : "var(--line)"),
    boxShadow: i === 5 ? "0 0 0 4px color-mix(in oklch, var(--accent) 18%, transparent)" : "none",
  };
}
function _a4TestNumStyle(pass) {
  return {
    width: 22, height: 22, borderRadius: "50%",
    background: pass ? "var(--ok)" : "var(--err)", color: "white",
    display: "inline-flex", alignItems: "center", justifyContent: "center",
    fontSize: 12, fontFamily: "var(--font-mono)", fontWeight: 600,
  };
}
const _A4_PASS_BANNER_STYLE = {
  marginTop: 8, padding: 10,
  background: "color-mix(in oklch, var(--ok) 8%, transparent)",
  border: "1px solid var(--ok)", borderRadius: 4,
  fontSize: 12, color: "var(--ink)",
  display: "flex", justifyContent: "space-between", alignItems: "center",
};
const _A4_PHOTO_TILE_STYLE = {
  aspectRatio: "1.4 / 1",
  background: "var(--bg-2)",
  border: "1px dashed var(--line)",
  borderRadius: 3,
  display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center",
  color: "var(--ink-3)", fontSize: 12, textAlign: "center", gap: 6, padding: 6,
};
const _A4_SIGNATURE_BAR_STYLE = {
  marginTop: 14, padding: 12,
  border: "1px solid var(--line)", borderRadius: 4,
  background: "var(--bg-1)",
  display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10,
};
const _A4_TEXTAREA_STYLE = {
  width: "100%", height: 86, padding: 8,
  background: "var(--bg-0)", border: "1px solid var(--line)",
  borderRadius: 3, color: "var(--ink-2)",
  fontFamily: "var(--font-mono)", fontSize: 12, resize: "none",
};
function _a4DimRowStyle(p) {
  return {
    display: "grid",
    gridTemplateColumns: "1.5fr 1.2fr 0.8fr 1.2fr 100px",
    gap: 12, padding: 12, marginBottom: 8,
    border: "1px solid " + (p ? "var(--line)" : "var(--err)"),
    borderRadius: 4,
    background: p ? "var(--bg-0)" : "color-mix(in oklch, var(--err) 6%, var(--bg-0))",
    alignItems: "center",
  };
}
function _a4ReqRowStyle(isShort) {
  return {
    display: "grid", gridTemplateColumns: "60px 1fr 60px 60px",
    padding: "7px 12px", borderBottom: "1px solid var(--line-soft)",
    fontSize: 12, alignItems: "center",
    background: isShort ? "color-mix(in oklch, var(--err) 6%, transparent)" : "transparent",
  };
}
const _A4_SCAN_RESULT_STYLE = {
  marginTop: 10, padding: 10,
  border: "1px solid var(--accent)",
  background: "color-mix(in oklch, var(--accent) 8%, var(--bg-0))",
  borderRadius: 3, fontSize: 12, color: "var(--ink-2)",
  display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 8,
};
function _a4ControlPlanRowStyle(colTpl, sel) {
  return {
    display: "grid",
    gridTemplateColumns: colTpl,
    borderBottom: "1px solid var(--line-soft)",
    padding: "8px 0",
    cursor: "pointer",
    background: sel ? "color-mix(in oklch, var(--accent) 6%, transparent)" : "transparent",
    alignItems: "start",
    fontSize: 12,
  };
}
const _A4_TOAST_STYLE = {
  position: "fixed", bottom: 24, right: 24,
  background: "var(--ink)", color: "var(--bg-0)",
  padding: "8px 14px", borderRadius: 4, fontSize: 12,
  boxShadow: "0 6px 20px rgba(0,0,0,0.18)",
  zIndex: 60,
};
const _A4_FIND_BAR_STYLE = {
  display: "flex", alignItems: "center", gap: 6, padding: "4px 8px",
  border: "1px solid var(--line)", background: "var(--bg-0)",
  borderRadius: 3, minWidth: 280,
};
function _a4ControlPlanHeader(colTpl) {
  return {
    display: "grid",
    gridTemplateColumns: colTpl,
    background: "var(--bg-1)", borderBottom: "1px solid var(--line)",
    height: 28, alignItems: "center",
    fontFamily: "var(--font-mono)",
    fontSize: 12,
    letterSpacing: "0.06em",
    textTransform: "uppercase",
    color: "var(--ink-3)",
    position: "sticky", top: 0, zIndex: 2,
  };
}
function _a4DraftInputStyle(mono) {
  return {
    width: "100%", padding: "5px 7px",
    border: "1px solid var(--line)", background: "var(--bg-0)",
    color: "var(--ink)", borderRadius: 3, fontSize: 12,
    fontFamily: mono ? "var(--font-mono)" : "inherit",
  };
}
function _a4GateGridHeader(colTpl) {
  return {
    display: "grid",
    gridTemplateColumns: colTpl,
    position: "sticky", top: 0, zIndex: 3,
    background: "var(--bg-1)", borderBottom: "1px solid var(--line)",
    height: 28, alignItems: "center",
  };
}
const _A4_STATION_GROUP_HEADER = {
  display: "flex", alignItems: "center", gap: 8,
  padding: "5px 12px", background: "var(--bg-2)",
  borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line-soft)",
  fontFamily: "var(--font-mono)", fontSize: 12,
  position: "sticky", top: 28, zIndex: 2,
};
function _a4GateRowStyle(colTpl, dirty) {
  return {
    display: "grid",
    gridTemplateColumns: colTpl,
    alignItems: "center",
    borderBottom: "1px solid var(--line-soft)",
    fontSize: 12,
    background: dirty ? "color-mix(in oklch, var(--warn) 7%, transparent)" : "transparent",
    height: 30,
  };
}
function _a4ToolbarStyle(height) {
  return {
    display: "flex",
    alignItems: "center",
    justifyContent: "space-between",
    gap: 8,
    padding: "6px 10px",
    background: "var(--bg-1)",
    borderBottom: "1px solid var(--line)",
    height: height || 38,
    flexShrink: 0,
  };
}
function _A4Toolbar({ children, right, height }) {
  return (
    <div style={_a4ToolbarStyle(height)}>
      <div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>{children}</div>
      <div style={{ display: "flex", alignItems: "center", gap: 6 }}>{right}</div>
    </div>
  );
}

/* Confirm modal (paper card overlay) — Esc dismisses, backdrop-click dismisses */
function _A4Modal({ title, body, onCancel, onConfirm, confirmLabel }) {
  uEA4(() => {
    const onKey = (e) => { if (e.key === "Escape" && typeof onCancel === "function") onCancel(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onCancel]);
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={() => { if (typeof onCancel === "function") onCancel(); }}
      onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (typeof onCancel === "function") onCancel(); } }}
      style={_A4_MODAL_BACKDROP_STYLE}
    >
      <div
        role="dialog"
        aria-label={title}
        tabIndex={-1}
        onClick={e => e.stopPropagation()}
        onKeyDown={e => e.stopPropagation()}
        style={_A4_MODAL_PANEL_STYLE}
      >
        <div className="label" style={{ color: "var(--ink-3)", marginBottom: 6 }}>{title}</div>
        <div style={{ fontSize: 13, color: "var(--ink)", lineHeight: 1.45, marginBottom: 14 }}>{body}</div>
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 6 }}>
          <Btn onClick={onCancel}>Cancel</Btn>
          <Btn variant="primary" onClick={onConfirm}>{confirmLabel || "Confirm"}</Btn>
        </div>
      </div>
    </div>
  );
}

/* Mini bar gauge for ratios 0..1 */
function _A4Gauge({ v, label, target, ok }) {
  const pct = Math.max(0, Math.min(1, v));
  const tone = ok ? "var(--ok)" : "var(--warn)";
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4, minWidth: 0 }}>
      {label && <div className="label" style={{ color: "var(--ink-3)" }}>{label}</div>}
      <div style={{ height: 8, background: "var(--bg-3)", borderRadius: 4, overflow: "hidden", border: "1px solid var(--line-soft)" }}>
        <div style={{ width: (pct * 100).toFixed(1) + "%", height: "100%", background: tone }} />
      </div>
      <div className="mono tnum" style={{ fontSize: 12, color: "var(--ink-3)", display: "flex", justifyContent: "space-between" }}>
        <span>{(pct * 100).toFixed(1)}%</span>
        {target ? <span style={{ color: "var(--ink-3)" }}>target {target}</span> : null}
      </div>
    </div>
  );
}

/* Inline pass/fail tag with mono caption */
function _A4Stat({ pass, label }) {
  return (
    <span style={_a4StatStyle(pass)}>
      {_a4_dot(pass ? "ok" : "err")}
      <span>{label || (pass ? "pass" : "fail")}</span>
    </span>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S21 — Routing canvas  /build/routing
   ──────────────────────────────────────────────────────────────────── */

function ScreenRoutingCanvas() {
  const T = useA4Tenant() || {};
  const baseRouting = (T.routing && T.routing.length) ? T.routing : [
    { id: "s1", seq: 1, name: "Receiving",                  owner: "Aryan M.", cycle_min: 8,  station_type: "inbound"     },
    { id: "s2", seq: 2, name: "Incoming Inspect",           owner: "Aryan M.", cycle_min: 12, station_type: "qa-inbound"  },
    { id: "s3", seq: 3, name: "Storage",                    owner: "Priya I.", cycle_min: 5,  station_type: "warehouse"   },
    { id: "s4", seq: 4, name: "Machine Shop",               owner: "Rohit B.", cycle_min: 22, station_type: "machining"   },
    { id: "s5", seq: 5, name: "Motor Assy + Winding",       owner: "Neha G.",  cycle_min: 38, station_type: "assembly"    },
    { id: "s6", seq: 6, name: "No-load + Pressure Test",    owner: "Neha G.",  cycle_min: 18, station_type: "test"        },
    { id: "s7", seq: 7, name: "Pump Assy + Final Test",     owner: "Rohit B.", cycle_min: 25, station_type: "final"       },
  ];

  const [stations, setStations] = uSA4(baseRouting.map((s, i) => ({
    ...s,
    health: i === 4 ? "warn" : i === 1 ? "warn" : "ok",
  })));
  const [selectedId, setSelectedId] = uSA4("s5");
  const [edit, setEdit] = uSA4(() => baseRouting.find(s => s.id === "s5") || baseRouting[0]);
  const [showRelease, setShowRelease] = uSA4(false);
  const [revisions, setRevisions] = uSA4([
    { id: "rev-A", label: "rev A · 2025-12-04", note: "first lift from M-26102 routing",        author: "Devansh A.", current: false },
    { id: "rev-B", label: "rev B · 2026-03-18", note: "added Surge test gate (QG-12)",          author: "Devansh A.", current: true  },
  ]);
  const [revName, setRevName] = uSA4("rev C");
  // W14D — toast for routing edits + release outcomes (server persistence).
  const [toast, setToast] = uSA4(null);
  function _flashToast(t) {
    setToast(t);
    setTimeout(() => setToast(null), 2400);
  }

  uEA4(() => {
    const s = stations.find(x => x.id === selectedId);
    if (s) setEdit({ ...s });
  }, [selectedId]);

  // Close release modal if tenant switches mid-flow.
  uEA4(() => {
    const onTenant = () => setShowRelease(false);
    window.addEventListener("forge:tenant-change", onTenant);
    return () => window.removeEventListener("forge:tenant-change", onTenant);
  }, []);

  function applyEdit() {
    // W14D — optimistic local update + persist the new routing snapshot to
    // tenant_kv via PATCH /resource/routing_revisions so a refresh keeps the
    // saved edits visible. Keeps the legacy in-memory `setStations` so the
    // SVG canvas updates instantly even if the network is slow.
    const newStations = stations.map(s => s.id === edit.id ? { ...s, ...edit } : s);
    setStations(newStations);
    const cur = (window.getCurrentUser && window.getCurrentUser()) || { name: "Devansh A." };
    const sku = "M-27418";
    const nextRev = revName;
    if (window.forgeApi && window.forgeApi.mutate) {
      window.forgeApi.mutate("routing_revisions", {
        op: "add",
        path: "/-",
        value: { sku, stations: newStations, rev: nextRev, actor: cur.name, ts: Date.now() },
      }).then(() => _flashToast({ kind: "ok", msg: "Routing rev " + nextRev + " saved" }))
        .catch(e => _flashToast({ kind: "err", msg: (e && e.message) || "save failed" }));
    }
  }

  function releaseRevision() {
    // W14D — optimistic local update + persist a release marker entry to
    // tenant_kv. The released revision is appended with `released: true` so a
    // refresh keeps the rev-list aligned with what the demo viewer just saw.
    setRevisions(prev => [
      ...prev.map(r => ({ ...r, current: false })),
      { id: revName.toLowerCase().replace(/\s+/g, "-"), label: revName + " · 2026-04-29", note: "release via routing canvas", author: "Devansh A.", current: true },
    ]);
    setShowRelease(false);
    const cur = (window.getCurrentUser && window.getCurrentUser()) || { name: "Devansh A." };
    const sku = "M-27418";
    const nextRev = revName;
    if (window.forgeApi && window.forgeApi.mutate) {
      window.forgeApi.mutate("routing_revisions", {
        op: "add",
        path: "/-",
        value: { sku, stations, rev: nextRev, released: true, actor: cur.name, ts: Date.now() },
      }).then(() => _flashToast({ kind: "ok", msg: "Routing rev " + nextRev + " released" }))
        .catch(e => _flashToast({ kind: "err", msg: (e && e.message) || "release failed" }));
    }
  }

  const totalCycle = stations.reduce((a, s) => a + (Number(s.cycle_min) || 0), 0);

  // SVG canvas constants
  const boxW = 138, boxH = 96, gap = 28;
  const canvasW = stations.length * boxW + (stations.length - 1) * gap + 40;
  const canvasH = 220;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      {/* Toolbar */}
      <_A4Toolbar
        right={
          <>
            <Btn icon="Diff" onClick={() => window.forgeToast && window.forgeToast("Compare to predecessor queued — wired in v2")}>Compare to predecessor</Btn>
            <Btn icon="File" onClick={() => window.forgeToast && window.forgeToast("Save as template queued — wired in v2")}>Save as template</Btn>
            <Btn variant="primary" icon="Git" onClick={() => setShowRelease(true)}>Release routing {revName}</Btn>
          </>
        }
      >
        <Btn icon="Plus" onClick={() => window.forgeToast && window.forgeToast("Add station queued — wired in v2")}>Add station</Btn>
        <Btn icon="Flow" onClick={() => window.forgeToast && window.forgeToast("Insert split/merge queued — wired in v2")}>Insert split / merge</Btn>
        <Btn icon="Sparkle" onClick={() => window.forgeToast && window.forgeToast("Auto-balance cycle queued — wired in v2")}>Auto-balance cycle</Btn>
        <span style={{ color: "var(--ink-3)", fontSize: 12, marginLeft: 8 }}>
          ROUT-M27418 · <b style={{ color: "var(--ink-2)" }}>{revisions.find(r => r.current)?.label || "rev B"}</b>
          &nbsp;·&nbsp;cycle <span className="mono tnum">{totalCycle} min</span>
          &nbsp;·&nbsp;<span style={{ color: "var(--ink-3)" }}>7 stations</span>
        </span>
      </_A4Toolbar>

      {/* Body */}
      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1fr 320px", minHeight: 0 }}>

        {/* Canvas + edit panel */}
        <div style={{ display: "flex", flexDirection: "column", minHeight: 0, overflow: "auto" }}>

          <div style={{ padding: 18, background: "var(--bg-1)", borderBottom: "1px solid var(--line)" }}>
            <svg
              viewBox={"0 0 " + canvasW + " " + canvasH}
              width="100%"
              height={canvasH}
              style={{ display: "block" }}
            >
              {/* Faint grid */}
              <defs>
                <pattern id="rcg" width="20" height="20" patternUnits="userSpaceOnUse">
                  <path d="M 20 0 L 0 0 0 20" fill="none" stroke="var(--line-soft)" strokeWidth="0.5" />
                </pattern>
                <marker id="rc-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
                  <path d="M0,0 L10,5 L0,10 Z" fill="var(--ink-3)" />
                </marker>
              </defs>
              <rect x="0" y="0" width={canvasW} height={canvasH} fill="url(#rcg)" />

              {/* Connectors */}
              {stations.map((s, i) => {
                if (i === stations.length - 1) return null;
                const x1 = 20 + (i + 1) * boxW + i * gap;
                const x2 = x1 + gap;
                const y = 60 + boxH / 2;
                return (
                  <line key={"conn-" + i} x1={x1} y1={y} x2={x2 - 4} y2={y}
                        stroke="var(--ink-3)" strokeWidth="1.2" markerEnd="url(#rc-arrow)" />
                );
              })}

              {/* Boxes */}
              {stations.map((s, i) => {
                const x = 20 + i * (boxW + gap);
                const y = 60;
                const sel = s.id === selectedId;
                const tone = s.health === "warn" ? "var(--warn)" : s.health === "err" ? "var(--err)" : "var(--ok)";
                return (
                  <g key={s.id} style={{ cursor: "pointer" }} onClick={() => setSelectedId(s.id)}>
                    <rect
                      x={x} y={y}
                      width={boxW} height={boxH}
                      rx="4"
                      fill="var(--bg-0)"
                      stroke={sel ? "var(--accent)" : "var(--line)"}
                      strokeWidth={sel ? "1.6" : "1"}
                    />
                    <text x={x + 8} y={y + 16} fontSize="10" fontFamily="var(--font-mono)" fill="var(--ink-3)" letterSpacing="0.06em">
                      {("S" + s.seq).toUpperCase()} · {s.station_type}
                    </text>
                    <text x={x + 8} y={y + 36} fontSize="11.5" fontWeight="600" fill="var(--ink)">
                      {s.name.length > 18 ? s.name.slice(0, 17) + "…" : s.name}
                    </text>
                    {s.name.length > 18 && (
                      <text x={x + 8} y={y + 50} fontSize="11" fill="var(--ink-2)">
                        {s.name.slice(17, 30)}
                      </text>
                    )}
                    <circle cx={x + boxW - 14} cy={y + 14} r="4" fill={tone} />
                    {/* avatar circle */}
                    <circle cx={x + 16} cy={y + boxH - 18} r="9" fill="color-mix(in oklch, var(--accent) 18%, var(--bg-2))" stroke="color-mix(in oklch, var(--accent) 32%, var(--line))" />
                    <text x={x + 16} y={y + boxH - 14} fontSize="8.5" textAnchor="middle" fontFamily="var(--font-mono)" fill="var(--accent)" fontWeight="600">
                      {_a4_initials(s.owner)}
                    </text>
                    <text x={x + 30} y={y + boxH - 22} fontSize="10" fill="var(--ink-3)">{s.owner}</text>
                    <text x={x + 30} y={y + boxH - 10} fontSize="10" fontFamily="var(--font-mono)" fill="var(--ink-2)">
                      {s.cycle_min} min
                    </text>
                  </g>
                );
              })}

              {/* legend */}
              <text x="20" y="200" fontSize="10" fill="var(--ink-3)" fontFamily="var(--font-mono)" letterSpacing="0.06em">
                CYCLE {totalCycle} MIN · CRITICAL PATH s4 → s5 · 1.6× takt
              </text>
            </svg>
          </div>

          {/* Edit panel for selected station */}
          <div style={{ padding: 14, flex: 1, overflow: "auto" }}>
            <div className="label" style={{ marginBottom: 8 }}>
              EDIT STATION · <span style={{ color: "var(--ink-2)" }}>{edit.id}</span>
            </div>
            <div style={{
              display: "grid",
              gridTemplateColumns: "repeat(2, 1fr)",
              gap: 10,
              padding: 12,
              border: "1px solid var(--line)",
              borderRadius: 4,
              background: "var(--bg-1)",
            }}>
              <div>
                <div className="label" style={{ marginBottom: 4 }}>Station name</div>
                <input
                  id="routing-station-name"
                  name="routing-station-name"
                  aria-label="Station name"
                  value={edit.name || ""}
                  onChange={e => setEdit(prev => ({ ...prev, name: e.target.value }))}
                  style={{
                    width: "100%", padding: "6px 8px",
                    border: "1px solid var(--line)", background: "var(--bg-0)",
                    color: "var(--ink)", borderRadius: 3, fontSize: 12,
                  }}
                />
              </div>
              <div>
                <div className="label" style={{ marginBottom: 4 }}>Owner</div>
                <input
                  id="routing-station-owner"
                  name="routing-station-owner"
                  aria-label="Owner"
                  value={edit.owner || ""}
                  onChange={e => setEdit(prev => ({ ...prev, owner: e.target.value }))}
                  style={{
                    width: "100%", padding: "6px 8px",
                    border: "1px solid var(--line)", background: "var(--bg-0)",
                    color: "var(--ink)", borderRadius: 3, fontSize: 12,
                  }}
                />
              </div>
              <div>
                <div className="label" style={{ marginBottom: 4 }}>Cycle (min)</div>
                <input
                  type="number"
                  id="routing-station-cycle-min"
                  name="routing-station-cycle-min"
                  aria-label="Cycle (min)"
                  value={edit.cycle_min || 0}
                  onChange={e => setEdit(prev => ({ ...prev, cycle_min: Number(e.target.value) || 0 }))}
                  style={_A4_INPUT_MONO_STYLE}
                />
              </div>
              <div>
                <div className="label" style={{ marginBottom: 4 }}>Station type</div>
                <select
                  id="routing-station-type"
                  name="routing-station-type"
                  aria-label="Station type"
                  value={edit.station_type || "assembly"}
                  onChange={e => setEdit(prev => ({ ...prev, station_type: e.target.value }))}
                  style={_A4_INPUT_STYLE}
                >
                  <option value="inbound">inbound</option>
                  <option value="qa-inbound">qa-inbound</option>
                  <option value="warehouse">warehouse</option>
                  <option value="machining">machining</option>
                  <option value="assembly">assembly</option>
                  <option value="test">test</option>
                  <option value="final">final</option>
                </select>
              </div>
              <div style={{ gridColumn: "1 / span 2", display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 4 }}>
                <span style={{ fontSize: 12, color: "var(--ink-3)" }}>
                  Linked CTQ gates: <b style={{ color: "var(--ink-2)" }}>
                  {(T.quality_gates || []).filter(g => g.station === edit.id).length}
                  </b>
                </span>
                <div style={{ display: "flex", gap: 6 }}>
                  <Btn onClick={() => {
                    const s = stations.find(x => x.id === selectedId);
                    if (s) setEdit({ ...s });
                  }}>Discard</Btn>
                  <Btn variant="primary" onClick={applyEdit}>Save · bumps to {revName}</Btn>
                </div>
              </div>
            </div>

            {/* secondary metadata grid */}
            <div className="label" style={{ marginTop: 16, marginBottom: 6 }}>STATION METRICS · last 7d</div>
            <div style={{
              display: "grid",
              gridTemplateColumns: "repeat(4, 1fr)",
              border: "1px solid var(--line)", borderRadius: 4, overflow: "hidden",
              background: "var(--bg-0)",
            }}>
              <StatPill label="actual cycle" value={(edit.cycle_min * 1.04).toFixed(1) + "m"} delta="+4%"   trend="up" />
              <StatPill label="first-pass yield" value="94.2%"                                  delta="+1.2pt" trend="up" />
              <StatPill label="ncr count" value="3"                                              delta="−2 wk" trend="down" />
              <StatPill label="utilization" value="0.78"                                         delta="+0.06" trend="up" />
            </div>

            {/* downstream impact panel */}
            <div className="label" style={{ marginTop: 16, marginBottom: 6 }}>DOWNSTREAM IMPACT IF EDITED</div>
            <ul style={{ margin: 0, padding: 0, listStyle: "none", border: "1px solid var(--line)", borderRadius: 4, background: "var(--bg-0)" }}>
              {[
                "WO-44219 Helios AgriFlow · 500 units · in-progress",
                "WO-44221 5HP scale-up pilot · 10 units · pilot",
                "Aftermarket spares lot Q2 · 200 units · scheduled",
                "Control plan CP-M27418-rB will need review",
              ].map((t, i) => (
                <li key={t} style={{ padding: "7px 10px", borderBottom: "1px dashed var(--line-soft)", fontSize: 12, color: "var(--ink-2)", display: "flex", alignItems: "center", gap: 8 }}>
                  {_a4_dot(i === 0 ? "warn" : "info")}
                  {t}
                </li>
              ))}
            </ul>
          </div>
        </div>

        {/* Right rail: rev history */}
        <div style={{
          borderLeft: "1px solid var(--line)",
          background: "var(--bg-1)",
          display: "flex", flexDirection: "column", minHeight: 0,
        }}>
          <div style={{ padding: "10px 12px", borderBottom: "1px solid var(--line)" }}>
            <div className="label" style={{ marginBottom: 6 }}>VERSION HISTORY</div>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <input
                id="routing-revision-name"
                name="routing-revision-name"
                aria-label="Revision name"
                value={revName}
                onChange={e => setRevName(e.target.value)}
                style={_A4_REV_INPUT_STYLE}
              />
              <Btn variant="primary" onClick={() => setShowRelease(true)}>Release</Btn>
            </div>
          </div>

          <div style={{ flex: 1, overflow: "auto" }}>
            {revisions.slice().reverse().map(r => (
              <div key={r.id} style={{
                padding: "10px 12px",
                borderBottom: "1px solid var(--line-soft)",
                background: r.current ? "color-mix(in oklch, var(--accent) 8%, transparent)" : "transparent",
              }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <span className="mono" style={{ fontSize: 12, color: "var(--ink)" }}>{r.label}</span>
                  {r.current && <Pill tone="ok">current</Pill>}
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4 }}>{r.note}</div>
                <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4, fontFamily: "var(--font-mono)" }}>by {r.author}</div>
              </div>
            ))}
          </div>

          <div style={{ padding: "10px 12px", borderTop: "1px solid var(--line)", background: "var(--bg-2)" }}>
            <div className="label" style={{ marginBottom: 4 }}>NOTES</div>
            <ul style={{ margin: 0, padding: "0 0 0 14px", fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>
              <li>Release locks the routing for new WO scheduling.</li>
              <li>Active WOs continue against the rev they were started on.</li>
              <li>Control plan & PFMEA links auto-revalidate.</li>
            </ul>
          </div>
        </div>
      </div>

      {showRelease && (
        <_A4Modal
          title="Release routing"
          body={
            <span>
              Affects <b>WO-44219</b> and <b>3 others</b> currently scheduled on rev B.
              New WOs after release will use <b>{revName}</b>. Existing WOs continue on rev B unless re-pegged. Continue?
            </span>
          }
          onCancel={() => setShowRelease(false)}
          onConfirm={releaseRevision}
          confirmLabel={"Release " + revName}
        />
      )}

      {toast && (
        <div style={_A4_TOAST_STYLE}>
          {toast.kind === "err" ? "error · " : ""}{toast.msg}
        </div>
      )}
    </div>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S22 — Quality gates editor  /build/gates
   ──────────────────────────────────────────────────────────────────── */

function ScreenQualityGatesEditor() {
  const T = useA4Tenant() || {};
  const stations = T.routing || [];
  const initial = (T.quality_gates || []).slice();

  const [gates, setGates] = uSA4(initial);
  const [filterStation, setFilterStation] = uSA4("all");
  const [onlyHighSev, setOnlyHighSev] = uSA4(false);
  const [filterInstr, setFilterInstr] = uSA4("all");
  const [search, setSearch] = uSA4("");
  const [selected, setSelected] = uSA4({}); // {gate_id: true}
  const [edits, setEdits] = uSA4({});       // {gate_id: {field: value}}
  const [adding, setAdding] = uSA4(false);
  const [draft, setDraft] = uSA4({
    gate_id: "", station: "s2", ctq_param: "", instrument: "", spec: "", ref_doc: "", effect_if_failed: "", severity: "med",
  });
  const [bulkOpen, setBulkOpen] = uSA4(false);
  const [bulkAction, setBulkAction] = uSA4("tighten");
  const [savedToast, setSavedToast] = uSA4(null);
  const _toastTimerRef = uRA4(null);

  // unique instruments
  const instrSet = uMA4(() => {
    const s = new Set();
    initial.forEach(g => s.add((g.instrument || "").split(" ")[0]));
    return Array.from(s);
  }, [initial]);

  uEA4(() => {
    const onTenant = () => {
      const T2 = (typeof getActiveTenant === "function" ? getActiveTenant() : null) || {};
      setGates((T2.quality_gates || []).slice()); setEdits({}); setSelected({}); setAdding(false); setBulkOpen(false); setSavedToast(null);
      setSearch(""); setFilterStation("all"); setFilterInstr("all"); setOnlyHighSev(false);
      if (_toastTimerRef.current) { clearTimeout(_toastTimerRef.current); _toastTimerRef.current = null; }
    };
    window.addEventListener("forge:tenant-change", onTenant);
    return () => { window.removeEventListener("forge:tenant-change", onTenant); if (_toastTimerRef.current) clearTimeout(_toastTimerRef.current); };
  }, []);

  const visible = uMA4(() => {
    return gates.filter(g => {
      if (filterStation !== "all" && g.station !== filterStation) return false;
      if (onlyHighSev && _a4_severityRank(g.severity) < 3) return false;
      if (filterInstr !== "all" && !((g.instrument || "").toLowerCase().includes(filterInstr.toLowerCase()))) return false;
      if (search.trim()) {
        const q = search.toLowerCase();
        if (!(
          (g.ctq_param || "").toLowerCase().includes(q) ||
          (g.gate_id || "").toLowerCase().includes(q) ||
          (g.ref_doc || "").toLowerCase().includes(q)
        )) return false;
      }
      return true;
    });
  }, [gates, filterStation, onlyHighSev, filterInstr, search]);

  const grouped = uMA4(() => {
    const m = {};
    visible.forEach(g => { (m[g.station] = m[g.station] || []).push(g); });
    return m;
  }, [visible]);

  function patchGate(id, patch) {
    setEdits(prev => ({ ...prev, [id]: { ...(prev[id] || {}), ...patch } }));
  }

  function commit() {
    // W14D — optimistic local update first so the UI feels instant, then
    // persist one `replace` per dirty gate so the edit survives refresh.
    // The server-side applyPatch rejects root paths, so we cannot send a
    // single bulk replace; per-index ops keep the array in lockstep.
    const newGates = gates.map(g => edits[g.gate_id] ? { ...g, ...edits[g.gate_id] } : g);
    setGates(newGates);
    setEdits({});
    setSavedToast("Saved " + Object.keys(edits).length + " gate edits");
    if (_toastTimerRef.current) clearTimeout(_toastTimerRef.current);
    _toastTimerRef.current = setTimeout(() => setSavedToast(null), 2400);
    if (window.forgeApi && window.forgeApi.mutate) {
      gates.forEach((g, idx) => {
        if (!edits[g.gate_id]) return;
        const merged = newGates[idx];
        window.forgeApi.mutate("quality_gates", {
          op: "replace",
          path: "/" + idx,
          value: merged,
        }).catch(e => {
          if (typeof console !== "undefined") console.warn("[gates] commit persist failed", e);
        });
      });
    }
  }

  function bulkApply() {
    const ids = Object.keys(selected).filter(k => selected[k]);
    // W14D — apply locally first, then persist a `replace` per touched gate
    // (root replace is rejected by applyPatch; per-index keeps array shape).
    const newGates = gates.map(g => {
      if (!selected[g.gate_id]) return g;
      if (bulkAction === "tighten") return { ...g, spec: g.spec + " · ±tightened 20%" };
      if (bulkAction === "critical") return { ...g, severity: "critical" };
      if (bulkAction === "instr-cal") return { ...g, instrument: g.instrument + " · re-cal due" };
      return g;
    });
    setGates(newGates);
    setSelected({});
    setBulkOpen(false);
    setSavedToast("Applied to " + ids.length + " gates");
    if (_toastTimerRef.current) clearTimeout(_toastTimerRef.current);
    _toastTimerRef.current = setTimeout(() => setSavedToast(null), 2400);
    if (window.forgeApi && window.forgeApi.mutate) {
      gates.forEach((g, idx) => {
        if (!selected[g.gate_id]) return;
        const merged = newGates[idx];
        window.forgeApi.mutate("quality_gates", {
          op: "replace",
          path: "/" + idx,
          value: merged,
        }).catch(e => {
          if (typeof console !== "undefined") console.warn("[gates] bulkApply persist failed", e);
        });
      });
    }
  }

  function addGate() {
    if (!draft.gate_id || !draft.ctq_param) return;
    const newGate = { ...draft };
    // W14D — optimistic local update + persist append to tenant_kv so the new
    // gate survives refresh. `/-` resolves to "append" inside applyPatch.
    setGates(prev => [...prev, newGate]);
    setAdding(false);
    setDraft({ gate_id: "", station: "s2", ctq_param: "", instrument: "", spec: "", ref_doc: "", effect_if_failed: "", severity: "med" });
    if (window.forgeApi && window.forgeApi.mutate) {
      window.forgeApi.mutate("quality_gates", {
        op: "add",
        path: "/-",
        value: newGate,
      }).catch(e => {
        if (typeof console !== "undefined") console.warn("[gates] addGate persist failed", e);
      });
    }
  }

  const valOf = (g, k) => (edits[g.gate_id] && (k in edits[g.gate_id])) ? edits[g.gate_id][k] : g[k];

  const colTpl = "26px 80px 1fr 1.2fr 0.8fr 0.9fr 1.2fr 70px";

  const selCount = Object.keys(selected).filter(k => selected[k]).length;
  const dirtyCount = Object.keys(edits).length;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>
      <_A4Toolbar
        right={
          <>
            <Btn icon="Diff" onClick={() => setBulkOpen(true)} title="Apply edit to all selected">
              Bulk apply ({selCount})
            </Btn>
            <Btn icon="Plus" onClick={() => setAdding(a => !a)}>
              {adding ? "Close add" : "Add new gate"}
            </Btn>
            <Btn variant="primary" icon="Check" onClick={commit} title="Commit pending edits">
              Save edits ({dirtyCount})
            </Btn>
          </>
        }
      >
        <div style={_A4_FIND_BAR_STYLE}>
          {I("Search", { size: 12 })}
          <input id="pump-a4-f1" name="pump-a4-f1" aria-label="Search gates / CTQs / ref docs"
            placeholder="Search gates / CTQs / ref docs"
            value={search}
            onChange={e => setSearch(e.target.value)}
            style={{ flex: 1, background: "transparent", border: 0, outline: "0 solid transparent", color: "var(--ink)", font: "inherit", fontSize: 12 }}
          />
        </div>
        <select id="pump-a4-f2" name="pump-a4-f2"
          value={filterStation}
          onChange={e => setFilterStation(e.target.value)}
          style={{ padding: "4px 6px", border: "1px solid var(--line)", background: "var(--bg-0)", color: "var(--ink)", borderRadius: 3, fontSize: 12 }}
        >
          <option value="all">All stations</option>
          {stations.map(s => <option key={s.id} value={s.id}>{s.id} · {s.name}</option>)}
        </select>
        <select id="pump-a4-f3" name="pump-a4-f3"
          value={filterInstr}
          onChange={e => setFilterInstr(e.target.value)}
          style={{ padding: "4px 6px", border: "1px solid var(--line)", background: "var(--bg-0)", color: "var(--ink)", borderRadius: 3, fontSize: 12 }}
        >
          <option value="all">Any instrument</option>
          {instrSet.map(s => <option key={s} value={s}>{s}</option>)}
        </select>
        <label style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, color: "var(--ink-3)" }}>
          <input id="pump-a4-f4" name="pump-a4-f4" type="checkbox" checked={onlyHighSev} onChange={e => setOnlyHighSev(e.target.checked)} />
          severity ≥ 3
        </label>
        <span style={{ fontSize: 12, color: "var(--ink-3)", marginLeft: 8 }}>
          showing <b style={{ color: "var(--ink-2)" }}>{visible.length}</b> / {gates.length} gates
        </span>
      </_A4Toolbar>

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: adding ? "1fr 320px" : "1fr", minHeight: 0 }}>

        {/* Main grid */}
        <div style={{ overflow: "auto", display: "flex", flexDirection: "column", minHeight: 0 }}>

          {/* sticky header */}
          <div className="label" style={_a4GateGridHeader(colTpl)}>
            <div style={{ paddingLeft: 8 }}>
              <input id="pump-a4-f5" name="pump-a4-f5"
                type="checkbox"
                checked={visible.length > 0 && visible.every(g => selected[g.gate_id])}
                onChange={e => {
                  const sel = { ...selected };
                  visible.forEach(g => { if (e.target.checked) sel[g.gate_id] = true; else delete sel[g.gate_id]; });
                  setSelected(sel);
                }}
              />
            </div>
            <div>Gate ID</div>
            <div>CTQ param</div>
            <div>Instrument</div>
            <div>Spec</div>
            <div>Ref doc</div>
            <div>Effect if failed</div>
            <div>Sev</div>
          </div>

          {/* groups */}
          {stations.reduce((acc, st) => { if (grouped[st.id] && grouped[st.id].length) acc.push(st); return acc; }, []).map(st => (
            <div key={st.id}>
              <div style={_A4_STATION_GROUP_HEADER}>
                <span style={{ color: "var(--ink-3)" }}>{st.id.toUpperCase()}</span>
                <span style={{ color: "var(--ink)" }}>{st.name}</span>
                <span style={{ color: "var(--ink-3)" }}>·</span>
                <span style={{ color: "var(--ink-3)" }}>{grouped[st.id].length} gates</span>
                <span style={{ color: "var(--ink-3)" }}>·</span>
                <span style={{ color: "var(--ink-3)" }}>cycle {st.cycle_min}m</span>
                <span style={{ marginLeft: "auto", color: "var(--ink-3)" }}>
                  owner {st.owner}
                </span>
              </div>

              {grouped[st.id].map(g => (
                <div key={g.gate_id} style={_a4GateRowStyle(colTpl, !!edits[g.gate_id])}>
                  <div style={{ paddingLeft: 8 }}>
                    <input
                      type="checkbox"
                      id={"gate-select-" + g.gate_id}
                      name={"gate-select-" + g.gate_id}
                      aria-label={"Select gate " + g.gate_id}
                      checked={!!selected[g.gate_id]}
                      onChange={e => setSelected(prev => ({ ...prev, [g.gate_id]: e.target.checked }))}
                    />
                  </div>
                  <div className="mono" style={{ color: "var(--ink-3)" }}>{g.gate_id}</div>
                  <input
                    id={"gate-ctq-" + g.gate_id}
                    name={"gate-ctq-" + g.gate_id}
                    aria-label={"CTQ param for gate " + g.gate_id}
                    value={valOf(g, "ctq_param") || ""}
                    onChange={e => patchGate(g.gate_id, { ctq_param: e.target.value })}
                    style={_a4_inlineInput()}
                  />
                  <input
                    id={"gate-instrument-" + g.gate_id}
                    name={"gate-instrument-" + g.gate_id}
                    aria-label={"Instrument for gate " + g.gate_id}
                    value={valOf(g, "instrument") || ""}
                    onChange={e => patchGate(g.gate_id, { instrument: e.target.value })}
                    style={_a4_inlineInput()}
                  />
                  <input
                    id={"gate-spec-" + g.gate_id}
                    name={"gate-spec-" + g.gate_id}
                    aria-label={"Spec for gate " + g.gate_id}
                    value={valOf(g, "spec") || ""}
                    onChange={e => patchGate(g.gate_id, { spec: e.target.value })}
                    style={_a4_inlineInput("var(--font-mono)")}
                  />
                  <input
                    id={"gate-ref-doc-" + g.gate_id}
                    name={"gate-ref-doc-" + g.gate_id}
                    aria-label={"Reference doc for gate " + g.gate_id}
                    value={valOf(g, "ref_doc") || ""}
                    onChange={e => patchGate(g.gate_id, { ref_doc: e.target.value })}
                    style={_a4_inlineInput("var(--font-mono)")}
                  />
                  <input
                    id={"gate-effect-if-failed-" + g.gate_id}
                    name={"gate-effect-if-failed-" + g.gate_id}
                    aria-label={"Effect if failed for gate " + g.gate_id}
                    value={valOf(g, "effect_if_failed") || ""}
                    onChange={e => patchGate(g.gate_id, { effect_if_failed: e.target.value })}
                    style={_a4_inlineInput()}
                  />
                  <div>
                    <select
                      id={"gate-severity-" + g.gate_id}
                      name={"gate-severity-" + g.gate_id}
                      aria-label={"Severity for gate " + g.gate_id}
                      value={valOf(g, "severity") || "med"}
                      onChange={e => patchGate(g.gate_id, { severity: e.target.value })}
                      style={{
                        background: "transparent",
                        border: "1px solid transparent",
                        color: "var(--ink-2)",
                        fontSize: 12,
                        padding: "2px 4px",
                      }}
                    >
                      <option value="low">low</option>
                      <option value="med">med</option>
                      <option value="high">high</option>
                      <option value="critical">critical</option>
                    </select>
                  </div>
                </div>
              ))}
            </div>
          ))}

          {visible.length === 0 && (
            <div style={{ padding: 32, textAlign: "center", color: "var(--ink-4)", fontSize: 12 }}>
              No gates match the current filters.
            </div>
          )}
        </div>

        {/* Add gate side panel */}
        {adding && (
          <div style={{ borderLeft: "1px solid var(--line)", background: "var(--bg-1)", padding: 14, overflow: "auto" }}>
            <div className="label" style={{ marginBottom: 8 }}>NEW QUALITY GATE</div>
            {[
              { k: "gate_id",         lbl: "Gate ID",      mono: true },
              { k: "ctq_param",       lbl: "CTQ parameter" },
              { k: "instrument",      lbl: "Instrument" },
              { k: "spec",            lbl: "Spec",        mono: true },
              { k: "ref_doc",         lbl: "Ref doc",     mono: true },
              { k: "effect_if_failed",lbl: "Effect if failed" },
            ].map(f => (
              <div key={f.k} style={{ marginBottom: 8 }}>
                <div className="label" style={{ marginBottom: 3 }}>{f.lbl}</div>
                <input id="pump-a4-f6" name="pump-a4-f6"
                  value={draft[f.k] || ""}
                  onChange={e => setDraft(prev => ({ ...prev, [f.k]: e.target.value }))}
                  style={_a4DraftInputStyle(f.mono)}
                />
              </div>
            ))}
            <div style={{ marginBottom: 8 }}>
              <div className="label" style={{ marginBottom: 3 }}>Station</div>
              <select id="pump-a4-f7" name="pump-a4-f7"
                value={draft.station}
                onChange={e => setDraft(prev => ({ ...prev, station: e.target.value }))}
                style={{ width: "100%", padding: "5px 7px", border: "1px solid var(--line)", background: "var(--bg-0)", color: "var(--ink)", borderRadius: 3, fontSize: 12 }}
              >
                {stations.map(s => <option key={s.id} value={s.id}>{s.id} · {s.name}</option>)}
              </select>
            </div>
            <div style={{ marginBottom: 8 }}>
              <div className="label" style={{ marginBottom: 3 }}>Severity</div>
              <select id="pump-a4-f8" name="pump-a4-f8"
                value={draft.severity}
                onChange={e => setDraft(prev => ({ ...prev, severity: e.target.value }))}
                style={{ width: "100%", padding: "5px 7px", border: "1px solid var(--line)", background: "var(--bg-0)", color: "var(--ink)", borderRadius: 3, fontSize: 12 }}
              >
                <option value="low">low</option>
                <option value="med">med</option>
                <option value="high">high</option>
                <option value="critical">critical</option>
              </select>
            </div>
            <div style={{ display: "flex", justifyContent: "flex-end", gap: 6, marginTop: 12 }}>
              <Btn onClick={() => setAdding(false)}>Cancel</Btn>
              <Btn variant="primary" onClick={addGate}>Add gate</Btn>
            </div>
            <div style={{ marginTop: 16, fontSize: 12, color: "var(--ink-4)", lineHeight: 1.5 }}>
              <div className="label" style={{ marginBottom: 4 }}>NOTES</div>
              New gates are created in <i>draft</i>. They become enforceable only after the next control-plan release.
            </div>
          </div>
        )}
      </div>

      {bulkOpen && (
        <_A4Modal
          title="Bulk apply"
          body={
            <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
              <span>Apply action to <b>{selCount}</b> selected gate(s).</span>
              <select id="pump-a4-f9" name="pump-a4-f9"
                value={bulkAction}
                onChange={e => setBulkAction(e.target.value)}
                style={{ padding: "6px 8px", border: "1px solid var(--line)", background: "var(--bg-0)", color: "var(--ink)", borderRadius: 3, fontSize: 12 }}
              >
                <option value="tighten">Tighten spec by 20%</option>
                <option value="critical">Mark severity = critical</option>
                <option value="instr-cal">Flag instrument re-calibration</option>
              </select>
            </div>
          }
          onCancel={() => setBulkOpen(false)}
          onConfirm={bulkApply}
          confirmLabel="Apply"
        />
      )}

      {savedToast && (
        <div style={_A4_TOAST_STYLE}>
          {savedToast}
        </div>
      )}
    </div>
  );
}

function _a4_inlineInput(font) {
  return {
    width: "100%",
    background: "transparent",
    border: "1px solid transparent",
    padding: "4px 6px",
    color: "var(--ink-2)",
    fontSize: 12,
    fontFamily: font || "inherit",
    outline: 0,
  };
}

/* ════════════════════════════════════════════════════════════════════
   S23 — Control plan view  /build/control-plan
   ──────────────────────────────────────────────────────────────────── */

function ScreenControlPlan() {
  const T = useA4Tenant() || {};
  const stations = T.routing || [];
  const gates = T.quality_gates || [];

  // synthesize per-station rows
  const rows = uMA4(() => {
    return stations.map(st => {
      const stGates = gates.filter(g => g.station === st.id);
      const ctqs = stGates.map(g => g.ctq_param);
      const instr = Array.from(new Set(stGates.map(g => (g.instrument || "").split(" ")[0]))).slice(0, 3);
      // sample size & frequency vary realistically per station type
      let sample, freq, reaction, resp;
      if (st.station_type === "inbound" || st.station_type === "qa-inbound") {
        sample = "AQL 1.0 · n=8 / lot"; freq = "every inbound lot"; reaction = "Quarantine lot · raise NCR · supplier alert"; resp = "Aryan M. · QC inbound";
      } else if (st.station_type === "warehouse") {
        sample = "every 10th unit";    freq = "shift-end audit";   reaction = "Re-bin · cycle count"; resp = "Priya I. · stores";
      } else if (st.station_type === "machining") {
        sample = "100% inspection · shaft Ø, stator bore"; freq = "in-process · every unit"; reaction = "Stop machine · re-cut if salvageable · scrap if not"; resp = "Rohit B. · machine shop";
      } else if (st.station_type === "assembly") {
        sample = "100% electrical · winding params"; freq = "in-process · every unit"; reaction = "Hold unit · re-wind cycle · escalate to engineering if 2+ in shift"; resp = "Neha G. · assembly";
      } else if (st.station_type === "test") {
        sample = "100% test · all params logged"; freq = "in-process · every unit"; reaction = "Hold unit · investigate · re-test once"; resp = "Neha G. · test station";
      } else {
        sample = "100% final test · serial-bound report"; freq = "in-process · every unit"; reaction = "Hold · investigate · CAPA loop"; resp = "Rohit B. · final assy";
      }
      return {
        st,
        process: st.name,
        ctqs,
        sample,
        freq,
        method: instr.join(" · ") || "—",
        reaction,
        resp,
        gate_ids: stGates.map(g => g.gate_id),
      };
    });
  }, [stations, gates]);

  const [selectedStation, setSelectedStation] = uSA4(stations[0]?.id || "s1");
  const colTpl = "70px 1.2fr 1.6fr 0.9fr 0.9fr 1fr 1.4fr 1fr";

  uEA4(() => {
    const onTenant = () => { const T2 = (typeof getActiveTenant === "function" ? getActiveTenant() : null) || {}; setSelectedStation((T2.routing && T2.routing[0] && T2.routing[0].id) || "s1"); };
    window.addEventListener("forge:tenant-change", onTenant);
    return () => window.removeEventListener("forge:tenant-change", onTenant);
  }, []);

  const selectedRow = rows.find(r => r.st.id === selectedStation);

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      <_A4Toolbar
        right={
          <>
            <Btn icon="File" onClick={() => window.forgeToast && window.forgeToast("Export PDF queued — wired in v2")}>Export PDF · CP-M27418-rB</Btn>
            <Btn icon="Diff" onClick={() => window.forgeToast && window.forgeToast("Compare to rev A queued — wired in v2")}>Compare to rev A</Btn>
            <Btn variant="primary" icon="Check" disabled title="coming in v2">Approve & lock</Btn>
          </>
        }
      >
        <span style={{ fontSize: 12, color: "var(--ink-4)" }}>
          Control Plan <b className="mono" style={{ color: "var(--ink-2)" }}>CP-M27418-rB</b>
          &nbsp;·&nbsp;7 stations · {gates.length} CTQs
          &nbsp;·&nbsp;<span style={{ color: "var(--ok)" }}>signed off</span> by Karan V. · Devansh A.
        </span>
      </_A4Toolbar>

      <div style={{ flex: 1, display: "flex", flexDirection: "column", minHeight: 0, overflow: "auto" }}>

        {/* Grid */}
        <div style={_a4ControlPlanHeader(colTpl)}>
          <div style={{ paddingLeft: 10 }}>Station</div>
          <div>Process</div>
          <div>CTQ spec</div>
          <div>Sample size</div>
          <div>Frequency</div>
          <div>Method</div>
          <div>Reaction plan</div>
          <div>Responsible</div>
        </div>

        {rows.map(r => (
          <div
            key={r.st.id}
            role="button"
            tabIndex={0}
            onClick={() => setSelectedStation(r.st.id)}
            onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelectedStation(r.st.id); } }}
            style={_a4ControlPlanRowStyle(colTpl, r.st.id === selectedStation)}
          >
            <div style={{ paddingLeft: 10, display: "flex", alignItems: "center", gap: 8 }}>
              {_a4_dot(r.st.id === selectedStation ? "info" : "mute")}
              <span className="mono" style={{ color: "var(--ink-3)" }}>{r.st.id.toUpperCase()}</span>
            </div>
            <div>
              <div style={{ color: "var(--ink)", fontWeight: 500 }}>{r.process}</div>
              <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
                seq {r.st.seq} · cycle {r.st.cycle_min}m · {r.st.station_type}
              </div>
            </div>
            <div style={{ color: "var(--ink-2)", lineHeight: 1.45 }}>
              {r.ctqs.length === 0 ? <span style={{ color: "var(--ink-4)" }}>-</span> : r.ctqs.map((c, i) => (
                <div key={c} style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 2 }}>
                  <span className="mono" style={{ fontSize: 12, color: "var(--ink-4)", minWidth: 36 }}>{r.gate_ids[i]}</span>
                  <span>{c}</span>
                </div>
              ))}
            </div>
            <div className="mono" style={{ color: "var(--ink-2)", fontSize: 12 }}>{r.sample}</div>
            <div style={{ color: "var(--ink-2)" }}>{r.freq}</div>
            <div className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>{r.method}</div>
            <div style={{ color: "var(--ink-2)", fontSize: 12 }}>{r.reaction}</div>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              {_a4_avatar(r.st.owner, 20)}
              <span style={{ fontSize: 12, color: "var(--ink-2)" }}>{r.resp.split(" · ")[1] || r.resp}</span>
            </div>
          </div>
        ))}

        {/* Detail panel — expanded view of selected station row */}
        {selectedRow && (
          <div style={{ padding: 14, background: "var(--bg-1)", borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)", fontSize: 12, lineHeight: 1.55 }}>
            <div className="label" style={{ marginBottom: 6 }}>
              STATION DETAIL · {selectedRow.st.id.toUpperCase()} · {selectedRow.process}
              <span style={{ color: "var(--ink-4)", fontWeight: 400 }}> · seq {selectedRow.st.seq} · cycle {selectedRow.st.cycle_min}m · {selectedRow.st.station_type} · {selectedRow.resp}</span>
            </div>
            <div className="mono" style={{ color: "var(--ink-4)", fontSize: 12 }}>
              CTQ GATES ({selectedRow.gate_ids.length}): <span style={{ color: "var(--ink-2)" }}>{selectedRow.gate_ids.length === 0 ? "—" : selectedRow.gate_ids.map((g, i) => g + " " + selectedRow.ctqs[i]).join(" · ")}</span>
            </div>
            <div style={{ marginTop: 6, color: "var(--ink-2)" }}><b style={{ color: "var(--ink-3)" }}>Reaction:</b> {selectedRow.reaction}</div>
            <div className="mono" style={{ marginTop: 4, color: "var(--ink-4)", fontSize: 12 }}>method · {selectedRow.method} &nbsp; sample · {selectedRow.sample} &nbsp; freq · {selectedRow.freq}</div>
          </div>
        )}

        {/* Flowchart */}
        <div style={{ padding: 24, background: "var(--bg-1)", borderTop: "1px solid var(--line)" }}>
          <div className="label" style={{ marginBottom: 14 }}>CONTROL FLOW · DECISION POINTS</div>
          <_A4ControlFlow stations={stations} />
        </div>

        {/* Notes */}
        <div style={{ padding: 16, background: "var(--bg-0)", borderTop: "1px solid var(--line)" }}>
          <div className="label" style={{ marginBottom: 8 }}>REACTION-PLAN HIERARCHY</div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12 }}>
            {[
              { k: "Detect",   d: "Operator captures CTQ value at gate. Out-of-spec triggers immediate hold." },
              { k: "Quarantine", d: "Hold unit at station. Tag with ORANGE traveler stripe. Lock from advancing in MES." },
              { k: "NCR",      d: "Auto-raise NCR with serial, gate ref, captured value, photo. Routes to QC engineer." },
              { k: "CAPA loop", d: "If repeat (≥2 in shift), spawn CAPA. Ties back to PFMEA mode for systemic fix." },
            ].map((b, i) => (
              <div key={b.k} style={{ padding: 10, border: "1px solid var(--line)", borderRadius: 3, background: "var(--bg-1)" }}>
                <div className="mono" style={{ fontSize: 12, color: "var(--ink-3)", letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 4 }}>
                  step {i + 1} · {b.k}
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-2)", lineHeight: 1.5 }}>{b.d}</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

function _A4ControlFlow({ stations }) {
  // simple boxes-and-arrows with a quarantine/NCR side branch
  const W = 1100, H = 200;
  const boxW = 110, boxH = 44, gap = 24;
  const startX = 30, midY = 70;
  return (
    <svg viewBox={"0 0 " + W + " " + H} width="100%" height={H}>
      <defs>
        <marker id="cf-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
          <path d="M0,0 L10,5 L0,10 Z" fill="var(--ink-3)" />
        </marker>
        <marker id="cf-arrow-warn" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">
          <path d="M0,0 L10,5 L0,10 Z" fill="var(--warn)" />
        </marker>
      </defs>
      {stations.map((s, i) => {
        const x = startX + i * (boxW + gap);
        return (
          <g key={s.id}>
            <rect x={x} y={midY} width={boxW} height={boxH} rx="3" fill="var(--bg-0)" stroke="var(--line)" />
            <text x={x + boxW / 2} y={midY + 18} fontSize="10.5" fontWeight="600" textAnchor="middle" fill="var(--ink)">{s.name.split(" ")[0]}</text>
            <text x={x + boxW / 2} y={midY + 32} fontSize="9.5" textAnchor="middle" fontFamily="var(--font-mono)" fill="var(--ink-4)">
              {s.id.toUpperCase()} · {s.cycle_min}m
            </text>
            {/* downward NCR branch */}
            <line x1={x + boxW / 2} y1={midY + boxH} x2={x + boxW / 2} y2={midY + boxH + 28} stroke="var(--warn)" strokeDasharray="3 3" />
            <text x={x + boxW / 2 + 4} y={midY + boxH + 22} fontSize="8.5" fill="var(--warn)" fontFamily="var(--font-mono)">fail</text>
            {/* arrow to next */}
            {i < stations.length - 1 && (
              <line x1={x + boxW} y1={midY + boxH / 2} x2={x + boxW + gap - 4} y2={midY + boxH / 2} stroke="var(--ink-3)" strokeWidth="1.2" markerEnd="url(#cf-arrow)" />
            )}
          </g>
        );
      })}
      {/* quarantine row */}
      <rect x={startX + 60} y={midY + boxH + 38} width="180" height="34" rx="3" fill="color-mix(in oklch, var(--warn) 14%, var(--bg-0))" stroke="var(--warn)" />
      <text x={startX + 150} y={midY + boxH + 60} fontSize="11" textAnchor="middle" fontWeight="600" fill="var(--warn)">QUARANTINE · NCR · CAPA</text>

      <text x={startX + 280} y={midY + boxH + 60} fontSize="10" fill="var(--ink-4)" fontFamily="var(--font-mono)">
        any-station fail → hold unit → MES lock → QC review → CAPA if repeat
      </text>
    </svg>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S25 — Line readiness panel  /line/readiness
   ──────────────────────────────────────────────────────────────────── */

function ScreenLineReadiness() {
  const T = useA4Tenant() || {};
  const allParts = T.parts || [];
  // pick 12 critical parts deterministically
  const criticalParts = uMA4(() => {
    const wanted = ["10.04","10.05","10.06","10.07","10.08","10.12","20.04","20.05","20.06","20.11","20.13","20.20"];
    return wanted.flatMap(num => {
      const found = allParts.find(p => p.num === num);
      return found ? [found] : [];
    });
  }, [allParts]);

  // Synthesize on-hand and required, with one short item
  const bom = uMA4(() => criticalParts.map((p, i) => {
    const required = (i === 8) ? 500 : (i === 5 ? 1500 : 500);
    let onHand;
    if (p.num === "20.04") onHand = 488;             // C.I. body short
    else if (p.num === "10.07") onHand = 522;        // small surplus
    else if (p.num === "10.12") onHand = 1480;
    else onHand = required + (i % 5) * 18 + 24;
    return { p, required, onHand, short: onHand < required };
  }), [criticalParts]);

  const [showDual, setShowDual] = uSA4(false);
  const [picked, setPicked] = uSA4(null);

  uEA4(() => {
    const onTenant = () => { setShowDual(false); setPicked(null); };
    window.addEventListener("forge:tenant-change", onTenant);
    return () => window.removeEventListener("forge:tenant-change", onTenant);
  }, []);

  const tooling = [
    { id: "FIX-12", name: "Stator press fixture",         status: "ready", calib: "2026-04-12" },
    { id: "FIX-08", name: "Rotor balancing arbor",        status: "ready", calib: "2026-04-09" },
    { id: "GRP-01", name: "Bore gauge 50-160 set",        status: "ready", calib: "2026-04-22" },
    { id: "GRP-02", name: "Digital caliper kit (5×)",     status: "ready", calib: "2026-04-15" },
    { id: "GRP-03", name: "Megger MIT420 IR tester",      status: "ready", calib: "2026-03-30" },
    { id: "GRP-04", name: "Hipot tester 5kV",             status: "ready", calib: "2026-04-04" },
    { id: "GRP-05", name: "Schenck dynamic balancer",     status: "ready", calib: "2026-04-07" },
    { id: "FIX-20", name: "Hydro pressure rig (1.5×)",    status: "ready", calib: "2026-04-19" },
    { id: "FIX-21", name: "Performance test rig · 4\"",   status: "in-prep", calib: "2026-04-29 · re-cal in-progress" },
    { id: "GRP-06", name: "Torque wrench 5-25 Nm (3×)",   status: "ready", calib: "2026-04-18" },
    { id: "FIX-30", name: "Cable poka-yoke fixture R-Y-B",status: "ready", calib: "2026-04-17" },
    { id: "FIX-32", name: "Carton seal pull-test rig",    status: "ready", calib: "2026-04-08" },
  ];

  const operators = [
    { name: "Aryan M.",  role: "Receiving + Incoming Inspect (s1, s2)", shift: "A", skills: { receiving: 1, qc_inbound: 1, machining: 0.4, assembly: 0.3, test: 0.2, final: 0.4 } },
    { name: "Priya I.",  role: "Storage / kitting (s3)",                shift: "A", skills: { receiving: 0.7, qc_inbound: 0.5, machining: 0.2, assembly: 0.6, test: 0.3, final: 0.4 } },
    { name: "Rohit B.",  role: "Machine Shop · Final Assy (s4, s7)",    shift: "A", skills: { receiving: 0.4, qc_inbound: 0.5, machining: 1, assembly: 0.7, test: 0.6, final: 1 } },
    { name: "Neha G.",   role: "Motor Assy + Test (s5, s6)",            shift: "A", skills: { receiving: 0.3, qc_inbound: 0.6, machining: 0.4, assembly: 1, test: 1, final: 0.6 } },
  ];

  const ncrs = [
    { id: "NCR-2026-0017", title: "Stator tube OD 0.04mm undersized", supplier: "Kestrel Tubing", lot: "KT-LOT-2026-0412", age_days: 3, severity: "high",     status: "open · RTV pending dispo" },
    { id: "NCR-2026-0018", title: "Hipot tester · cal due in 5d",     supplier: "—",              lot: "—",                age_days: 1, severity: "med",      status: "open · awaiting calibration agency" },
  ];

  const altSuppliers = [
    { name: "Drava Foundry",   loc: "Kolhapur, IN",  lt: 28, audit: 88, note: "Approved for SG-iron parts on M-26102; can lift to 200/wk" },
    { name: "Sentinel Metalworks",loc: "Anand, IN",  lt: 32, audit: 84, note: "Conditional supplier; fast-track audit feasible in 5d" },
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>
      <_A4Toolbar
        right={
          <>
            <Btn icon="Calendar" onClick={() => window.forgeToast && window.forgeToast("Shift schedule queued — wired in v2")}>Shift schedule</Btn>
            <Btn icon="File" onClick={() => window.forgeToast && window.forgeToast("Export readiness PDF queued — wired in v2")}>Export readiness PDF</Btn>
            <Btn variant="primary" icon="Check" disabled title="coming in v2">Lock line for tomorrow</Btn>
          </>
        }
      >
        <span style={{ fontSize: 12, color: "var(--ink-4)" }}>
          Tomorrow · <b className="mono" style={{ color: "var(--ink-2)" }}>2026-04-30 / Shift A</b>
          &nbsp;·&nbsp;target 22 units / shift &nbsp;·&nbsp;
          <span style={{ color: "var(--warn)" }}>1 BOM short, 1 tooling in-prep, 2 NCR open</span>
        </span>
      </_A4Toolbar>

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1.2fr 1fr 1fr 1fr", minHeight: 0 }}>

        {/* Column 1 — BOM */}
        <div style={{ borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--bg-1)", display: "flex", justifyContent: "space-between" }}>
            <span>BOM AVAILABILITY · 12 CRITICAL</span>
            <span style={{ color: "var(--warn)" }}>1 short</span>
          </div>
          <div style={{ flex: 1, overflow: "auto" }}>
            <div className="label" style={_A4_BOM_HEADER_STYLE}>
              <div>P/N</div>
              <div>Name</div>
              <div>Need</div>
              <div>O/H</div>
              <div>Status</div>
            </div>
            {bom.map(b => (
              <div
                key={b.p.num}
                style={{
                  display: "grid",
                  gridTemplateColumns: "60px 1fr 60px 60px 80px",
                  padding: "7px 12px",
                  borderBottom: "1px solid var(--line-soft)",
                  alignItems: "center",
                  fontSize: 12,
                  background: b.short ? "color-mix(in oklch, var(--err) 7%, transparent)" : "transparent",
                }}>
                <div className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{b.p.num}</div>
                <div>
                  <div style={{ color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                    {b.p.name}
                  </div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
                    {b.p.supplier} · LT {b.p.lead_time_days}d
                  </div>
                </div>
                <div className="mono tnum" style={{ color: "var(--ink-3)" }}>{b.required}</div>
                <div className="mono tnum" style={{ color: b.short ? "var(--err)" : "var(--ink)" }}>{b.onHand}</div>
                <div>
                  {b.short ? (
                    <Pill tone="err">short {b.required - b.onHand}</Pill>
                  ) : (
                    <Pill tone="ok">covered</Pill>
                  )}
                </div>
                {b.short && (
                  <div style={{ gridColumn: "1 / -1", padding: "8px 0 0 60px" }}>
                    <div style={{
                      padding: 8, background: "var(--bg-2)",
                      border: "1px dashed var(--err)", borderRadius: 3,
                      fontSize: 12, color: "var(--ink-2)",
                    }}>
                      <div style={{ marginBottom: 6 }}>
                        <b>{b.p.name}</b> short by <b className="mono">{b.required - b.onHand}</b> units.
                        Expedite from <b>Athena Castings</b> (lead 35d → 14d possible via priority).
                      </div>
                      <div style={{ display: "flex", gap: 6 }}>
                        <Btn onClick={() => window.forgeToast && window.forgeToast("Expedite primary queued — wired in v2")}>Expedite primary</Btn>
                        <Btn variant="primary" onClick={() => setShowDual(true)}>Suggest dual-source</Btn>
                      </div>
                    </div>
                  </div>
                )}
              </div>
            ))}
          </div>
        </div>

        {/* Column 2 — Tooling */}
        <div style={{ borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--bg-1)", display: "flex", justifyContent: "space-between" }}>
            <span>TOOLING · 12 ITEMS</span>
            <span style={{ color: "var(--warn)" }}>11 ready · 1 in-prep</span>
          </div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {tooling.map(t => (
              <div key={t.id} style={{
                display: "grid",
                gridTemplateColumns: "60px 1fr 70px",
                padding: "7px 12px",
                borderBottom: "1px solid var(--line-soft)",
                alignItems: "center",
                fontSize: 12,
              }}>
                <div className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{t.id}</div>
                <div>
                  <div style={{ color: "var(--ink)" }}>{t.name}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>cal {t.calib}</div>
                </div>
                <div>
                  {t.status === "ready" ? <Pill tone="ok">ready</Pill> : <Pill tone="warn">in-prep</Pill>}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Column 3 — Operators */}
        <div style={{ borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--bg-1)", display: "flex", justifyContent: "space-between" }}>
            <span>OPERATORS · SHIFT A</span>
            <span style={{ color: "var(--ok)" }}>4/4 confirmed</span>
          </div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {operators.map(o => (
              <div key={o.name} style={{ padding: "10px 12px", borderBottom: "1px solid var(--line-soft)" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
                  {_a4_avatar(o.name, 24)}
                  <div style={{ minWidth: 0 }}>
                    <div style={{ color: "var(--ink)", fontSize: 12 }}>{o.name}</div>
                    <div style={{ fontSize: 12, color: "var(--ink-4)" }}>{o.role}</div>
                  </div>
                </div>
                {/* skill matrix */}
                <div style={{
                  display: "grid",
                  gridTemplateColumns: "60px 1fr",
                  gap: 4,
                  marginTop: 4,
                  fontSize: 12,
                  fontFamily: "var(--font-mono)",
                }}>
                  {Object.entries(o.skills).map(([k, v]) => (
                    <React.Fragment key={k}>
                      <div style={{ color: "var(--ink-4)" }}>{k}</div>
                      <_A4SkillBar v={v} />
                    </React.Fragment>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Column 4 — NCR / Quality */}
        <div style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--bg-1)", display: "flex", justifyContent: "space-between" }}>
            <span>NCR / QUALITY</span>
            <span style={{ color: "var(--err)" }}>2 open</span>
          </div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {ncrs.map(n => (
              <div key={n.id} style={{ padding: 12, borderBottom: "1px solid var(--line-soft)" }}>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                  <span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>{n.id}</span>
                  <Pill tone={_a4_severityTone(n.severity)}>{n.severity}</Pill>
                </div>
                <div style={{ fontSize: 12, color: "var(--ink)", marginTop: 4 }}>{n.title}</div>
                <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4 }}>
                  {n.supplier !== "—" && <span>supplier <b>{n.supplier}</b> · </span>}
                  {n.lot !== "—" && <span>lot <span className="mono">{n.lot}</span> · </span>}
                  age <span className="mono">{n.age_days}d</span>
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 6 }}>{n.status}</div>
                <div style={{ marginTop: 6, display: "flex", gap: 6 }}>
                  <Btn onClick={() => window.forgeToast && window.forgeToast("NCR draft queued — wired in v2")}>Open NCR</Btn>
                  <Btn icon="Sparkle" onClick={() => window.forgeToast && window.forgeToast("Suggest dispo queued — wired in v2")}>Suggest dispo</Btn>
                </div>
              </div>
            ))}

            <div style={{ padding: 12, fontSize: 12, color: "var(--ink-3)", lineHeight: 1.5 }}>
              <div className="label" style={{ marginBottom: 4 }}>READINESS VERDICT</div>
              Line is <b style={{ color: "var(--warn)" }}>conditionally ready</b> for shift A on 2026-04-30.
              Resolve C.I. body short (12 units) and complete Performance test rig re-cal before 06:00.
              If both clear by 22:00 tonight, 22-unit target is achievable.
            </div>
          </div>
        </div>
      </div>

      {showDual && (
        <_A4Modal
          title="Dual-source candidates · C.I. body 86mm"
          body={
            <div>
              <div style={{ marginBottom: 10, fontSize: 12, color: "var(--ink-2)" }}>
                Existing primary (Spire Foundry) is currently blocked. Consider these alternates:
              </div>
              {altSuppliers.map(s => (
                <div key={s.name} role="button" tabIndex={0} style={{
                  padding: 10, marginBottom: 8,
                  border: "1px solid var(--line)", borderRadius: 3,
                  background: picked === s.name ? "color-mix(in oklch, var(--accent) 8%, transparent)" : "var(--bg-1)",
                  cursor: "pointer",
                }} onClick={() => setPicked(s.name)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setPicked(s.name); } }}>
                  <div style={{ display: "flex", justifyContent: "space-between" }}>
                    <b style={{ fontSize: 13 }}>{s.name}</b>
                    <span className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{s.loc}</span>
                  </div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4 }}>
                    LT <b className="mono">{s.lt}d</b> · audit <b className="mono">{s.audit}%</b>
                  </div>
                  <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 6 }}>{s.note}</div>
                </div>
              ))}
            </div>
          }
          onCancel={() => { setShowDual(false); setPicked(null); }}
          onConfirm={() => { setShowDual(false); setPicked(null); }}
          confirmLabel={picked ? "RFQ to " + picked : "Close"}
        />
      )}
    </div>
  );
}

function _A4SkillBar({ v }) {
  const tone = v >= 0.9 ? "var(--ok)" : v >= 0.6 ? "var(--info)" : v >= 0.3 ? "var(--warn)" : "var(--ink-4)";
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 4 }}>
      <div style={{ height: 5, flex: 1, background: "var(--bg-3)", borderRadius: 2, overflow: "hidden" }}>
        <div style={{ width: (v * 100) + "%", height: "100%", background: tone }} />
      </div>
      <span style={{ width: 26, textAlign: "right", color: "var(--ink-3)" }}>{Math.round(v * 100)}</span>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S27 — Station console: Receiving + Incoming Inspect
   /traveler/receiving
   ──────────────────────────────────────────────────────────────────── */

function ScreenStationReceiving() {
  const T = useA4Tenant() || {};
  const operator = "Aryan M.";
  const stationName = "Receiving + Incoming Inspect (s1, s2)";

  const initialLots = [
    { id: "LOT-2026-0418-A", part: "10.07", part_name: "Stator Tube 4\"",            supplier: "Kestrel Tubing",   qty: 250, grn: "2026-04-28 11:42", state: "awaiting", note: null },
    { id: "LOT-2026-0418-B", part: "10.04", part_name: "Rotor Shaft SS410",         supplier: "Vega Shafts",      qty: 600, grn: "2026-04-28 13:10", state: "awaiting", note: null },
    { id: "LOT-2026-0418-C", part: "10.12", part_name: "Copper Winding Wire 1.0sq", supplier: "Indus Copper Mills", qty: 80, grn: "2026-04-28 14:55", state: "awaiting", note: null },
    { id: "LOT-2026-0418-D", part: "10.06", part_name: "Stator Stack 120SL",        supplier: "Elara Stamping",   qty: 500, grn: "2026-04-28 09:20", state: "passed",   note: "AQL 1.0 · 8/8 pass" },
    { id: "LOT-2026-0418-E", part: "10.05", part_name: "Rotor Stamping Al-die",     supplier: "Elara Stamping",   qty: 500, grn: "2026-04-28 08:45", state: "passed",   note: "AQL 1.0 · 8/8 pass" },
    { id: "LOT-2026-0417-F", part: "10.07", part_name: "Stator Tube 4\"",            supplier: "Kestrel Tubing",   qty: 200, grn: "2026-04-27 17:30", state: "quarantined", note: "NCR-2026-0017 · OD 0.04mm undersized · RTV pending" },
  ];

  const [lots, setLots] = uSA4(initialLots);
  const [scan, setScan] = uSA4("");
  const [scanned, setScanned] = uSA4(null);
  const [actLog, setActLog] = uSA4([]);
  const [highlight, setHighlight] = uSA4(null);
  const _hlTimerRef = uRA4(null);

  // Reset console + clear highlight timer on unmount.
  uEA4(() => {
    const onTenant = () => {
      setLots(initialLots); setScan(""); setScanned(null); setActLog([]); setHighlight(null);
      if (_hlTimerRef.current) { clearTimeout(_hlTimerRef.current); _hlTimerRef.current = null; }
    };
    window.addEventListener("forge:tenant-change", onTenant);
    return () => { window.removeEventListener("forge:tenant-change", onTenant); if (_hlTimerRef.current) clearTimeout(_hlTimerRef.current); };
  }, []);

  function doScan() {
    if (!scan.trim()) return;
    // synth scanned lot
    const id = "LOT-2026-0429-" + (Math.floor(Math.random() * 900) + 100);
    const newLot = {
      id,
      scanned_code: scan.trim(),
      part: "10.08",
      part_name: "GM Bushing 22×28×30",
      supplier: "Athena Castings",
      qty: 1500,
      grn: new Date().toISOString().slice(0, 16).replace("T", " "),
      state: "awaiting",
      fresh: true,
    };
    setScanned(newLot);
    setLots(prev => [newLot, ...prev]);
    setActLog(prev => [{ ts: _a4_now(), msg: "Scanned " + scan.trim() + " → " + id }, ...prev].slice(0, 8));
    setScan("");
    setHighlight(id);
    if (_hlTimerRef.current) clearTimeout(_hlTimerRef.current);
    _hlTimerRef.current = setTimeout(() => setHighlight(null), 3000);
  }

  function actOn(lotId, action) {
    setLots(prev => prev.map(l => {
      if (l.id !== lotId) return l;
      if (action === "pass")     return { ...l, state: "passed",      note: "Passed to storage · 8/8 sample · Aryan M." };
      if (action === "hold")     return { ...l, state: "held",        note: "Held for full inspection" };
      if (action === "reject")   return { ...l, state: "rejected",    note: "Returned to supplier" };
      if (action === "ncr")      return { ...l, state: "quarantined", note: "NCR raised by Aryan M. · QC routing" };
      return l;
    }));
    setActLog(prev => [{ ts: _a4_now(), msg: action.toUpperCase() + " · " + lotId }, ...prev].slice(0, 8));
  }

  const reqs = [
    { p: "10.07", n: "Stator Tube 4\"",       need: 500,  have: 522, short: 0  },
    { p: "10.08", n: "GM Bushing top",        need: 500,  have: 1500, short: 0 },
    { p: "10.09", n: "GM Bushing mid-A",      need: 500,  have: 480, short: 20 },
    { p: "10.10", n: "GM Bushing mid-B",      need: 500,  have: 510, short: 0  },
    { p: "10.11", n: "GM Bushing bottom",     need: 500,  have: 470, short: 30 },
    { p: "10.04", n: "Rotor Shaft SS410",     need: 500,  have: 600, short: 0  },
    { p: "10.05", n: "Rotor Stamping",        need: 500,  have: 500, short: 0  },
    { p: "10.12", n: "Cu winding wire 1.0sq", need: 1500, have: 1480, short: 20 },
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      <_A4StationHeader
        operator={operator}
        station={stationName}
        seq="01–02 / 07"
        wo="WO-44219 · M-27418 · Helios AgriFlow · 500u"
        right={<span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>Shift A · {_a4_now()}</span>}
      />

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1.4fr 1fr", minHeight: 0 }}>

        <div style={{ display: "flex", flexDirection: "column", minHeight: 0, borderRight: "1px solid var(--line)" }}>
          {/* Scan strip */}
          <div style={{ padding: 14, borderBottom: "1px solid var(--line)", background: "var(--bg-1)" }}>
            <div className="label" style={{ marginBottom: 6 }}>SCAN INBOUND PART · BARCODE OR LOT TAG</div>
            <div style={{ display: "flex", gap: 6 }}>
              <div style={_A4_SCAN_INPUT_WRAP}>
                {I("Search", { size: 14 })}
                <input
                  id="recv-scan"
                  name="recv-scan"
                  aria-label="Scan inbound part barcode or lot tag"
                  placeholder="e.g. LOT-AC-2026-0429-X · GRN-04219 · or paste serial"
                  value={scan}
                  onChange={e => setScan(e.target.value)}
                  onKeyDown={e => { if (e.key === "Enter") doScan(); }}
                  style={_A4_SCAN_INPUT_STYLE}
                />
              </div>
              <Btn variant="primary" icon="Plus" onClick={doScan}>Capture</Btn>
            </div>
            {scanned && (
              <div style={_A4_SCAN_RESULT_STYLE}>
                <div><div className="label">LOT ID</div><span className="mono">{scanned.id}</span></div>
                <div><div className="label">PART</div>{scanned.part_name} <span className="mono" style={{ color: "var(--ink-4)" }}>({scanned.part})</span></div>
                <div><div className="label">SUPPLIER · QTY</div>{scanned.supplier} · <b className="mono">{scanned.qty}</b></div>
                <div><div className="label">GRN</div><span className="mono">{scanned.grn}</span></div>
              </div>
            )}
          </div>

          {/* Lots table */}
          <div style={{ flex: 1, overflow: "auto" }}>
            <div className="label" style={_A4_LOT_HEADER_STYLE}>
              <div>Lot ID</div>
              <div>Part</div>
              <div>Supplier</div>
              <div>Qty</div>
              <div>GRN</div>
              <div>State</div>
              <div>Action</div>
            </div>
            {lots.map(l => (
              <div key={l.id} style={_a4LotRowStyle(highlight === l.id, l.state)}>
                <div className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>{l.id}</div>
                <div>
                  <div style={{ color: "var(--ink)" }}>{l.part_name}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)" }}>{l.part}</div>
                </div>
                <div style={{ color: "var(--ink-2)" }}>{l.supplier}</div>
                <div className="mono tnum" style={{ color: "var(--ink-2)" }}>{l.qty}</div>
                <div className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{l.grn}</div>
                <div>
                  {l.state === "awaiting"     && <Pill tone="warn">awaiting</Pill>}
                  {l.state === "passed"       && <Pill tone="ok">passed</Pill>}
                  {l.state === "held"         && <Pill tone="warn">on hold</Pill>}
                  {l.state === "quarantined"  && <Pill tone="err">quarantine</Pill>}
                  {l.state === "rejected"     && <Pill tone="err">rejected</Pill>}
                </div>
                <div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
                  {l.state === "awaiting" && (
                    <>
                      <Btn size="sm" onClick={() => actOn(l.id, "pass")}>Pass</Btn>
                      <Btn size="sm" onClick={() => actOn(l.id, "hold")}>Hold</Btn>
                      <Btn size="sm" onClick={() => actOn(l.id, "reject")}>Reject</Btn>
                      <Btn size="sm" variant="primary" onClick={() => actOn(l.id, "ncr")}>NCR</Btn>
                    </>
                  )}
                  {l.state !== "awaiting" && (
                    <span style={{ fontSize: 12, color: "var(--ink-4)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{l.note}</span>
                  )}
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Right rail — WO requirements feed + activity */}
        <div style={{ display: "flex", flexDirection: "column", minHeight: 0 }}>
          <div style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)", background: "var(--bg-1)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
            <span className="label">WO-44219 · REQUIREMENTS</span>
            <Pill>live</Pill>
          </div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {reqs.map((r) => (
              <div key={r.p} style={_a4ReqRowStyle(r.short > 0)}>
                <div className="mono" style={{ fontSize: 12, color: "var(--ink-3)" }}>{r.p}</div>
                <div style={{ color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.n}</div>
                <div className="mono tnum" style={{ color: "var(--ink-3)" }}>{r.need}</div>
                <div className="mono tnum" style={{ color: r.short > 0 ? "var(--err)" : "var(--ink)" }}>{r.have}</div>
                {r.short > 0 && (
                  <div style={_A4_SHORT_NOTE_STYLE}>
                    <span>short by <b className="mono">{r.short}</b> · escalate to planning</span>
                    <Btn size="sm" onClick={() => window.forgeToast && window.forgeToast("Expedite queued — wired in v2")}>expedite</Btn>
                  </div>
                )}
              </div>
            ))}
            <div style={{ padding: "10px 12px", fontSize: 12, color: "var(--ink-4)", lineHeight: 1.5 }}>
              <div className="label" style={{ marginBottom: 4 }}>ACTIVITY · LAST 8 EVENTS</div>
              {actLog.length === 0 && <div>No activity captured yet this shift.</div>}
              {actLog.map((a) => (
                <div key={a.ts + "·" + a.msg} style={{ display: "flex", gap: 8, padding: "3px 0", borderBottom: "1px dashed var(--line-soft)" }}>
                  <span className="mono" style={{ color: "var(--ink-4)" }}>{a.ts}</span>
                  <span style={{ color: "var(--ink-2)" }}>{a.msg}</span>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

function _a4_now() {
  const t = new Date();
  const p = (n) => String(n).padStart(2, "0");
  return p(t.getHours()) + ":" + p(t.getMinutes()) + ":" + p(t.getSeconds());
}

/* Common station header */
function _A4StationHeader({ operator, station, seq, wo, right }) {
  return (
    <div style={{
      padding: "10px 14px",
      background: "var(--bg-2)",
      borderBottom: "1px solid var(--line)",
      display: "flex", alignItems: "center", gap: 14,
    }}>
      {_a4_avatar(operator, 32, "var(--accent)")}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13, color: "var(--ink)", display: "flex", alignItems: "center", gap: 8 }}>
          <b>{operator}</b>
          <span style={{ color: "var(--ink-4)" }}>·</span>
          <span style={{ color: "var(--ink-2)" }}>{station}</span>
          {seq && (
            <span className="mono" style={{ marginLeft: 6, padding: "1px 6px", border: "1px solid var(--line)", borderRadius: 3, fontSize: 12, color: "var(--ink-3)" }}>
              seq {seq}
            </span>
          )}
        </div>
        <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>{wo}</div>
      </div>
      {right}
    </div>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S28 — Station console: Machine Shop
   /traveler/machine-shop
   ──────────────────────────────────────────────────────────────────── */

function ScreenStationMachineShop() {
  const T = useA4Tenant() || {};
  const operator = "Rohit B.";
  const stationName = "Machine Shop (s4)";

  const _initialDims = [
    { id: "rotor",    label: "Rotor shaft Ø",  target: "18.00 +0/-0.02 mm",  spec: { lo: 17.98, hi: 18.00 }, captured: 17.98, instrument: "Mitutoyo digital caliper · 0.01mm", gate: "QG-01" },
    { id: "stator",   label: "Stator bore",    target: "120.00 +0.05/-0 mm", spec: { lo: 120.00, hi: 120.05 }, captured: 119.94, instrument: "Bore gauge 50-160 (set 04)",   gate: "QG-03" },
    { id: "impeller", label: "Impeller bore",  target: "12.00 ±0.01 mm",     spec: { lo: 11.99, hi: 12.01 }, captured: 12.005, instrument: "Bore gauge 10-25 (set 02)",      gate: "QG-04" },
    { id: "concent",  label: "Stamping concentricity (TIR)", target: "<0.04mm TIR", spec: { lo: 0, hi: 0.04 }, captured: 0.022, instrument: "V-block + DTI", gate: "QG-06" },
  ];

  const [serial, setSerial] = uSA4("TPX-H-0011");
  const [signed, setSigned] = uSA4(false);
  const [photoCount, setPhotoCount] = uSA4(0);
  const [dims, setDims] = uSA4(_initialDims);

  uEA4(() => {
    const onTenant = () => { setSerial("TPX-H-0011"); setSigned(false); setPhotoCount(0); setDims(_initialDims.map(d => ({ ...d }))); };
    window.addEventListener("forge:tenant-change", onTenant);
    return () => window.removeEventListener("forge:tenant-change", onTenant);
  }, []);

  function passOf(d) { return d.captured >= d.spec.lo && d.captured <= d.spec.hi; }
  const allPass = dims.every(passOf);

  function setCap(id, v) {
    setDims(prev => prev.map(d => d.id === id ? { ...d, captured: Number(v) || 0 } : d));
  }

  // Queue ahead
  const queue = [
    { serial: "TPX-H-0012", wait: "12 min" },
    { serial: "TPX-H-0013", wait: "25 min" },
    { serial: "TPX-H-0014", wait: "38 min" },
    { serial: "TPX-H-0015", wait: "53 min" },
    { serial: "TPX-H-0016", wait: "1h 08m" },
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      <_A4StationHeader
        operator={operator}
        station={stationName}
        seq="04 / 07"
        wo="WO-44219 · M-27418 · Helios AgriFlow · 500u"
        right={
          <span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>
            cycle target 22m · actual {dims.length === 0 ? "—" : "21.4m"}
          </span>
        }
      />

      {/* Traveler card */}
      <div style={{ padding: "10px 14px", background: "var(--bg-1)", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 14 }}>
        <div style={{
          padding: "6px 12px",
          border: "1px solid var(--accent)",
          borderRadius: 4,
          background: "color-mix(in oklch, var(--accent) 8%, var(--bg-0))",
        }}>
          <div className="label" style={{ color: "var(--accent)" }}>UNIT IN PROGRESS</div>
          <div className="mono" style={{ fontSize: 14, color: "var(--ink)", letterSpacing: "0.04em" }}>{serial}</div>
        </div>

        {/* progress dots */}
        <div style={{ display: "flex", alignItems: "center", gap: 10, flex: 1 }}>
          {["s1","s2","s3","s4","s5","s6","s7"].map((s, i) => {
            const active = i < 3;
            const here   = i === 3;
            return (
              <div key={s} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
                <div style={{
                  width: 18, height: 18, borderRadius: "50%",
                  background: active ? "var(--ok)" : here ? "var(--accent)" : "var(--bg-3)",
                  border: "1px solid " + (active ? "var(--ok)" : here ? "var(--accent)" : "var(--line)"),
                  boxShadow: here ? "0 0 0 4px color-mix(in oklch, var(--accent) 18%, transparent)" : "none",
                }} />
                <span className="mono" style={{ fontSize: 12, color: here ? "var(--ink)" : "var(--ink-4)", letterSpacing: "0.04em" }}>{s.toUpperCase()}</span>
              </div>
            );
          })}
        </div>

        <Btn icon="Diff" onClick={() => window.forgeToast && window.forgeToast("Prior unit view queued — wired in v2")}>View prior unit (TPX-H-0010)</Btn>
        <Btn icon="File" onClick={() => window.forgeToast && window.forgeToast("Work instruction opened — wired in v2")}>Open work instruction</Btn>
      </div>

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1.6fr 280px", minHeight: 0 }}>

        <div style={{ overflow: "auto", padding: 16 }}>

          <div className="label" style={{ marginBottom: 8 }}>DIMENSION CAPTURE · 4 CTQs</div>

          {dims.map(d => {
            const p = passOf(d);
            return (
              <div key={d.id} style={_a4DimRowStyle(p)}>
                <div>
                  <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{d.label}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>
                    {d.gate} · target <span style={{ color: "var(--ink-3)" }}>{d.target}</span>
                  </div>
                </div>
                <div>
                  <div className="label" style={{ marginBottom: 3 }}>captured</div>
                  <input
                    type="number"
                    step="0.001"
                    id={"mach-cap-" + d.id}
                    name={"mach-cap-" + d.id}
                    aria-label={"Captured " + d.label}
                    value={d.captured}
                    onChange={e => setCap(d.id, e.target.value)}
                    style={_a4DimCaptureStyle(p)}
                  />
                </div>
                <div>
                  <div className="label" style={{ marginBottom: 3 }}>units</div>
                  <span className="mono" style={{ color: "var(--ink-3)" }}>{d.id === "concent" ? "mm" : "mm"}</span>
                </div>
                <div>
                  <div className="label" style={{ marginBottom: 3 }}>instrument</div>
                  <span style={{ fontSize: 12, color: "var(--ink-2)" }}>{d.instrument}</span>
                </div>
                <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                  <_A4Stat pass={p} label={p ? "in tol" : "out of tol"} />
                  {!p && <span className="mono" style={{ fontSize: 12, color: "var(--err)" }}>re-machine</span>}
                </div>
              </div>
            );
          })}

          <div style={{
            display: "grid",
            gridTemplateColumns: "1fr 1fr",
            gap: 12, marginTop: 14,
          }}>
            {/* photo upload */}
            <div style={{ padding: 14, border: "1px dashed var(--line)", borderRadius: 4, background: "var(--bg-1)", textAlign: "center" }}>
              <div className="label" style={{ marginBottom: 6 }}>PHOTO CAPTURE</div>
              <div style={{ fontSize: 12, color: "var(--ink-3)", marginBottom: 8, lineHeight: 1.5 }}>
                Snap micrometer reading + finished surface.
                Auto-tags <span className="mono">{serial}</span> + station + timestamp.
              </div>
              <Btn icon="Plus" onClick={() => setPhotoCount(c => c + 1)}>+ Add photo</Btn>
              <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 6 }}>
                {photoCount} captured · {photoCount === 0 ? "0 of 2 required" : photoCount + " of 2 required"}
              </div>
            </div>
            {/* CMM dump */}
            <div style={{ padding: 14, border: "1px solid var(--line)", borderRadius: 4, background: "var(--bg-1)" }}>
              <div className="label" style={{ marginBottom: 6 }}>CMM DUMP · OPTIONAL</div>
              <textarea
                id="mach-cmm-dump"
                name="mach-cmm-dump"
                aria-label="CMM dump (optional)"
                placeholder={"# paste Mitutoyo Crysta-Apex S CMM dump here\nFEAT-01 Ø17.98  TIR 0.012\nFEAT-02 Ø119.94 TIR 0.018\n..."}
                style={_A4_TEXTAREA_STYLE}
              />
            </div>
          </div>

          {/* signature */}
          <div style={_A4_SIGNATURE_BAR_STYLE}>
            <div>
              <div className="label" style={{ marginBottom: 3 }}>OPERATOR SIGNATURE · ATTESTATION</div>
              <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
                I, <b style={{ color: "var(--ink-2)" }}>{operator}</b>, certify the captured measurements above.
              </div>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              {signed
                ? (
                  <>
                    <span className="mono" style={{ fontSize: 12, color: "var(--ok)" }}>✓ signed · {_a4_now()}</span>
                    <Btn onClick={() => setSigned(false)}>Reset</Btn>
                  </>
                )
                : <Btn variant="primary" onClick={() => setSigned(true)}>Sign with PIN</Btn>}
            </div>
          </div>

          {/* pass-out — Btn primitive ignores `disabled`; visually gate via wrapper + guard onClick. */}
          <div style={{ marginTop: 14, display: "flex", justifyContent: "flex-end", gap: 6 }}>
            <Btn onClick={() => window.forgeToast && window.forgeToast("Hold for QC queued — wired in v2")}>Hold for QC</Btn>
            <span style={{
              display: "inline-flex",
              opacity: (allPass && signed) ? 1 : 0.45,
              pointerEvents: (allPass && signed) ? "auto" : "none",
            }}>
              <Btn icon="Check" variant="primary"
                   onClick={() => { if (allPass && signed && typeof navigate === "function") navigate("/traveler/motor-assy"); }}>
                {allPass ? (signed ? "Pass to Storage / Motor Assy ▸" : "Sign to advance") : "Resolve out-of-tol first"}
              </Btn>
            </span>
          </div>
        </div>

        {/* right rail — queue */}
        <div style={{ borderLeft: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0, background: "var(--bg-1)" }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)" }}>QUEUE · NEXT 5 UNITS</div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {queue.map((q, i) => (
              <div key={q.serial} style={{
                padding: "10px 12px", borderBottom: "1px solid var(--line-soft)",
                display: "flex", justifyContent: "space-between", alignItems: "center",
              }}>
                <div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink)" }}>{q.serial}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>est wait {q.wait}</div>
                </div>
                <Btn size="sm" onClick={() => { setSerial(q.serial); }}>Load</Btn>
              </div>
            ))}
            <div style={{ padding: "10px 12px", fontSize: 12, color: "var(--ink-4)", lineHeight: 1.5 }}>
              <div className="label" style={{ marginBottom: 4 }}>STATION NOTES</div>
              Lathe-3 is the bottleneck for shaft turning. Tool life log is current.
              Bore gauge 50-160 set was re-zeroed at 06:42 against ring std.
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S29 — Station console: Motor Assembly + Winding
   /traveler/motor-assy
   ──────────────────────────────────────────────────────────────────── */

function ScreenStationMotorAssy() {
  // Hero-flow s5 console — list of in-progress + hold serials at this station,
  // disposition modal for rework/scrap/use-as-is, "advance" actions.
  const [tenant, mutate] = (window.useTenantState || function() { return [useActiveTenant(), function() {}]; })();
  const stationId = "s5";
  const station = ((tenant && tenant.routing) || []).find(function(r) { return r.id === stationId; }) || { name: "Motor Assembly + Winding", seq: 5 };
  const currentUser = (window.getCurrentUser && window.getCurrentUser()) || { name: "Neha G." };
  const operator = currentUser.name;
  const stationName = station.name + " (" + stationId + ")";
  const activeWo = "WO-44219";

  // Filter quality gates for this station.
  const gates = (tenant && tenant.quality_gates || []).filter(function(g) { return g.station === stationId; });

  // Serials in-progress / hold at this station for the active WO.
  const travelersHere = (tenant && tenant.travelers || []).filter(function(t) {
    return t.wo === activeWo && t.current_station === stationId;
  });
  const holdSerials = travelersHere.filter(function(t) { return t.status === "hold"; });
  const okSerials   = travelersHere.filter(function(t) { return t.status !== "hold"; });

  const [selSerial, setSelSerial] = uSA4(null);
  const [showDispo, setShowDispo] = uSA4(false);
  const [dispoPath, setDispoPath] = uSA4("rework");
  const [dispoReason, setDispoReason] = uSA4("re-grind shaft to 24.95mm ±0.01mm");
  const [reworkBusy, setReworkBusy] = uSA4(null);
  const [toast, setToast] = uSA4(null);

  // Active serial: hold > selected > first ok > first travelersHere.
  const activeSerial = uMA4(function() {
    if (selSerial) return travelersHere.find(function(t) { return t.serial === selSerial; });
    if (holdSerials.length) return holdSerials[0];
    return okSerials[0] || travelersHere[0];
  }, [selSerial, travelersHere.length, holdSerials.length]);

  // Capture state for the active serial's gates.
  const initialCaptures = uMA4(function() {
    const out = {};
    gates.forEach(function(g) { out[g.gate_id] = ""; });
    return out;
  }, [gates.length]);
  const [captures, setCaptures] = uSA4(initialCaptures);
  uEA4(function() { setCaptures(initialCaptures); }, [(activeSerial && activeSerial.serial) || "_"]);

  function logToast(msg) {
    setToast(msg);
    setTimeout(function() { setToast(null); }, 2400);
  }

  function openDispo(serial) {
    setSelSerial(serial);
    setDispoPath("rework");
    setDispoReason(activeSerial && activeSerial.hold_reason
      ? "re-grind shaft to 24.95mm ±0.01mm"
      : "");
    setShowDispo(true);
  }

  function saveDispo() {
    const t = travelersHere.find(function(x) { return x.serial === selSerial; });
    if (!t) return;
    // Wave 8E.1 — Server persistence first; the legacy traveler dispatcher
    // (server/index.js applyTravelerCheckin for Tritan, tenant has no SM)
    // accepts {station, status, hold_reason, dispo_path}. The SSE refetch
    // then reconciles the optimistic local mutation below.
    const serverStatus = dispoPath === "use-as-is" ? "ok"
      : dispoPath === "scrap" ? "scrapped"
      : dispoPath === "rework" ? "rework-in-progress"
      : "hold";
    if (window.forgeApi && window.forgeApi.traveler) {
      window.forgeApi.traveler.checkin(selSerial, {
        station: t.current_station || stationId,
        status: serverStatus,
        hold_reason: serverStatus === "hold" || serverStatus === "rework-in-progress" ? dispoReason : null,
        dispo_path: dispoPath,
        actor: currentUser.name,
      }).then(function() {
        logToast("Disposition logged · " + dispoPath + " · " + selSerial);
      }).catch(function(err) {
        const msg = (err && err.message) || "dispo persist failed";
        if (typeof console !== "undefined") console.warn("[motor-assy] dispo persist failed", err);
        logToast("error · " + msg);
      });
    } else {
      // No API available — UI-only path still surfaces feedback.
      logToast("Disposition logged · " + dispoPath + " · " + selSerial);
    }
    mutate(function() {
      // local optimistic update (mirror of server)
      t.status = "hold";
      t.hold_reason = dispoReason;
      t.dispo_path = dispoPath;
      t.checkins = t.checkins || [];
      t.checkins.push({
        station_id: stationId,
        operator_id: currentUser.id || currentUser.name,
        type: "disposition",
        dispo_path: dispoPath,
        reason: dispoReason,
        ts: Date.now(),
      });
      if (dispoPath === "rework") t.status = "rework-in-progress";
      if (dispoPath === "use-as-is") { t.status = "ok"; t.hold_reason = null; }
      if (dispoPath === "scrap") t.status = "scrapped";
    });
    setShowDispo(false);
  }

  function simulateReworkComplete(serial) {
    setReworkBusy(serial);
    // small synthetic delay so the user sees progress
    setTimeout(function() {
      // Rework complete → run a check-in that passes the station + advances.
      window.forgeApi.traveler.checkin(serial, {
        station_id: stationId,
        operator_id: currentUser.id || currentUser.name,
        dimensions: { rotor_shaft_dia: 24.95 },
        photos: ["/photos/" + serial + "-rework.jpg"],
        result: "pass",
        measurement_notes: "Rework complete · shaft re-ground to 24.95mm",
      }).then(function() {
        setReworkBusy(null);
        logToast("Rework complete · " + serial + " advanced to s6");
      });
    }, 700);
  }

  function passCurrent() {
    if (!activeSerial) return;
    // Validate at least 1 captured gate (lenient — demo).
    const filledCaptures = {};
    gates.forEach(function(g) {
      const v = captures[g.gate_id];
      if (v !== "" && v !== undefined && v !== null) {
        filledCaptures[g.gate_id] = v;
      }
    });
    window.forgeApi.traveler.checkin(activeSerial.serial, {
      station_id: stationId,
      operator_id: currentUser.id || currentUser.name,
      dimensions: filledCaptures,
      photos: [],
      result: "pass",
      measurement_notes: "Operator pass · all gates captured",
    }).then(function() {
      logToast(activeSerial.serial + " passed s5 → advanced to s6");
    }).catch(function(err) {
      const msg = (err && err.message) || "pass checkin failed";
      if (typeof console !== "undefined") console.warn("[motor-assy] pass checkin failed", err);
      logToast("error · " + msg);
    });
  }

  function holdCurrent() {
    if (!activeSerial) return;
    openDispo(activeSerial.serial);
  }

  // Gate-spec parsing — returns true if captured value is within numeric range parsed from spec string.
  function gateInTol(spec, captured) {
    if (captured === "" || captured === null || captured === undefined) return true;
    const n = Number(captured);
    if (Number.isNaN(n)) return true; // non-numeric specs (e.g. "No breakdown") — not validated client-side.
    // Try range patterns like "8.6Ω ±5%", "240 turns ±2", "1.8-2.2A @ 230V", "<0.04mm TIR", "≥40 LPM @ 22m", ">100 MΩ", "17.97-17.99mm".
    let m;
    if ((m = spec.match(/(-?\d+(?:\.\d+)?)\s*[-–]\s*(-?\d+(?:\.\d+)?)/))) {
      return n >= Number(m[1]) && n <= Number(m[2]);
    }
    if ((m = spec.match(/(-?\d+(?:\.\d+)?)\s*±\s*(\d+(?:\.\d+)?)\s*%/))) {
      const center = Number(m[1]);
      const pct = Number(m[2]) / 100;
      return n >= center * (1 - pct) && n <= center * (1 + pct);
    }
    if ((m = spec.match(/(-?\d+(?:\.\d+)?)\s*±\s*(-?\d+(?:\.\d+)?)/))) {
      const center = Number(m[1]);
      const delta  = Number(m[2]);
      return n >= center - delta && n <= center + delta;
    }
    if ((m = spec.match(/[<≤]\s*(-?\d+(?:\.\d+)?)/))) {
      return n <= Number(m[1]);
    }
    if ((m = spec.match(/[>≥]\s*(-?\d+(?:\.\d+)?)/))) {
      return n >= Number(m[1]);
    }
    return true;
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      <_A4StationHeader
        operator={operator}
        station={stationName}
        seq="05 / 07"
        wo="WO-44219 · M-27418 · Helios AgriFlow · 500u"
        right={
          <span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>
            queue {travelersHere.length} · hold {holdSerials.length}
          </span>
        }
      />

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "300px 1fr", minHeight: 0 }}>

        {/* Left rail — queue + hold list */}
        <div style={{ borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0, background: "var(--bg-1)" }}>

          {holdSerials.length > 0 && (
            <div style={{ padding: "8px 12px", borderBottom: "1px solid var(--err)", background: "color-mix(in oklch, var(--err) 8%, var(--bg-1))" }}>
              <div className="label" style={{ color: "var(--err)", marginBottom: 6 }}>HOLD · {holdSerials.length}</div>
              {holdSerials.map(function(t) {
                const isActive = activeSerial && activeSerial.serial === t.serial;
                return (
                  <div key={t.serial}
                       role="button" tabIndex={0}
                       onClick={function() { setSelSerial(t.serial); }}
                       onKeyDown={function(e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelSerial(t.serial); } }}
                       style={{
                         padding: "8px 10px", marginBottom: 4, borderRadius: 3,
                         background: isActive ? "color-mix(in oklch, var(--err) 18%, var(--bg-0))" : "var(--bg-0)",
                         border: "1px solid " + (isActive ? "var(--err)" : "var(--line)"),
                         cursor: "pointer",
                       }}>
                    <div className="mono" style={{ fontSize: 12, color: "var(--err)", fontWeight: 600 }}>{t.serial}</div>
                    <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 3, lineHeight: 1.4 }}>{t.hold_reason || "hold"}</div>
                    {!isActive && <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 4 }}>click to inspect ▸</div>}
                  </div>
                );
              })}
            </div>
          )}

          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)" }}>IN-PROGRESS AT S5 · {okSerials.length}</div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {okSerials.length === 0 && (
              <div style={{ padding: 12, fontSize: 12, color: "var(--ink-4)" }}>No serials currently at s5.</div>
            )}
            {okSerials.map(function(t) {
              const isActive = activeSerial && activeSerial.serial === t.serial;
              return (
                <div key={t.serial}
                     role="button" tabIndex={0}
                     onClick={function() { setSelSerial(t.serial); }}
                     onKeyDown={function(e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelSerial(t.serial); } }}
                     style={{
                       padding: "10px 12px", borderBottom: "1px solid var(--line-soft)",
                       cursor: "pointer", background: isActive ? "color-mix(in oklch, var(--accent) 10%, transparent)" : "transparent",
                     }}>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink)" }}>{t.serial}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 3 }}>
                    completed: {(t.completed_stations || []).join(" → ") || "—"}
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        {/* Right pane — active unit detail + gates capture */}
        <div style={{ display: "flex", flexDirection: "column", minHeight: 0, overflow: "auto" }}>
          {!activeSerial && (
            <div style={{ padding: 24, color: "var(--ink-4)", textAlign: "center" }}>Select a serial from the left.</div>
          )}
          {activeSerial && (
            <>
              {/* traveler band */}
              <div style={{ padding: "12px 16px", background: "var(--bg-1)", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 14 }}>
                <div style={{
                  padding: "6px 12px",
                  border: "1px solid " + (activeSerial.status === "hold" || activeSerial.status === "rework-in-progress" ? "var(--err)" : "var(--accent)"),
                  borderRadius: 4,
                  background: "color-mix(in oklch, " + (activeSerial.status === "hold" || activeSerial.status === "rework-in-progress" ? "var(--err)" : "var(--accent)") + " 8%, var(--bg-0))",
                }}>
                  <div className="label" style={{ color: activeSerial.status === "hold" || activeSerial.status === "rework-in-progress" ? "var(--err)" : "var(--accent)" }}>
                    {activeSerial.status === "hold" ? "ON HOLD" : activeSerial.status === "rework-in-progress" ? "REWORK IN PROGRESS" : "UNIT IN PROGRESS"}
                  </div>
                  <div className="mono" style={{ fontSize: 14, color: "var(--ink)", letterSpacing: "0.04em" }}>{activeSerial.serial}</div>
                </div>

                <div style={{ flex: 1, display: "flex", gap: 10, alignItems: "center" }}>
                  {["s1","s2","s3","s4","s5","s6","s7"].map(function(s, i) {
                    const done = (activeSerial.completed_stations || []).indexOf(s) >= 0;
                    const here = activeSerial.current_station === s;
                    return (
                      <div key={s} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
                        <div style={{
                          width: 18, height: 18, borderRadius: "50%",
                          background: done ? "var(--ok)" : here ? (activeSerial.status === "hold" ? "var(--err)" : "var(--accent)") : "var(--bg-3)",
                          border: "1px solid " + (done ? "var(--ok)" : here ? (activeSerial.status === "hold" ? "var(--err)" : "var(--accent)") : "var(--line)"),
                          boxShadow: here ? "0 0 0 4px color-mix(in oklch, " + (activeSerial.status === "hold" ? "var(--err)" : "var(--accent)") + " 18%, transparent)" : "none",
                        }} />
                        <span className="mono" style={{ fontSize: 12, color: here ? "var(--ink)" : "var(--ink-4)" }}>{s.toUpperCase()}</span>
                      </div>
                    );
                  })}
                </div>
              </div>

              {activeSerial.status === "hold" && (
                <div style={{ margin: 16, padding: 12, border: "1px solid var(--err)", borderRadius: 4, background: "color-mix(in oklch, var(--err) 6%, var(--bg-0))" }}>
                  <div className="label" style={{ color: "var(--err)", marginBottom: 6 }}>HOLD REASON</div>
                  <div style={{ fontSize: 13, color: "var(--ink)", marginBottom: 10, lineHeight: 1.5 }}>{activeSerial.hold_reason}</div>
                  <div style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                    <Btn variant="primary" icon="Edit" onClick={function() { openDispo(activeSerial.serial); }}>Log Disposition</Btn>
                  </div>
                </div>
              )}

              {activeSerial.status === "rework-in-progress" && (
                <div style={{ margin: 16, padding: 12, border: "1px solid var(--warn)", borderRadius: 4, background: "color-mix(in oklch, var(--warn) 8%, var(--bg-0))" }}>
                  <div className="label" style={{ color: "var(--warn)", marginBottom: 6 }}>REWORK IN PROGRESS</div>
                  <div style={{ fontSize: 13, color: "var(--ink)", marginBottom: 6, lineHeight: 1.5 }}>
                    Path: <b style={{ fontFamily: "var(--font-mono)" }}>{activeSerial.dispo_path || "rework"}</b>
                  </div>
                  <div style={{ fontSize: 12, color: "var(--ink-3)", marginBottom: 10, lineHeight: 1.5 }}>{activeSerial.hold_reason}</div>
                  <div style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                    <Btn
                      variant="primary"
                      icon="Check"
                      onClick={function() { simulateReworkComplete(activeSerial.serial); }}
                    >
                      {reworkBusy === activeSerial.serial ? "Reworking…" : "Simulate rework complete → advance to s6"}
                    </Btn>
                  </div>
                </div>
              )}

              {/* Quality gates for this station */}
              <div style={{ padding: 16 }}>
                <div className="label" style={{ marginBottom: 8 }}>QUALITY GATES · {gates.length} AT THIS STATION</div>
                {gates.length === 0 && <div style={{ fontSize: 12, color: "var(--ink-4)" }}>No gates registered at {stationId}.</div>}
                {gates.map(function(g) {
                  const captured = captures[g.gate_id];
                  const inTol = gateInTol(g.spec || "", captured);
                  return (
                    <div key={g.gate_id} style={{
                      padding: 12, marginBottom: 8,
                      border: "1px solid " + (captured !== "" && !inTol ? "var(--err)" : "var(--line)"),
                      borderRadius: 4,
                      background: captured !== "" && !inTol ? "color-mix(in oklch, var(--err) 6%, var(--bg-0))" : "var(--bg-0)",
                    }}>
                      <div style={{ display: "grid", gridTemplateColumns: "120px 1fr 1fr 1fr", gap: 12, alignItems: "center" }}>
                        <div>
                          <div className="mono" style={{ fontSize: 12, color: "var(--accent)", fontWeight: 600 }}>{g.gate_id}</div>
                          <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>{g.severity}</div>
                        </div>
                        <div>
                          <div style={{ fontSize: 13, color: "var(--ink)" }}>{g.ctq_param}</div>
                          <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 3 }}>{g.instrument} · {g.ref_doc}</div>
                        </div>
                        <div>
                          <div className="label">spec</div>
                          <span className="mono" style={{ fontSize: 12, color: "var(--ink-2)" }}>{g.spec}</span>
                        </div>
                        <div>
                          <div className="label">captured</div>
                          <input
                            id={"massy-cap-" + g.gate_id}
                            name={"massy-cap-" + g.gate_id}
                            aria-label={"Captured value for " + g.gate_id}
                            value={captured || ""}
                            onChange={function(e) {
                              const v = e.target.value;
                              setCaptures(function(prev) { const n = Object.assign({}, prev); n[g.gate_id] = v; return n; });
                            }}
                            placeholder={/turns|RPM|mm|Ω|MΩ|A |LPM|kN|N\b|cycles/i.test(g.spec) ? "0.000" : "pass/fail"}
                            style={{
                              width: "100%",
                              padding: "5px 8px",
                              border: "1px solid " + (captured !== "" && !inTol ? "var(--err)" : "var(--line)"),
                              background: "var(--bg-0)", color: "var(--ink)",
                              borderRadius: 3, fontSize: 13, fontFamily: "var(--font-mono)",
                            }}
                          />
                          {captured !== "" && !inTol && (
                            <div className="mono" style={{ fontSize: 12, color: "var(--err)", marginTop: 3 }}>OUT-OF-SPEC</div>
                          )}
                        </div>
                      </div>
                    </div>
                  );
                })}
              </div>

              {/* Sign + pass / hold */}
              {activeSerial.status !== "hold" && activeSerial.status !== "rework-in-progress" && (
                <div style={{ margin: "0 16px 16px 16px", padding: 12, border: "1px solid var(--line)", borderRadius: 4, background: "var(--bg-1)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                  <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
                    Operator <b style={{ color: "var(--ink-2)" }}>{operator}</b> · gates filled: <span className="mono">{Object.values(captures).filter(function(v) { return v !== ""; }).length}</span> / {gates.length}
                  </div>
                  <div style={{ display: "flex", gap: 6 }}>
                    <Btn onClick={holdCurrent}>Hold + dispo</Btn>
                    <Btn variant="primary" icon="Check" onClick={passCurrent}>Pass → s6</Btn>
                  </div>
                </div>
              )}
            </>
          )}
        </div>
      </div>

      {/* Disposition modal */}
      {showDispo && (
        <_A4Modal
          title={"LOG DISPOSITION · " + (activeSerial && activeSerial.serial)}
          body={
            <div>
              <div style={{ marginBottom: 10, fontSize: 12, color: "var(--ink-3)" }}>
                Decide what to do with this held unit. {activeSerial && activeSerial.hold_reason ? <span style={{ color: "var(--ink-2)" }}>Reason: {activeSerial.hold_reason}.</span> : null}
              </div>
              <div style={{ marginBottom: 10 }}>
                <div className="label" style={{ marginBottom: 4 }}>DISPOSITION PATH</div>
                {[
                  { id: "rework",     label: "Rework", desc: "Re-machine / re-process; unit re-enters at this station." },
                  { id: "scrap",      label: "Scrap",  desc: "Reject unit. Serial closes with status=scrapped." },
                  { id: "use-as-is",  label: "Use-as-is", desc: "Engineering concession; clear hold, advance with annotation." },
                ].map(function(opt) {
                  return (
                    <label key={opt.id} style={{ display: "flex", gap: 8, alignItems: "flex-start", padding: "6px 8px", border: "1px solid " + (dispoPath === opt.id ? "var(--accent)" : "var(--line)"), borderRadius: 3, marginBottom: 4, cursor: "pointer", background: dispoPath === opt.id ? "color-mix(in oklch, var(--accent) 8%, var(--bg-0))" : "var(--bg-0)" }}>
                      <input type="radio" name="dispo" value={opt.id} checked={dispoPath === opt.id} onChange={function() { setDispoPath(opt.id); }} style={{ marginTop: 3 }} />
                      <div>
                        <div style={{ fontSize: 13, color: "var(--ink)", fontWeight: 500 }}>{opt.label}</div>
                        <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 2 }}>{opt.desc}</div>
                      </div>
                    </label>
                  );
                })}
              </div>
              <div>
                <div className="label" style={{ marginBottom: 4 }}>REASON / INSTRUCTION</div>
                <textarea
                  id="dispo-reason"
                  name="dispo-reason"
                  aria-label="Disposition reason or instruction"
                  value={dispoReason}
                  onChange={function(e) { setDispoReason(e.target.value); }}
                  placeholder="e.g. re-grind shaft to 24.95mm ±0.01mm"
                  style={Object.assign({}, _A4_TEXTAREA_STYLE, { height: 64 })}
                />
              </div>
            </div>
          }
          onCancel={function() { setShowDispo(false); }}
          onConfirm={saveDispo}
          confirmLabel={"Save · " + dispoPath}
        />
      )}

      {toast && <div style={_A4_TOAST_STYLE}>{toast}</div>}
    </div>
  );
}

function _A4StarDiagram() {
  return (
    <svg viewBox="0 0 280 130" width="100%" height="130" style={{ background: "var(--bg-0)", border: "1px solid var(--line)", borderRadius: 4 }}>
      <defs>
        <marker id="ma-arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="5" markerHeight="5" orient="auto">
          <path d="M0,0 L10,5 L0,10 Z" fill="var(--ink-3)" />
        </marker>
      </defs>
      {/* center N point */}
      <circle cx="140" cy="65" r="4" fill="var(--ink)" />
      <text x="148" y="68" fontSize="10" fontFamily="var(--font-mono)" fill="var(--ink-2)">N</text>
      {/* three phases */}
      {[
        { ang: 270, color: "#c0392b", lbl: "R" },
        { ang: 30,  color: "#f1c40f", lbl: "Y" },
        { ang: 150, color: "#2980b9", lbl: "B" },
      ].map((ph) => {
        const rad = (ph.ang * Math.PI) / 180;
        const x = 140 + Math.cos(rad) * 50;
        const y = 65 + Math.sin(rad) * 45;
        const xt = 140 + Math.cos(rad) * 64;
        const yt = 65 + Math.sin(rad) * 58;
        return (
          <g key={ph.lbl}>
            <line x1="140" y1="65" x2={x} y2={y} stroke={ph.color} strokeWidth="2.4" markerEnd="url(#ma-arr)" />
            <circle cx={x} cy={y} r="6" fill={ph.color} stroke="var(--bg-0)" strokeWidth="1.5" />
            <text x={xt} y={yt + 4} fontSize="11" fontFamily="var(--font-mono)" textAnchor="middle" fill={ph.color} fontWeight="600">{ph.lbl}</text>
          </g>
        );
      })}
      <text x="14" y="118" fontSize="9.5" fontFamily="var(--font-mono)" fill="var(--ink-4)">
        STAR · 3-phase · phase voltage 230V · line voltage 400V · DOL start
      </text>
    </svg>
  );
}

/* ════════════════════════════════════════════════════════════════════
   S30 — Station console: No-load + Pressure Test
   /traveler/test
   ──────────────────────────────────────────────────────────────────── */

function ScreenStationTest() {
  const operator = "Neha G.";
  const stationName = "No-load + Pressure Test (s6)";
  const [serial] = uSA4("TPX-H-0008");
  const [generated, setGenerated] = uSA4(false);

  uEA4(() => {
    const onTenant = () => setGenerated(false);
    window.addEventListener("forge:tenant-change", onTenant);
    return () => window.removeEventListener("forge:tenant-change", onTenant);
  }, []);

  const tests = [
    { id: "T1", name: "Insulation Resistance",   instrument: "Megger MIT420 · 500V DC",        spec: "≥100 MΩ",          captured: "380 MΩ",       pass: true,  ts: "10:14:21" },
    { id: "T2", name: "HV Hipot · 1500V AC 1min", instrument: "Hipot tester 5kV · ramp 200V/s", spec: "no breakdown",     captured: "no breakdown · 0.42 mA leak", pass: true, ts: "10:16:08" },
    { id: "T3", name: "No-load current",         instrument: "Clamp meter true-RMS",           spec: "1.6 – 1.9 A",      captured: "1.78 A",       pass: true,  ts: "10:18:44" },
    { id: "T4", name: "Hydro pressure · 1.5×",   instrument: "Hydro rig · 0-20 bar",           spec: "no leak · hold 60s", captured: "0 seepage · 60s hold", pass: true, ts: "10:23:10" },
    { id: "T5", name: "Q-H @ duty (160 LPM, 22m)",instrument: "Performance test rig 4\"",       spec: "≥38% η",           captured: "38.6% η",      pass: true,  ts: "10:27:55" },
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>

      <_A4StationHeader
        operator={operator}
        station={stationName}
        seq="06 / 07"
        wo="WO-44219 · M-27418 · Helios AgriFlow · 500u"
        right={<span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>cycle target 18m · actual 17.8m</span>}
      />

      {/* arrival */}
      <div style={{ padding: "10px 14px", background: "var(--bg-1)", borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 14 }}>
        <div style={{ padding: "6px 12px", border: "1px solid var(--accent)", borderRadius: 4, background: "color-mix(in oklch, var(--accent) 8%, var(--bg-0))" }}>
          <div className="label" style={{ color: "var(--accent)" }}>UNIT ARRIVED FROM ASSY</div>
          <div className="mono" style={{ fontSize: 14, color: "var(--ink)", letterSpacing: "0.04em" }}>{serial}</div>
        </div>
        <div style={{ flex: 1, display: "flex", gap: 10 }}>
          {["s1","s2","s3","s4","s5","s6","s7"].map((s, i) => (
            <div key={s} style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 4 }}>
              <div style={_a4StationDotStyle(i)} />
              <span className="mono" style={{ fontSize: 12, color: i === 5 ? "var(--ink)" : "var(--ink-4)" }}>{s.toUpperCase()}</span>
            </div>
          ))}
        </div>
        <Btn icon="File" onClick={() => window.forgeToast && window.forgeToast("Test instructions opened — wired in v2")}>Test instructions · TI-T6-rA</Btn>
      </div>

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "1fr 1fr", minHeight: 0, overflow: "auto" }}>

        {/* Test sequence table */}
        <div style={{ padding: 16, borderRight: "1px solid var(--line)" }}>
          <div className="label" style={{ marginBottom: 8 }}>TEST SEQUENCE · 5 TESTS</div>
          {tests.map((t, i) => (
            <div key={t.id} style={{
              padding: 12, marginBottom: 8,
              border: "1px solid " + (t.pass ? "var(--line)" : "var(--err)"),
              borderRadius: 4,
              background: "var(--bg-0)",
            }}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 6 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={_a4TestNumStyle(t.pass)}>{i + 1}</span>
                  <span style={{ color: "var(--ink)", fontSize: 13, fontWeight: 500 }}>{t.name}</span>
                </div>
                <_A4Stat pass={t.pass} />
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 80px", gap: 8, fontSize: 12 }}>
                <div>
                  <div className="label">instrument</div>
                  <span style={{ color: "var(--ink-3)" }}>{t.instrument}</span>
                </div>
                <div>
                  <div className="label">spec</div>
                  <span className="mono" style={{ color: "var(--ink-2)" }}>{t.spec}</span>
                </div>
                <div>
                  <div className="label">captured</div>
                  <span className="mono" style={{ color: "var(--ink)", fontWeight: 600 }}>{t.captured}</span>
                </div>
                <div>
                  <div className="label">timestamp</div>
                  <span className="mono" style={{ color: "var(--ink-4)" }}>{t.ts}</span>
                </div>
              </div>
            </div>
          ))}

          <div style={_A4_PASS_BANNER_STYLE}>
            <span><b>5 / 5 tests passed.</b> Unit cleared for Pump Assembly + Final.</span>
            <span className="mono" style={{ fontSize: 12, color: "var(--ok)" }}>total {("17:34")}</span>
          </div>
        </div>

        {/* Q-H curve + report gen */}
        <div style={{ padding: 16, display: "flex", flexDirection: "column", minHeight: 0 }}>

          <div className="label" style={{ marginBottom: 8 }}>Q-H CURVE · TPX-H 4" 1.0HP STAGE-3</div>
          <_A4QHCurve />

          <div style={{ marginTop: 12, padding: 10, border: "1px solid var(--line)", borderRadius: 3, background: "var(--bg-1)" }}>
            <div className="label" style={{ marginBottom: 4 }}>OPERATING POINT @ DUTY</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12, fontSize: 12 }}>
              <div><div className="label">Q</div><span className="mono">160 LPM</span></div>
              <div><div className="label">H</div><span className="mono">22.0 m</span></div>
              <div><div className="label">η</div><span className="mono" style={{ color: "var(--ok)" }}>38.6%</span></div>
              <div><div className="label">P-shaft</div><span className="mono">748 W</span></div>
            </div>
          </div>

          <div className="label" style={{ marginTop: 14, marginBottom: 6 }}>TEST REPORT</div>
          <div style={_A4_REPORT_BAR_STYLE}>
            <div style={{ minWidth: 0 }}>
              <div className="mono" style={{ fontSize: 12, color: "var(--ink)" }}>TR-{serial}-2026-04-29.pdf</div>
              <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4 }}>
                Auto-bound to serial · 5 tests · Q-H curve · operator signature.
                Forge ledger entry pending.
              </div>
            </div>
            <Btn variant="primary" icon="File"
                 onClick={() => { setGenerated(true); if (typeof navigate === "function") navigate("/traveler/report"); }}>
              {generated ? "Report generated ▸" : "Generate signed report"}
            </Btn>
          </div>

          <div style={{ marginTop: "auto", paddingTop: 14, fontSize: 12, color: "var(--ink-4)", lineHeight: 1.5 }}>
            <div className="label" style={{ marginBottom: 4 }}>NOTES</div>
            Re-test policy: any single-test failure permits one re-run within 30 min.
            Two failures route to NCR-T-quarantine. Hipot ramp logs are stored alongside the PDF.
          </div>
        </div>
      </div>
    </div>
  );
}

function _A4QHCurve() {
  // Plot a synthetic Q-H curve with one captured point
  const W = 460, H = 220, padL = 38, padB = 28, padT = 14, padR = 10;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  // synthetic curve points (Q in LPM, H in m): pump at 1.0 HP / 4" / 3-stage
  const data = [
    { q: 0,   h: 32   },
    { q: 40,  h: 31.2 },
    { q: 80,  h: 29.4 },
    { q: 120, h: 26.5 },
    { q: 160, h: 22.0 },
    { q: 200, h: 16.2 },
    { q: 240, h: 9.0  },
  ];
  const Qmax = 260, Hmax = 35;
  const sx = q => padL + (q / Qmax) * innerW;
  const sy = h => padT + innerH - (h / Hmax) * innerH;
  const path = data.map((p, i) => (i === 0 ? "M" : "L") + sx(p.q) + "," + sy(p.h)).join(" ");
  const captured = { q: 160, h: 22 };

  return (
    <svg viewBox={"0 0 " + W + " " + H} width="100%" height={H} style={{ background: "var(--bg-0)", border: "1px solid var(--line)", borderRadius: 4 }}>
      {/* grid */}
      {[0, 5, 10, 15, 20, 25, 30, 35].map(h => (
        <g key={"h"+h}>
          <line x1={padL} y1={sy(h)} x2={W - padR} y2={sy(h)} stroke="var(--line-soft)" strokeWidth="0.5" />
          <text x={padL - 5} y={sy(h) + 4} fontSize="9" textAnchor="end" fontFamily="var(--font-mono)" fill="var(--ink-4)">{h}</text>
        </g>
      ))}
      {[0, 50, 100, 150, 200, 250].map(q => (
        <g key={"q"+q}>
          <line x1={sx(q)} y1={padT} x2={sx(q)} y2={H - padB} stroke="var(--line-soft)" strokeWidth="0.5" />
          <text x={sx(q)} y={H - padB + 12} fontSize="9" textAnchor="middle" fontFamily="var(--font-mono)" fill="var(--ink-4)">{q}</text>
        </g>
      ))}
      {/* axes labels */}
      <text x="6" y="14" fontSize="9.5" fontFamily="var(--font-mono)" fill="var(--ink-3)">H (m)</text>
      <text x={W - padR - 4} y={H - 4} fontSize="9.5" textAnchor="end" fontFamily="var(--font-mono)" fill="var(--ink-3)">Q (LPM)</text>

      {/* expected band (±5% on H) */}
      {data.length > 1 && (
        <path
          d={data.map((p, i) => (i === 0 ? "M" : "L") + sx(p.q) + "," + sy(p.h * 1.05)).join(" ") +
             " " + data.slice().reverse().map(p => "L" + sx(p.q) + "," + sy(p.h * 0.95)).join(" ") +
             " Z"}
          fill="color-mix(in oklch, var(--accent) 14%, transparent)"
          stroke="none"
        />
      )}
      {/* expected curve */}
      <path d={path} stroke="var(--accent)" strokeWidth="1.6" fill="none" />
      {/* captured point */}
      <line x1={sx(captured.q)} y1={sy(captured.h)} x2={sx(captured.q)} y2={H - padB} stroke="var(--ok)" strokeDasharray="2 3" />
      <line x1={padL} y1={sy(captured.h)} x2={sx(captured.q)} y2={sy(captured.h)} stroke="var(--ok)" strokeDasharray="2 3" />
      <circle cx={sx(captured.q)} cy={sy(captured.h)} r="5" fill="var(--ok)" stroke="var(--bg-0)" strokeWidth="1.5" />
      <text x={sx(captured.q) + 8} y={sy(captured.h) - 8} fontSize="10" fontFamily="var(--font-mono)" fill="var(--ok)" fontWeight="600">
        captured · 160 LPM @ 22m
      </text>

      {/* legend */}
      <g transform="translate(280, 22)">
        <rect x="0" y="-6" width="170" height="38" fill="var(--bg-0)" stroke="var(--line-soft)" rx="2" />
        <line x1="6" y1="6" x2="22" y2="6" stroke="var(--accent)" strokeWidth="1.6" />
        <text x="28" y="9" fontSize="9.5" fontFamily="var(--font-mono)" fill="var(--ink-3)">expected · 4" 1.0HP 3-stg</text>
        <circle cx="14" cy="22" r="4" fill="var(--ok)" />
        <text x="28" y="25" fontSize="9.5" fontFamily="var(--font-mono)" fill="var(--ink-3)">captured · pass</text>
      </g>
    </svg>
  );
}

/* ════════════════════════════════════════════════════════════════════
   Generic station console — fills s3 (Storage) and s7 (Pump Assy + Final).
   Same fixture-bound check-in pattern as the s5 hero console, slimmer UI.
   ──────────────────────────────────────────────────────────────────── */

function ScreenGenericStation(props) {
  const stationId = props.stationId;
  const [tenant, mutate] = (window.useTenantState || function() { return [useActiveTenant(), function() {}]; })();
  const station = ((tenant && tenant.routing) || []).find(function(r) { return r.id === stationId; }) || { name: stationId, seq: 0 };
  const currentUser = (window.getCurrentUser && window.getCurrentUser()) || { name: station.owner || "—" };
  const operator = currentUser.name;
  const stationName = station.name + " (" + stationId + ")";
  const activeWo = "WO-44219";

  const gates = (tenant && tenant.quality_gates || []).filter(function(g) { return g.station === stationId; });
  const travelersHere = (tenant && tenant.travelers || []).filter(function(t) {
    return t.wo === activeWo && t.current_station === stationId;
  });

  const [selSerial, setSelSerial] = uSA4(null);
  const activeSerial = uMA4(function() {
    if (selSerial) return travelersHere.find(function(t) { return t.serial === selSerial; });
    return travelersHere[0];
  }, [selSerial, travelersHere.length]);

  const initialCaptures = uMA4(function() {
    const out = {};
    gates.forEach(function(g) { out[g.gate_id] = ""; });
    return out;
  }, [gates.length]);
  const [captures, setCaptures] = uSA4(initialCaptures);
  uEA4(function() { setCaptures(initialCaptures); }, [(activeSerial && activeSerial.serial) || "_"]);

  const [toast, setToast] = uSA4(null);
  function logToast(msg) { setToast(msg); setTimeout(function() { setToast(null); }, 2400); }

  function passCurrent() {
    if (!activeSerial) return;
    const filledCaptures = {};
    gates.forEach(function(g) {
      const v = captures[g.gate_id];
      if (v !== "" && v !== undefined && v !== null) filledCaptures[g.gate_id] = v;
    });
    window.forgeApi.traveler.checkin(activeSerial.serial, {
      station_id: stationId,
      operator_id: currentUser.id || currentUser.name,
      dimensions: filledCaptures,
      photos: [],
      result: "pass",
      measurement_notes: "Pass at " + stationId,
    }).then(function() {
      logToast(activeSerial.serial + " passed " + stationId);
    });
  }
  function holdCurrent() {
    if (!activeSerial) return;
    window.forgeApi.traveler.hold(activeSerial.serial, "Held by operator at " + stationId, "rework").then(function() {
      logToast("Hold on " + activeSerial.serial);
    });
  }

  const isFinal = (((tenant && tenant.routing) || []).length > 0)
    ? station.seq === ((tenant && tenant.routing) || []).length
    : false;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }}>
      <_A4StationHeader
        operator={operator}
        station={stationName}
        seq={String(station.seq).padStart(2, "0") + " / 07"}
        wo="WO-44219 · M-27418 · Helios AgriFlow · 500u"
        right={<span className="mono" style={{ color: "var(--ink-3)", fontSize: 12 }}>queue {travelersHere.length}</span>}
      />

      <div style={{ flex: 1, display: "grid", gridTemplateColumns: "260px 1fr", minHeight: 0 }}>
        <div style={{ borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", minHeight: 0, background: "var(--bg-1)" }}>
          <div className="label" style={{ padding: "8px 12px", borderBottom: "1px solid var(--line)" }}>UNITS AT {stationId.toUpperCase()}</div>
          <div style={{ flex: 1, overflow: "auto" }}>
            {travelersHere.length === 0 && <div style={{ padding: 12, fontSize: 12, color: "var(--ink-4)" }}>No serials at this station yet.</div>}
            {travelersHere.map(function(t) {
              const isActive = activeSerial && activeSerial.serial === t.serial;
              return (
                <div key={t.serial}
                     role="button" tabIndex={0}
                     onClick={function() { setSelSerial(t.serial); }}
                     onKeyDown={function(e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelSerial(t.serial); } }}
                     style={{
                       padding: "10px 12px", borderBottom: "1px solid var(--line-soft)", cursor: "pointer",
                       background: isActive ? "color-mix(in oklch, var(--accent) 10%, transparent)" : "transparent",
                     }}>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink)" }}>{t.serial}</div>
                  <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 3 }}>
                    {t.status === "hold" ? "HOLD" : "ok"} · done: {(t.completed_stations || []).length}/7
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        <div style={{ overflow: "auto", padding: 16 }}>
          {!activeSerial && <div style={{ padding: 24, color: "var(--ink-4)", textAlign: "center" }}>Select a serial.</div>}
          {activeSerial && (
            <>
              <div style={{ padding: "12px 14px", background: "var(--bg-1)", border: "1px solid var(--line)", borderRadius: 4, marginBottom: 14, display: "flex", alignItems: "center", gap: 14 }}>
                <div style={{ padding: "6px 12px", border: "1px solid var(--accent)", borderRadius: 4, background: "color-mix(in oklch, var(--accent) 8%, var(--bg-0))" }}>
                  <div className="label" style={{ color: "var(--accent)" }}>ACTIVE SERIAL</div>
                  <div className="mono" style={{ fontSize: 14, color: "var(--ink)", letterSpacing: "0.04em" }}>{activeSerial.serial}</div>
                </div>
                <div style={{ flex: 1, fontSize: 12, color: "var(--ink-3)" }}>
                  Completed: <span className="mono" style={{ color: "var(--ink-2)" }}>{(activeSerial.completed_stations || []).join(" → ") || "—"}</span>
                </div>
                {isFinal && (activeSerial.completed_stations || []).length === 7 && (
                  <Btn variant="primary" icon="File" onClick={function() { if (typeof navigate === "function") navigate("/traveler/fat?serial=" + activeSerial.serial); }}>
                    Generate FAT certificate ▸
                  </Btn>
                )}
              </div>

              <div className="label" style={{ marginBottom: 8 }}>QUALITY GATES · {gates.length} AT THIS STATION</div>
              {gates.length === 0 && <div style={{ fontSize: 12, color: "var(--ink-4)", padding: 12 }}>No gates registered.</div>}
              {gates.map(function(g) {
                const captured = captures[g.gate_id];
                // Simple in-tol check
                const inTol = (function() {
                  if (captured === "" || captured === null || captured === undefined) return true;
                  const n = Number(captured);
                  if (Number.isNaN(n)) return true;
                  let m;
                  if ((m = (g.spec || "").match(/(-?\d+(?:\.\d+)?)\s*[-–]\s*(-?\d+(?:\.\d+)?)/))) {
                    return n >= Number(m[1]) && n <= Number(m[2]);
                  }
                  if ((m = (g.spec || "").match(/(-?\d+(?:\.\d+)?)\s*±\s*(\d+(?:\.\d+)?)\s*%/))) {
                    const c = Number(m[1]); const p = Number(m[2]) / 100;
                    return n >= c * (1 - p) && n <= c * (1 + p);
                  }
                  if ((m = (g.spec || "").match(/[<≤]\s*(-?\d+(?:\.\d+)?)/))) return n <= Number(m[1]);
                  if ((m = (g.spec || "").match(/[>≥]\s*(-?\d+(?:\.\d+)?)/))) return n >= Number(m[1]);
                  return true;
                })();

                return (
                  <div key={g.gate_id} style={{
                    padding: 10, marginBottom: 6,
                    border: "1px solid " + (captured !== "" && !inTol ? "var(--err)" : "var(--line)"),
                    borderRadius: 4,
                    background: captured !== "" && !inTol ? "color-mix(in oklch, var(--err) 6%, var(--bg-0))" : "var(--bg-0)",
                  }}>
                    <div style={{ display: "grid", gridTemplateColumns: "100px 1fr 1fr 1fr", gap: 12, alignItems: "center" }}>
                      <div>
                        <div className="mono" style={{ fontSize: 12, color: "var(--accent)" }}>{g.gate_id}</div>
                        <div className="mono" style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>{g.severity}</div>
                      </div>
                      <div>
                        <div style={{ fontSize: 13, color: "var(--ink)" }}>{g.ctq_param}</div>
                        <div style={{ fontSize: 12, color: "var(--ink-4)", marginTop: 2 }}>{g.instrument}</div>
                      </div>
                      <div>
                        <div className="label">spec</div>
                        <span className="mono" style={{ fontSize: 12, color: "var(--ink-2)" }}>{g.spec}</span>
                      </div>
                      <div>
                        <div className="label">captured</div>
                        <input
                          id={"fassy-cap-" + g.gate_id}
                          name={"fassy-cap-" + g.gate_id}
                          aria-label={"Captured value for " + g.gate_id}
                          value={captured || ""}
                          onChange={function(e) {
                            const v = e.target.value;
                            setCaptures(function(prev) { const n = Object.assign({}, prev); n[g.gate_id] = v; return n; });
                          }}
                          placeholder="value"
                          style={{
                            width: "100%", padding: "5px 8px",
                            border: "1px solid " + (captured !== "" && !inTol ? "var(--err)" : "var(--line)"),
                            background: "var(--bg-0)", color: "var(--ink)",
                            borderRadius: 3, fontSize: 13, fontFamily: "var(--font-mono)",
                          }}
                        />
                        {captured !== "" && !inTol && <div className="mono" style={{ fontSize: 12, color: "var(--err)", marginTop: 3 }}>OUT-OF-SPEC</div>}
                      </div>
                    </div>
                  </div>
                );
              })}

              <div style={{ marginTop: 14, padding: 12, border: "1px solid var(--line)", borderRadius: 4, background: "var(--bg-1)", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <div style={{ fontSize: 12, color: "var(--ink-3)" }}>
                  Operator <b style={{ color: "var(--ink-2)" }}>{operator}</b> · gates filled: <span className="mono">{Object.values(captures).filter(function(v) { return v !== ""; }).length}</span> / {gates.length}
                </div>
                <div style={{ display: "flex", gap: 6 }}>
                  <Btn onClick={holdCurrent}>Hold</Btn>
                  <Btn variant="primary" icon="Check" onClick={passCurrent}>
                    Pass{isFinal ? " (final)" : " → next"}
                  </Btn>
                </div>
              </div>
            </>
          )}
        </div>
      </div>

      {toast && <div style={_A4_TOAST_STYLE}>{toast}</div>}
    </div>
  );
}

function ScreenStationStorage()  { return React.createElement(ScreenGenericStation, { stationId: "s3" }); }
function ScreenStationFinalAssy(){ return React.createElement(ScreenGenericStation, { stationId: "s7" }); }

/* ════════════════════════════════════════════════════════════════════
   Route registration
   ──────────────────────────────────────────────────────────────────── */

if (typeof registerPumpRoute === "function") {
  registerPumpRoute({ path: "/build/routing",       mode: "build",    title: "Routing canvas",   renderer: () => React.createElement(ScreenRoutingCanvas) });
  registerPumpRoute({ path: "/build/gates",         mode: "build",    title: "Quality gates",    renderer: () => React.createElement(ScreenQualityGatesEditor) });
  registerPumpRoute({ path: "/build/control-plan",  mode: "build",    title: "Control plan",     renderer: () => React.createElement(ScreenControlPlan) });
  registerPumpRoute({ path: "/line/readiness",      mode: "line",     title: "Line readiness",   renderer: () => React.createElement(ScreenLineReadiness) });
  registerPumpRoute({ path: "/traveler/receiving",  mode: "traveler", title: "Receiving",        renderer: () => React.createElement(ScreenStationReceiving) });
  registerPumpRoute({ path: "/traveler/storage",    mode: "traveler", title: "Storage",          renderer: () => React.createElement(ScreenStationStorage) });
  registerPumpRoute({ path: "/traveler/machine-shop", mode: "traveler", title: "Machine shop",   renderer: () => React.createElement(ScreenStationMachineShop) });
  registerPumpRoute({ path: "/traveler/motor-assy", mode: "traveler", title: "Motor assembly",   renderer: () => React.createElement(ScreenStationMotorAssy) });
  registerPumpRoute({ path: "/traveler/test",       mode: "traveler", title: "Test station",    renderer: () => React.createElement(ScreenStationTest) });
  registerPumpRoute({ path: "/traveler/final-assy", mode: "traveler", title: "Pump assy + final", renderer: () => React.createElement(ScreenStationFinalAssy) });
}
