<!-- CANONICAL per-item markup contract for /changes/ pages.
     Included by: generate-changes-page.md, apply.md. The widget
     parser (apps/widget/src/changes-parser.ts)
     reads EXACTLY these attribute names and classes — edit the parser and
     this file together or the in-page review flow dies silently. -->

### ⚠️ NORMATIVE — the per-item markup contract (every `.pp-change-item`, no exceptions)

Every reviewable item you emit on the `/changes/` page MUST follow this
exact shape. The widget parses it mechanically — attribute names and CSS
classes are load-bearing, not styling suggestions. Do not rename, omit, or
"improve" any of them; wrap them in your own additional styling classes if
you want the page to look different.

```html
<div class="pp-change-item"
     data-pp-item="{page-slug}:{slugified-title}"
     data-pp-title="{page-slug} · {N}. {Human-readable title}"
     data-pp-page-url="{root-relative URL of the ACTUAL page, e.g. / or /pricing — NOT this /changes/ URL}"
     data-pp-source-selector="{CSS selector uniquely resolving to the changed element in that page's source}"
     data-pp-applied-cr-id="{cr_id — ONLY when this item derives from an applied CR; omit otherwise}">
  <div class="change-num">{N}. {Section / field name}</div>
  <div class="change-row b"><div class="lbl">Before</div><div class="val">{old text}</div></div>
  <div class="change-row a"><div class="lbl">After</div><div class="val">{new text}</div></div>
</div>
```

Field-by-field, with what silently breaks when you get it wrong:

- **`data-pp-item`** — stable id, `{page-slug}:{slugified-title}` convention.
- **`data-pp-page-url`** — drives the in-page review chip ("N changes to
  review on this page"). The widget stores it as `target_page_url`;
  `pending-decisions` filters on it. **Omit it and the chip never appears —
  the entire in-page Approve / Reject / Request-change flow is dead even
  though items register fine.** Exact name: `data-pp-page` (without `-url`)
  is NOT read. Root-relative form only (`/`, `/pricing`,
  `/tr/features/asset-management/`), read from the page's frontmatter /
  route definition — never the `/changes/` URL itself.
- **`data-pp-source-selector`** — lets review mode frame the changed element
  on the source page. Must uniquely resolve to that one element in the
  page's source **as it exists right now** (source is read-only — never add
  ids/classes to make a selector). Preference order: existing unique anchor
  (`#id`, unique class, `[data-testid=…]`) > stable structural path
  (`main > section.hero > h1`) > `:nth-of-type(N)` counting. Standard CSS
  only — no `:contains()`.
- **`data-pp-applied-cr-id`** — when the item derives from a CR you just
  applied, set it to that `cr_id`. Without it, a reviewer's rejection
  spawns a DUPLICATE CR instead of transitioning the original. Items not
  derived from any CR carry no such attribute.
  **One CR, many pages → the SAME cr_id on every item.** When a single
  applied CR produced edits on multiple pages (typical case: a copy fix
  applied to a page AND its locale mirrors, e.g. `/journal/…` + `/tr/…`),
  each page gets its own item and EVERY one of them carries that same
  `data-pp-applied-cr-id`. Tagging only the "origin" page's item leaves
  the mirror items unlinked — their registrations survive the CR's
  deletion (the stale-item guard keys on this attribute) and reviewer
  decisions on them spawn duplicate CRs.
- **Visible CR number for CR-derived items** — when the item derives
  from an applied CR, lead its `.change-num` with the CR's short number:
  `#<seq_no> · {Section / field name}` (the apply flow knows each CR's
  `seq_no` from the inbox rows). This is the SAME number the admin
  dashboard and the in-page review panel show, so reviewers and admins
  can reference one identity everywhere. Items with no backing CR keep
  the plain ordinal (`{N}. {Section}`).
- **Before/after rows** — the texts MUST live in
  `.change-row.b .val` (before) and `.change-row.a .val` (after).
  The parser reads exactly those classes; home-grown alternatives
  (`pp-before`, `pp-after`, `before-text`, …) are NOT read, and the
  Before/After panes of the in-page overlay render empty — plus rejected
  items lose their revert snapshots.
- **Before/after CONTENT discipline** — what goes inside `.val` depends
  on what changed:
  - **Text change** → the LITERAL old and new text, verbatim, nothing
    else. Not `"Bizi Tanıyın — beyaz metin"` (text + style commentary
    mixed) — the literal `"Bizi Tanıyın"`. These values are the revert
    snapshot: on a rejection the AI swaps `.val`(before) back into
    source byte-for-byte, and commentary baked into the string makes
    that match fail.
    **Verbatim includes punctuation.** After you finish editing a source
    file, RE-READ the changed region from the file and paste THAT into
    `.val` — do not retype from memory. Never normalize typographic
    punctuation: if the source says `pool’s` (curly apostrophe) or `—`
    (em dash), the `.val` text carries the same characters, not `'` or
    `-`. A two-character quote drift is enough to break the green
    applied-highlight and make later revert prompts quote a text that
    isn't on the page.
  - **Visual/style/structural change** (color, size, background,
    reordering — no old/new text exists) → a short factual description
    is correct and expected (e.g. `Before: thin grid texture (SVG
    pattern)` / `After: plain gradient background`). Reverts for these
    items ride on the CR's own `before_text`/`after_text` event
    snapshots, not on `.val`.
  - **Both changed** (text AND styling) → `.val` carries ONLY the
    literal text; put the style delta in a separate plain line inside
    the item (outside `.change-row`), e.g.
    `<div class="note">Also: link color white → red.</div>`.
- **`data-pp-source-files`** (recommended) — comma-separated source-repo
  paths containing this element (e.g.
  `data-pp-source-files="src/pages/index.astro"`). The widget forwards
  it with decisions and the markdown export surfaces it, so the apply
  run can open the right file without grepping.

**Self-check before you ship the page** (do not skip):

1. Every `.pp-change-item` has `data-pp-page-url` (root-relative) and
   `data-pp-source-selector` (mentally apply it to the source file —
   exactly one match).
2. Every item's before/after texts sit inside `.change-row.b .val` /
   `.change-row.a .val`.
3. For every TEXT change, `.val` is the literal old/new text — no style
   commentary mixed into the string.
4. Every item derived from an applied CR carries its
   `data-pp-applied-cr-id` — including locale-mirror items, which carry
   the SAME cr_id as the origin page's item.
5. No source file was modified to make a selector work.
