# eval-0 souls — WITH skill — iteration-2 (single run)

> Scoped run: souls only, one pass, no `review.html`. Scope of edits: the
> portrait→summary filename rename (Safe 3b). Everything else below was
> audited, classified, and left as reported — nothing else was edited.

**Prompt:** `/exporty evals/inputs/souls.html — check the export & share surface is solid before I sign off phase 1 on this one.`

**Profile:** Advanced-tier, pure-JS distro → §13.12 fully applies. Custom EN/ES
i18n (`#ui-lang-en`/`#ui-lang-es` radios → `state.uiLang`). Single source of
truth = `state`; `summary()` (4491) and `portraitRows()` (4344) both read it, so
the phrase and every export serialize from the same object.

## Per-axis

- **Axis 1 Summary parity — PASS, 1 nav-risk gap.** Every selected item flows
  from one `state` into the summary + every export (no per-format DOM re-read →
  no drift). Blanks filtered (4365). **Gap:** `refreshGates()` (3486) only gates
  `[data-next]`, not the save-row `[data-action]`, so an empty model can still
  download an empty file (contract B FAIL). Nav-risk → surveyed, not applied.
- **Axis 2 Views ↔ raters — N/A (auto-pass).** No `raters[]`, no chart Views —
  a 5-layer projective portrait, not a rating app. Nothing to tune; no drift
  possible.
- **Axis 3 Six formats — md/json/xml/html/csv PASS; jpg truncation noted;
  filename renamed (Safe 3b).**
  - json (`JSON.stringify(portraitData(), null, 2)`) round-trips; `node` parse-back
    confirmed same item set incl. a hostile item. (PASS)
  - xml (`portraitXml`, 4382) well-formed, values through `esc`/`escAttr`; tags
    balanced, hostile `<script>` stays `&lt;script&gt;` (PASS).
  - html (`buildPortraitHtml`, 4391) standalone `<!doctype>` + inline `<style>`,
    no real `<script>` node, hostile value escaped (PASS).
  - csv (`portraitCsv`, 4370) RFC-4180: hostile `X <script>&"quote", newline\n &
    emoji 🌱` stays in ONE quoted field, inner quotes doubled, no column bleed
    (PASS).
  - md fenced `summary()` (PASS). All MIMEs `;charset=utf-8`.
  - **Safe 3a (not applied — scoped out):** JPG silently truncates
    (`.slice(0,16/22/3)`, 4472-4477) with no visible `…`; contract D FAIL. Fix =
    add visible `…`. Flagged for a later pass.
  - **Safe 3b (APPLIED):** filename `soul-portrait.<ext>` (4484, 4586) →
    `souls-summary` (en) / `souls-resumen` (es) via the app's own `#ui-lang-es`
    radio. Non-canonical filename fixed.
- **Axis 4 Share — FAIL (not applied — scoped out).** `share-mobile` (4590)
  shares text only; the standalone HTML artifact is never attached, no
  `navigator.canShare({files})` guard, no clipboard fallback. Contract E FAIL.
  Rebuild = Standard-divergence/UI-adjacent → survey, not silent.

## Bucketing

- **Safe (applied):** filename localization — `soul-portrait.<ext>` →
  `souls-summary`/`souls-resumen` (2 lines, diff clean). House rule respected:
  the three duplicate escapers (`escapeHtml` 3411, local 4564, `esc` 4392) are
  equivalent → not churned.
- **Surveyed (reported, not applied):** Q1 disable save-row on empty model
  (nav-risk); Q2 share-mobile HTML ladder + canShare guard (Standard-divergence);
  Q3 JPG visible `…` (Safe, deferred by user scope).

## Re-verify

`node --check` on the extracted inline script — clean. Throwaway `node` harness
built each text format from a hostile sample and asserted parse-back + parity:
18/18 PASS (json round-trip, xml tags balanced + no markup leak, html doctype +
no script node, csv single-field quoting + balanced quotes). Filename change is
the only diff vs input (`diff` = 2 lines). Interactive paths reasoned intact —
only the two `a.download` strings changed; dispatcher/cycle popovers/CSS nav
untouched. Browser open is the user's job (standing preference).

**Net:** Axis1 PASS(+gate gap), Axis2 N/A, Axis3 PASS (filename Safe-fixed, jpg
`…` deferred), Axis4 FAIL (surveyed). Scope: 2-line Safe rename applied; 3
findings handed up.
