Object.assign(App, {
  renderPreview: function() {
    this._renderAllSheets();
    if (this._rightTab === "selected") this._renderSelectedSheet();
    var tp = this.toolpaths.find(function(t) { return t.id === this.selectedId; });
    if (tp) this._updatePreviewToolWarning(tp);
  },

  // ── All Sheets pane ───────────────────────────────────────────────────────
  _renderAllSheets: function() {
    var wrap = document.getElementById("pane-all-sheets");
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);

    if (!this.nesting || !this.nesting.sheets || !this.nesting.sheets.length) {
      var hint = document.createElement("div");
      hint.id        = "preview-hint";
      hint.className = "preview-hint";
      hint.innerHTML = ggT("gcm_preview_hint_none");
      wrap.appendChild(hint);
      return;
    }

    var self  = this;
    var PAD   = 10;
    var MAX_H = 280;
    var MAX_W = 500;

    this.nesting.sheets.forEach(function(sheet, si) {
      var card = self._buildSheetCard(sheet, si, PAD, MAX_W, MAX_H, false);
      wrap.appendChild(card);
    });
  },

  // ── Selected Sheet pane — true vector zoom via SVG viewBox ───────────────
  // The SVG is rendered in mm-coordinate space (1 user unit = 1 mm).
  // Zoom/pan manipulates the viewBox — no pixel scaling, always crisp.
  // vector-effect="non-scaling-stroke" keeps stroke widths constant in pixels.
  _renderSelectedSheet: function() {
    if (this._zoomCleanup) { this._zoomCleanup(); this._zoomCleanup = null; }

    var wrap = document.getElementById("pane-selected-sheet");
    if (!wrap) return;
    while (wrap.firstChild) wrap.removeChild(wrap.firstChild);

    if (!this.nesting || !this.nesting.sheets || !this.nesting.sheets.length) {
      var hint = document.createElement("div");
      hint.className = "preview-hint";
      hint.textContent = ggT("gcm_preview_hint_none_plain");
      wrap.appendChild(hint);
      return;
    }

    var sheet = this.nesting.sheets[this.sheetIdx];
    if (!sheet) return;

    var self = this;
    var W = sheet.width_mm  || 1;
    var H = sheet.height_mm || 1;
    // mm padding around sheet in viewBox space
    var PAD_MM = Math.max(W, H) * 0.04;

    // y-flip: SVG y increases downward; our data y increases upward
    function tx(x) { return x; }
    function ty(y) { return H - y; }

    // ── Pane setup ──
    wrap.style.cssText = "position:relative; overflow:hidden; padding:0; display:block;" +
                         " cursor:grab; background:#6e6e6e; min-height:0;";

    // ── SVG fills pane ──
    var svg = svgEl("svg");
    svg.style.cssText  = "display:block;width:100%;height:100%;position:relative;";
    svg.setAttribute("preserveAspectRatio", "xMidYMid meet");

    // ViewBox state (in mm)
    var vbX = -PAD_MM, vbY = -PAD_MM;
    var vbW = W + 2 * PAD_MM, vbH = H + 2 * PAD_MM;
    var vbX0 = vbX, vbY0 = vbY, vbW0 = vbW, vbH0 = vbH;

    function setVB() {
      svg.setAttribute("viewBox",
        vbX.toFixed(3) + " " + vbY.toFixed(3) + " " +
        vbW.toFixed(3) + " " + vbH.toFixed(3));
    }
    setVB();

    // ── Sheet background ──
    var bg = svgEl("rect");
    bg.setAttribute("x", "0"); bg.setAttribute("y", "0");
    bg.setAttribute("width",  W); bg.setAttribute("height", H);
    bg.setAttribute("fill", "#fff");
    bg.setAttribute("stroke", "#333"); bg.setAttribute("stroke-width", "1.5");
    bg.setAttribute("vector-effect", "non-scaling-stroke");
    svg.appendChild(bg);

    // ── Dot grid (scales with zoom — shows more dots when zoomed in) ──
    var gridG = svgEl("g");
    gridG.setAttribute("opacity", "0.18");
    var step = Math.pow(10, Math.floor(Math.log10(Math.max(W, H) / 8)));
    for (var gx = 0; gx <= W + step * 0.01; gx += step) {
      for (var gy = 0; gy <= H + step * 0.01; gy += step) {
        var gd = svgEl("circle");
        gd.setAttribute("cx",   gx.toFixed(2));
        gd.setAttribute("cy",   ty(gy).toFixed(2));
        gd.setAttribute("r",    (step * 0.045).toFixed(3));
        gd.setAttribute("fill", "#333");
        gridG.appendChild(gd);
      }
    }
    svg.appendChild(gridG);

    // ── Drawing helpers (mm-space, non-scaling strokes) ──
    function drawPolyline(pts, color, sw, opacity, pathClass, close, tag) {
      if (!pts || pts.length < 2) return;
      var d = "M" + tx(pts[0][0]).toFixed(3) + "," + ty(pts[0][1]).toFixed(3);
      for (var i = 1; i < pts.length; i++) {
        d += " L" + tx(pts[i][0]).toFixed(3) + "," + ty(pts[i][1]).toFixed(3);
      }
      if (close) d += " Z";
      var path = svgEl("path");
      path.setAttribute("d", d);
      path.setAttribute("fill", "none");
      path.setAttribute("stroke", color);
      path.setAttribute("stroke-width", sw);
      path.setAttribute("opacity",      opacity);
      path.setAttribute("vector-effect", "non-scaling-stroke");
      if (pathClass) path.setAttribute("class", pathClass);
      if (tag) path.setAttribute("data-tag", tag);
      svg.appendChild(path);
    }

    function drawDrillToolCircle(op, tp, color, opacity, tag) {
      var pts = op.points;
      if (!pts || pts.length < 2) return;
      var xs = pts.map(function(p) { return p[0]; });
      var ys = pts.map(function(p) { return p[1]; });
      var minX = Math.min.apply(null, xs), maxX = Math.max.apply(null, xs);
      var minY = Math.min.apply(null, ys), maxY = Math.max.apply(null, ys);
      var cx = tx((minX + maxX) / 2);
      var cy = ty((minY + maxY) / 2);
      var diam = _resolveTpToolDiameter(tp, self);
      var Rout = diam / 2;
      var activeDrill = tp.id === self.selectedId;
      var swPrefMm = activeDrill ? 0.4 : 0.32;
      var g = _drillPreviewRingGeom(Rout, swPrefMm);
      var r = g.r;
      var swMm = g.sw;
      var c  = svgEl("circle");
      c.setAttribute("cx", cx.toFixed(3)); c.setAttribute("cy", cy.toFixed(3));
      c.setAttribute("r",  r.toFixed(3));
      c.setAttribute("fill", color + "25");
      c.setAttribute("stroke", color); c.setAttribute("stroke-width", swMm.toFixed(3));
      c.setAttribute("opacity", opacity);
      if (tag) c.setAttribute("data-tag", tag);
      svg.appendChild(c);
      var dot = svgEl("circle");
      dot.setAttribute("cx", cx.toFixed(3)); dot.setAttribute("cy", cy.toFixed(3));
      dot.setAttribute("r",  Math.min(Rout * 0.1, 1.2).toFixed(2));
      dot.setAttribute("fill", color);
      dot.setAttribute("opacity", opacity);
      if (tag) dot.setAttribute("data-tag", tag);
      svg.appendChild(dot);
      var arm   = Rout * 0.38;
      var cross = svgEl("path");
      cross.setAttribute("d",
        "M" + (cx - arm).toFixed(3) + "," + cy.toFixed(3) +
        " H" + (cx + arm).toFixed(3) +
        " M" + cx.toFixed(3) + "," + (cy - arm).toFixed(3) +
        " V" + (cy + arm).toFixed(3));
      cross.setAttribute("stroke", color);
      cross.setAttribute("stroke-width", Math.max(swMm * 0.65, 0.15).toFixed(3));
      cross.setAttribute("opacity", opacity);
      if (tag) cross.setAttribute("data-tag", tag);
      svg.appendChild(cross);
    }

    function drawOp(op, color, sw, opacity) {
      if (!op.points || op.points.length < 2) return;
      var tag = op.tag ? String(op.tag) : null;
      var layerClosed = !_useOpenProfilePolyline(op);
      drawPolyline(op.points, color, sw, opacity, null, layerClosed, tag);
    }

    var tagToTps = _buildTagToTps(self.toolpaths, self);

    (sheet.parts || []).forEach(function(part) {
      (part.operations || []).forEach(function(op) {
        drawOp(op, self._layerStrokeColor(op, null), "1", "0.5");
      });
    });
    var sheetMinDim = Math.min(W, H);
    var selMarkR = Math.max(2.5, sheetMinDim * 0.0033);
    var selArrowLen = Math.max(4, sheetMinDim * 0.0057);
    var selPocketArrowBase = Math.max(8, sheetMinDim * 0.011);
    var selPocketArrowLen = Math.max(1, selPocketArrowBase / 8);
    var selArrowWing = 0.5;
    (sheet.parts || []).forEach(function(part) {
      (part.operations || []).forEach(function(op) {
        var t   = op.tag != null ? String(op.tag).trim() : "";
        var tps = (t && tagToTps[t]) ? tagToTps[t] : [];
        tps.forEach(function(tp) {
          var active  = tp.id === self.selectedId;
          var opacity = active ? "1" : "0.82";
          var sw      = active ? "1.2" : "0.88";
          var c = self._toolpathPreviewStrokeColor;
          if (tp.type === "profile") {
            var pts = _profilePreviewPolyline(op, tp, self) || op.points;
            if (active && pts && pts.length >= 2) {
              var mg = svgEl("g");
              mg.setAttribute("class", "profile-feed-markers");
              mg.appendChild(_dot(tx(pts[0][0]), ty(pts[0][1]), selMarkR, "#2ecc71"));
              _arrowFromWorldPath(mg, pts, tx, ty, c, true, selArrowLen, selArrowWing, "1");
              svg.appendChild(mg);
            }
            drawPolyline(pts, c, sw, opacity, "profile-toolpath-stroke", !_useOpenProfilePolyline(op), t);
          } else if (tp.type === "drill") {
            drawDrillToolCircle(op, tp, c, opacity, t);
          } else if (tp.type === "pocket") {
            if (active) {
              var nestInfo = _pocketNestingForOp(op, tp, sheet);
              if (nestInfo.skip) return;   // island — milled as the parent's hole
              var holes = nestInfo.holes;
              var s = tp.settings || {};
              var diam = _resolveTpToolDiameter(tp, self);
              var toolR = diam / 2;
              var stepover = Math.max(parseFloat(s.stepover) || diam * 0.4, 0.5);
              var fillMode = s.pocket_fill || "raster";
              var arrowPts = null;
              var fillSw  = String(Math.max(parseFloat(sw) * 0.65, 0.7));
              var fillOpa = "0.65";
              if (fillMode === "offset") {
                var loops = _pocketOffsetLoops(op.points, toolR, stepover, holes);
                loops.forEach(function(loop) {
                  drawPolyline(loop.slice(0, -1), c, fillSw, fillOpa, null, true, t);
                });
                if (loops.length) arrowPts = loops[0].slice(0, -1);
              } else {
                var segs = _pocketRasterSegments(op.points, toolR, stepover, holes);
                if (segs.length > 0) {
                  var zigzag = [];
                  segs.forEach(function(seg) { zigzag.push(seg[0], seg[1]); });
                  drawPolyline(zigzag, c, fillSw, fillOpa, null, false, t);
                  arrowPts = zigzag;
                }
              }
              if (arrowPts && arrowPts.length >= 2) {
                var pmg = svgEl("g");
                pmg.setAttribute("class", "pocket-feed-markers");
                _arrowFromWorldPath(pmg, arrowPts, tx, ty, c, true, selPocketArrowLen, selArrowWing, "0.7");
                svg.appendChild(pmg);
              }
            }
          } else {
            drawPolyline(op.points, c, sw, opacity, "profile-toolpath-stroke", !!op.closed, t);
          }
        });
      });
    });

