// Florio Analytics — Traits tab (real data via window.FlorioAPI / window.FlorioDB).
// Ports js/pages/traits.js to the new design kit. Consumes
// analytics_daily.{date}.traits rollups (from daily-aggregate.ts):
//   • Trait × archetype gap heatmap (byArchetype sum/count → avg delta)
//   • Gap-ranking table (byTraitValue → avg delta × scan count)
//   • Swap-rate cards (swapRate.overrideCount / total)
//   • Retake-rate cards (retakeRate.retakeCount / total)
//   • Inline avatar_catalog weight editor (Firestore read + setDoc merge)
(function () {
  const NS = window.HyperChartsDesignSystem_e51a95;
  const I = window.HyperIcons;
  const { useState, useEffect } = React;

  const rangeToDays = (r) => ({ Day: 1, Week: 7, Month: 30, Year: 365 }[r] || 30);

  // ── Constants (mirrors traits.js) ──
  const ARCHETYPES = [
    "flowering_dominant",
    "foliage_dominant",
    "structural_dominant",
    "trailing_hanging",
  ];

  const TRAIT_DIMS = [
    "leaf_shape",
    "leaf_size",
    "growth_habit",
    "stem_type",
    "silhouette",
    "texture",
    "size_class",
    "variegation",
    "biome",
    "spikiness",
    "flower_presence",
    "flower_color",
    "color",
  ];

  const ARCHETYPE_LABELS = {
    flowering_dominant: "Flowering",
    foliage_dominant: "Foliage",
    structural_dominant: "Structural",
    trailing_hanging: "Trailing",
  };

  const TRAIT_LABELS = {
    leaf_shape: "Leaf shape",
    leaf_size: "Leaf size",
    growth_habit: "Growth habit",
    stem_type: "Stem type",
    silhouette: "Silhouette",
    texture: "Texture",
    size_class: "Size class",
    variegation: "Variegation",
    biome: "Biome",
    spikiness: "Spikiness",
    flower_presence: "Has flower",
    flower_color: "Flower color",
    color: "Leaf color",
  };

  const MIN_SCAN_COUNT_FOR_GAP_TABLE = 10;

  const DISPLAY_NAMES = {
    monstera: "Monstera",
    palm: "Palm",
    bushy: "Bushy",
    peace: "Peace Lily",
    cactus: "Cactus",
    succulent: "Succulent",
    aloevera: "Aloe Vera",
    snake: "Snake Plant",
    fern: "Fern",
    hanging: "Hanging",
    orchid: "Orchid",
    anthurium: "Anthurium",
    bonsai: "Bonsai",
    banana: "Banana",
    dracaena: "Dracaena",
    ficus: "Ficus",
    prayer: "Prayer Plant",
    clover: "Clover",
  };

  const CATALOG_FIELDS = [
    { key: "growth_habit_affinity", label: "Growth habits", type: "csv", full: true },
    { key: "size_class_affinity", label: "Size classes", type: "csv" },
    { key: "leaf_shape_affinity", label: "Leaf shapes", type: "csv" },
    { key: "texture_affinity", label: "Textures", type: "csv" },
    { key: "silhouette_affinity", label: "Silhouettes", type: "csv" },
    { key: "biome_affinity", label: "Biomes", type: "csv" },
    { key: "stem_type_affinity", label: "Stem types", type: "csv" },
    { key: "variegation_affinity", label: "Variegation", type: "csv" },
    { key: "archetype_affinity", label: "Archetypes", type: "csv", full: true },
    { key: "flower_color_affinity", label: "Flower colors", type: "csv" },
    { key: "palette", label: "Palette (hex)", type: "csv", full: true },
    { key: "spikiness_range", label: "Spikiness [min, max]", type: "range" },
    { key: "prominent_feature", label: "Prominent feature", type: "string" },
    { key: "has_flowers", label: "Has flowers", type: "bool" },
    { key: "has_spines", label: "Has spines", type: "bool" },
  ];

  // 6 heatmap levels → color ramp (more gap = hotter).
  const HEAT_COLORS = [
    "var(--surface-sunken)",
    "color-mix(in srgb, #FE7E07 18%, var(--surface-sunken))",
    "color-mix(in srgb, #FE7E07 38%, var(--surface-sunken))",
    "color-mix(in srgb, #FE7E07 58%, var(--surface-sunken))",
    "color-mix(in srgb, #FE7E07 78%, var(--surface-sunken))",
    "#FE7E07",
  ];

  // ── Helpers ──
  function deltaToLevel(delta) {
    if (delta <= 0.05) return 0;
    if (delta <= 0.15) return 1;
    if (delta <= 0.3) return 2;
    if (delta <= 0.5) return 3;
    if (delta <= 0.7) return 4;
    return 5;
  }

  // Merge per-day `traits` rollups across the selected range. Sum/count pairs add.
  function mergeTraitRollups(dates, data) {
    const merged = {
      byArchetype: {},
      byTraitValue: {},
      swapRate: {},
      retakeRate: {},
      scanCount: {},
    };

    for (const date of dates) {
      const traits = data[date] && data[date].traits;
      if (!traits) continue;

      for (const [arch, dims] of Object.entries(traits.byArchetype || {})) {
        if (!merged.byArchetype[arch]) merged.byArchetype[arch] = {};
        for (const [dim, sc] of Object.entries(dims || {})) {
          if (!merged.byArchetype[arch][dim]) merged.byArchetype[arch][dim] = { sum: 0, count: 0 };
          merged.byArchetype[arch][dim].sum += sc.sum || 0;
          merged.byArchetype[arch][dim].count += sc.count || 0;
        }
      }

      for (const [arch, dims] of Object.entries(traits.byTraitValue || {})) {
        if (!merged.byTraitValue[arch]) merged.byTraitValue[arch] = {};
        for (const [dim, values] of Object.entries(dims || {})) {
          if (!merged.byTraitValue[arch][dim]) merged.byTraitValue[arch][dim] = {};
          for (const [val, sc] of Object.entries(values || {})) {
            if (!merged.byTraitValue[arch][dim][val]) merged.byTraitValue[arch][dim][val] = { sum: 0, count: 0 };
            merged.byTraitValue[arch][dim][val].sum += sc.sum || 0;
            merged.byTraitValue[arch][dim][val].count += sc.count || 0;
          }
        }
      }

      for (const [arch, sr] of Object.entries(traits.swapRate || {})) {
        if (!merged.swapRate[arch]) merged.swapRate[arch] = { overrideCount: 0, total: 0 };
        merged.swapRate[arch].overrideCount += sr.overrideCount || 0;
        merged.swapRate[arch].total += sr.total || 0;
      }

      for (const [arch, rr] of Object.entries(traits.retakeRate || {})) {
        if (!merged.retakeRate[arch]) merged.retakeRate[arch] = { retakeCount: 0, total: 0 };
        merged.retakeRate[arch].retakeCount += rr.retakeCount || 0;
        merged.retakeRate[arch].total += rr.total || 0;
      }

      for (const [arch, n] of Object.entries(traits.scanCount || {})) {
        merged.scanCount[arch] = (merged.scanCount[arch] || 0) + (n || 0);
      }
    }

    return merged;
  }

  function buildGapRows(merged) {
    const rows = [];
    for (const arch of ARCHETYPES) {
      const dims = merged.byTraitValue[arch] || {};
      for (const [dim, values] of Object.entries(dims)) {
        for (const [val, sc] of Object.entries(values)) {
          if (sc.count < MIN_SCAN_COUNT_FOR_GAP_TABLE) continue;
          const avg = sc.count > 0 ? sc.sum / sc.count : 0;
          rows.push({
            archetype: arch,
            dim,
            value: val,
            avgDelta: avg,
            scanCount: sc.count,
            gapScore: avg * sc.count,
          });
        }
      }
    }
    rows.sort((a, b) => b.gapScore - a.gapScore);
    return rows.slice(0, 50);
  }

  // ── Empty state ──
  function Empty({ note }) {
    return (
      <div style={{ minHeight: 120, display: "grid", placeItems: "center", textAlign: "center", gap: 8, padding: 22 }}>
        <span style={{ color: "var(--text-muted)", display: "flex" }}>{I.search ? I.search({ size: 24 }) : null}</span>
        <span style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)" }}>{note || "No data yet"}</span>
      </div>
    );
  }

  // ── Heatmap (Trait × Archetype gap) ──
  function Heatmap({ merged }) {
    const grid = `120px repeat(${ARCHETYPES.length}, 1fr)`;
    return (
      <div>
        <div style={{ display: "grid", gridTemplateColumns: grid, gap: 4 }}>
          <div />
          {ARCHETYPES.map((arch) => (
            <div key={arch} style={{ textAlign: "center", fontFamily: "var(--font-mono-display)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--text-muted)", paddingBottom: 4 }}>
              {ARCHETYPE_LABELS[arch]}
            </div>
          ))}
          {TRAIT_DIMS.map((dim) => (
            <React.Fragment key={dim}>
              <div style={{ display: "flex", alignItems: "center", fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-secondary)" }}>
                {TRAIT_LABELS[dim]}
              </div>
              {ARCHETYPES.map((arch) => {
                const entry = merged.byArchetype[arch] && merged.byArchetype[arch][dim];
                if (!entry || entry.count === 0) {
                  return <div key={arch} title="no data" style={{ height: 26, borderRadius: 6, background: "var(--surface-sunken)", opacity: 0.5 }} />;
                }
                const avg = entry.sum / entry.count;
                const lvl = deltaToLevel(avg);
                const tip = `Δ ${avg.toFixed(2)} · ${entry.count} scan${entry.count !== 1 ? "s" : ""}`;
                return <div key={arch} title={tip} style={{ height: 26, borderRadius: 6, background: HEAT_COLORS[lvl] }} />;
              })}
            </React.Fragment>
          ))}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 16, fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--text-muted)" }}>
          <span>Less gap</span>
          {HEAT_COLORS.map((c, i) => (
            <span key={i} style={{ width: 16, height: 16, borderRadius: 4, background: c, border: "1px solid var(--border-subtle)" }} />
          ))}
          <span>More gap</span>
        </div>
      </div>
    );
  }

  // ── Gap table ──
  function GapTable({ rows }) {
    if (!rows.length) {
      return <Empty note={`No trait values with ≥${MIN_SCAN_COUNT_FOR_GAP_TABLE} scans in this range yet.`} />;
    }
    const th = { textAlign: "left", fontFamily: "var(--font-mono-display)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--text-muted)", padding: "0 10px 10px", borderBottom: "1px solid var(--border-subtle)", whiteSpace: "nowrap" };
    const td = { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-secondary)", padding: "10px", borderBottom: "1px solid var(--border-subtle)", whiteSpace: "nowrap" };
    const tdNum = { ...td, fontFamily: "var(--font-mono)", textAlign: "right", color: "var(--text-primary)" };
    return (
      <div style={{ overflowX: "auto" }}>
        <table style={{ width: "100%", borderCollapse: "collapse" }}>
          <thead>
            <tr>
              <th style={th}>Archetype</th>
              <th style={th}>Trait</th>
              <th style={th}>Value</th>
              <th style={{ ...th, textAlign: "right" }}>Avg delta</th>
              <th style={{ ...th, textAlign: "right" }}>Scans</th>
              <th style={{ ...th, textAlign: "right" }}>Gap score</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r, i) => (
              <tr key={i}>
                <td style={{ ...td, color: "var(--text-primary)", fontWeight: 600 }}>{ARCHETYPE_LABELS[r.archetype] || r.archetype}</td>
                <td style={td}>{TRAIT_LABELS[r.dim] || r.dim}</td>
                <td style={td}>{r.value}</td>
                <td style={tdNum}>{r.avgDelta.toFixed(2)}</td>
                <td style={tdNum}>{r.scanCount}</td>
                <td style={{ ...tdNum, fontWeight: 700 }}>{r.gapScore.toFixed(2)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }

  // ── Swap / retake rate cards ──
  function RateGrid({ merged, kind }) {
    const isSwap = kind === "swap";
    const src = isSwap ? merged.swapRate : merged.retakeRate;
    const accent = isSwap ? "var(--app-accent)" : "var(--negative)";
    return (
      <div style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 12 }}>
        {ARCHETYPES.map((arch) => {
          const rec = src[arch] || (isSwap ? { overrideCount: 0, total: 0 } : { retakeCount: 0, total: 0 });
          const num = isSwap ? rec.overrideCount : rec.retakeCount;
          const pct = rec.total > 0 ? ((num / rec.total) * 100).toFixed(1) : "0.0";
          return (
            <div key={arch} style={{ background: "var(--surface-sunken)", border: "1px solid var(--border-subtle)", borderRadius: "var(--radius-md)", padding: 14, display: "flex", flexDirection: "column", gap: 6 }}>
              <span style={{ fontFamily: "var(--font-sans)", fontSize: 12, fontWeight: 600, color: "var(--text-secondary)" }}>{ARCHETYPE_LABELS[arch]}</span>
              <span style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 24, letterSpacing: "-.02em", color: accent, lineHeight: 1 }}>{pct}%</span>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--text-muted)" }}>{num} / {rec.total}</span>
            </div>
          );
        })}
      </div>
    );
  }

  // ── Inline avatar_catalog editor ──
  function escapeForInput(s) {
    return s == null ? "" : String(s);
  }

  function EditorCard({ id, data, onSave }) {
    const displayName = DISPLAY_NAMES[id] || id;
    const initial = {};
    for (const f of CATALOG_FIELDS) {
      const val = data[f.key];
      if (f.type === "csv") {
        initial[f.key] = Array.isArray(val) ? val.join(", ") : escapeForInput(val);
      } else if (f.type === "range") {
        initial[f.key] = Array.isArray(val) && val.length === 2 ? `${val[0]}, ${val[1]}` : "";
      } else if (f.type === "bool") {
        initial[f.key] = !!val;
      } else {
        initial[f.key] = escapeForInput(val);
      }
    }
    const [vals, setVals] = useState(initial);
    const [status, setStatus] = useState(null); // {kind:'ok'|'err'|'saving', text}

    const setField = (key, v) => setVals((prev) => ({ ...prev, [key]: v }));

    const handleSave = async () => {
      setStatus({ kind: "saving", text: "Saving…" });
      const update = {};
      for (const f of CATALOG_FIELDS) {
        const v = vals[f.key];
        if (f.type === "csv") {
          const raw = String(v || "").trim();
          if (raw === "") {
            update[f.key] = f.key === "flower_color_affinity" ? null : [];
          } else {
            update[f.key] = raw.split(",").map((s) => s.trim()).filter(Boolean);
          }
        } else if (f.type === "range") {
          const parts = String(v || "").split(",").map((s) => parseFloat(s.trim())).filter((n) => !Number.isNaN(n));
          if (parts.length === 2) update[f.key] = parts;
        } else if (f.type === "bool") {
          update[f.key] = !!v;
        } else {
          update[f.key] = String(v || "").trim();
        }
      }
      try {
        await onSave(id, update);
        setStatus({ kind: "ok", text: `Saved ${new Date().toLocaleTimeString()}` });
      } catch (e) {
        console.error("[Traits] save failed:", e);
        setStatus({ kind: "err", text: `Save failed: ${e.message}` });
      }
    };

    const statusColor = status
      ? (status.kind === "ok" ? "var(--app-accent)" : status.kind === "err" ? "var(--negative)" : "var(--text-muted)")
      : "var(--text-muted)";

    return (
      <div style={{ background: "var(--surface-sunken)", border: "1px solid var(--border-subtle)", borderRadius: "var(--radius-lg)", padding: 16, display: "flex", flexDirection: "column", gap: 14 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{ width: 44, height: 44, borderRadius: 12, flexShrink: 0, display: "grid", placeItems: "center", background: "color-mix(in srgb, var(--app-accent) 14%, transparent)", color: "var(--app-accent)", fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 18 }}>
            {(displayName[0] || "?").toUpperCase()}
          </div>
          <span style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 15, color: "var(--text-primary)" }}>{displayName}</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          {CATALOG_FIELDS.map((f) => {
            if (f.type === "bool") {
              return (
                <label key={f.key} style={{ gridColumn: "span 1", display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-sans)", fontSize: 12, color: "var(--text-secondary)", cursor: "pointer" }}>
                  <input type="checkbox" checked={!!vals[f.key]} onChange={(e) => setField(f.key, e.target.checked)} />
                  {f.label}
                </label>
              );
            }
            return (
              <div key={f.key} style={{ gridColumn: f.full ? "span 2" : "span 1", display: "flex", flexDirection: "column", gap: 4 }}>
                <label style={{ fontFamily: "var(--font-mono-display)", fontSize: 9.5, letterSpacing: ".06em", textTransform: "uppercase", color: "var(--text-muted)" }}>{f.label}</label>
                <input
                  type="text"
                  value={vals[f.key]}
                  placeholder={f.type === "range" ? "0.0, 0.3" : ""}
                  onChange={(e) => setField(f.key, e.target.value)}
                  style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--text-primary)", background: "var(--surface-card)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", padding: "7px 9px", outline: "none", width: "100%", boxSizing: "border-box" }}
                />
              </div>
            );
          })}
        </div>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: statusColor }}>{status ? status.text : ""}</span>
          <button
            onClick={handleSave}
            style={{ fontFamily: "var(--font-sans)", fontWeight: 700, fontSize: 13, color: "#fff", background: "var(--app-accent)", border: "none", borderRadius: "var(--radius-md)", padding: "8px 16px", cursor: "pointer" }}
          >
            Save
          </button>
        </div>
      </div>
    );
  }

  function Editor() {
    const [open, setOpen] = useState(false);
    const [state, setState] = useState({ loading: false, error: null, docs: null });

    const load = async () => {
      setState({ loading: true, error: null, docs: null });
      try {
        await window.FlorioReady;
        const { db, collection, getDocs } = window.FlorioDB;
        const snap = await getDocs(collection(db, "avatar_catalog"));
        const docs = [];
        snap.forEach((d) => docs.push({ id: d.id, data: d.data() }));
        docs.sort((a, b) => a.id.localeCompare(b.id));
        setState({ loading: false, error: null, docs });
      } catch (e) {
        console.error("[Traits] editor load failed:", e);
        setState({ loading: false, error: e.message, docs: null });
      }
    };

    const toggle = () => {
      const next = !open;
      setOpen(next);
      if (next && !state.docs && !state.loading) load();
    };

    const saveAvatar = async (id, update) => {
      await window.FlorioReady;
      const { db, doc, setDoc, serverTimestamp } = window.FlorioDB;
      const payload = { ...update, schemaVersion: 2, id, updatedAt: serverTimestamp() };
      await setDoc(doc(db, "avatar_catalog", id), payload, { merge: true });
    };

    return (
      <P.ChartCard
        title="Avatar weight editor"
        eyebrow="avatar_catalog · per-avatar trait affinities"
        span={12}
        action={
          <button
            onClick={toggle}
            style={{ fontFamily: "var(--font-sans)", fontWeight: 600, fontSize: 13, color: "var(--text-primary)", background: "var(--surface-card)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", padding: "8px 14px", cursor: "pointer" }}
          >
            {open ? "Hide editor" : "Open editor"}
          </button>
        }
      >
        {!open ? (
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--text-muted)" }}>
            Edit per-avatar trait affinities in avatar_catalog. Changes are written with schemaVersion 2 and an updatedAt timestamp.
          </div>
        ) : state.loading ? (
          <div style={{ display: "grid", placeItems: "center", minHeight: 120 }}>
            <div className="pa-spin" style={{ width: 32, height: 32, borderRadius: "50%", border: "3px solid var(--border-default)", borderTopColor: "var(--app-accent)" }} />
          </div>
        ) : state.error ? (
          <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--negative)" }}>Failed to load catalog: {state.error}</div>
        ) : !state.docs || state.docs.length === 0 ? (
          <Empty note="No avatars in catalog yet" />
        ) : (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))", gap: 16 }}>
            {state.docs.map((d) => (
              <EditorCard key={d.id} id={d.id} data={d.data} onSave={saveAvatar} />
            ))}
          </div>
        )}
      </P.ChartCard>
    );
  }

  // Pull P out lazily (window.PA defined by Widgets.jsx).
  let P;

  function Traits({ range }) {
    P = window.PA;
    const [d, setD] = useState(null);

    useEffect(() => {
      let alive = true;
      setD(null);
      (async () => {
        await window.FlorioReady;
        const API = window.FlorioAPI;
        const dates = (range && range.start && range.end)
          ? API.getDateRangeBetween(range.start, range.end)
          : API.getDateRange(rangeToDays(range && range.key ? range.key : range));

        let data = {};
        try {
          data = await API.fetchDailyAggregates(dates);
        } catch (e) {
          console.error("[Traits] fetchDailyAggregates failed:", e);
          data = {};
        }
        if (!alive) return;

        const merged = mergeTraitRollups(dates, data);
        const gapRows = buildGapRows(merged);

        const heatHasData = ARCHETYPES.some((a) =>
          TRAIT_DIMS.some((dim) => {
            const e = merged.byArchetype[a] && merged.byArchetype[a][dim];
            return e && e.count > 0;
          })
        );
        const swapHasData = ARCHETYPES.some((a) => merged.swapRate[a] && merged.swapRate[a].total > 0);
        const retakeHasData = ARCHETYPES.some((a) => merged.retakeRate[a] && merged.retakeRate[a].total > 0);

        setD({ merged, gapRows, dayCount: dates.length, heatHasData, swapHasData, retakeHasData });
      })();
      return () => { alive = false; };
    }, [range]);

    if (!d) {
      return (
        <div style={{ display: "grid", placeItems: "center", minHeight: "60vh" }}>
          <div className="pa-spin" style={{ width: 38, height: 38, borderRadius: "50%", border: "3px solid var(--border-default)", borderTopColor: "var(--app-accent)" }} />
        </div>
      );
    }

    return (
      <div style={{ display: "grid", gridTemplateColumns: "repeat(12, minmax(0,1fr))", gap: 20 }}>
        <P.ChartCard title="Trait gap heatmap" eyebrow={`Avg classifier delta · last ${d.dayCount} days`} span={7}>
          {d.heatHasData ? <Heatmap merged={d.merged} /> : <Empty note="No trait scans in range yet" />}
        </P.ChartCard>

        <P.ChartCard title="Override rate" eyebrow="Users who swapped the suggested avatar" span={5}>
          {d.swapHasData ? <RateGrid merged={d.merged} kind="swap" /> : <Empty note="No avatar selections in range" />}
        </P.ChartCard>

        <P.ChartCard title="Top trait gaps" eyebrow={`Avg delta × scans · ≥${MIN_SCAN_COUNT_FOR_GAP_TABLE} scans · top 50`} span={7}>
          <GapTable rows={d.gapRows} />
        </P.ChartCard>

        <P.ChartCard title="Retake rate" eyebrow="Users who retook the scan photo" span={5}>
          {d.retakeHasData ? <RateGrid merged={d.merged} kind="retake" /> : <Empty note="No retakes recorded in range" />}
        </P.ChartCard>

        <Editor />
      </div>
    );
  }

  (window.FlorioTabs = window.FlorioTabs || {}).traits = Traits;
})();
