# Widget vs origin KJ — parity report

**Date:** 2026-05-18
**Tested affiliate keys:**
- Lean (no chrome):       `-UuRmeYaPyIkg4KuWbhqKw` (`keep=""`)
- Full chrome (all on):   `fullchrome-test-key-001` (`keep="header,topbar,menu,footer,popups,chatter"`)
**Origin URL pattern:**   `http://localhost:8018/de/job-offers` (no query string)
**Widget URL pattern:**   `/api/widget/fragment?key=...&path=...&keep=...`

The same `wartungstechniker-44` job and the `/de/job-offers` listing were
compared via direct `curl` of the server-rendered HTML (no JS), plus the
live `<kj-embed>` panels in `static/test_kj_embed.html`.

## Page-by-page parity

| Page | Origin → Embed (server-rendered) |
|------|----------------------------------|
| `/de/job-offers` listing | **12** `.kj-job-card` per page in both. Pagination links `1..5` in both. |
| `/de/job-offers/<slug>-<id>` detail | **3** `Jetzt bewerben` buttons in both. `#wrapwrap`/`#top` header in both. |
| `/de/company-profiles` listing | **13** company cards in both. |

Apart from a ~3.6 kB size delta (the embed layout adds inline CSS for the
chrome-stripping rules + an explicit FA4 stylesheet link), the HTML bodies
match the origin **structurally** — same QWeb templates, same `kj-*`
classes, same data attributes.

## Visual cross-check (`<kj-embed>` panels)

### Full-chrome panel (`fullchrome-test-key-001`)

| Element                                    | Origin | Widget full-chrome |
|--------------------------------------------|:------:|:------------------:|
| Logo + red ribbon                          | ✅     | ✅                 |
| Language switcher (Deutsch / English UK)   | ✅     | ✅                 |
| `Anmelden` link                            | ✅     | ✅                 |
| Top nav (Stellenangebote, Firmenprofile,…) | ✅     | ✅                 |
| Hero `Stellenangebote Recht & Steuern`     | ✅     | ✅                 |
| Search button `Stelle finden`              | ✅     | ✅                 |
| `#jobFacetedFilters` placeholder           | ✅     | ✅ + widget chip-bar |
| `.kj-jobs-list` 12 cards/page              | ✅     | ✅                 |
| Pagination links 1..5                      | ✅     | ✅                 |
| `Karriere-Tipps` sidebar                   | ✅     | ✅                 |
| Footer                                     | ✅     | ✅                 |
| `Stelle veröffentlichen` CTA               | ✅     | ✅                 |

### Lean panel (`-UuRmeYaPyIkg4KuWbhqKw`, **by design**)

Same content, header/nav/footer/popups stripped via the `?embed=1&keep=`
mechanism (this is what we want — the affiliate uses their own site chrome
and just embeds the job board area). The widget's own dark left-nav
mirrors KJ's `website.menu` so nothing is unreachable.

## Behavioural differences (intentional)

| Behaviour                                | Origin OWL | Widget |
|------------------------------------------|:----------:|:------:|
| Faceted-filter chip-bar (mobile bottom sheet) | OWL     | Vanilla-JS dropdown row (`_enhanceFacets`) |
| Autocomplete dropdown                    | OWL + ES   | Vanilla-JS (`/api/widget/autocomplete`) |
| Apply form                               | OWL modal  | Shadow-DOM modal → `POST /api/widget/job/<id>/apply` |
| Auto-load-on-scroll (extra cards beyond page 1) | Yes  | No (pagination only) |
| Save-search / job-alert widgets          | OWL        | Hidden via embed CSS unless `keep=chatter` |
| Favorite stars on job cards              | OWL        | Hidden via embed CSS unless `keep=chatter` |
| Login/signup links                       | Same tab   | New tab (KJ session is meaningless on affiliate origin) |

## Recent polish (this session)

| Was                                       | Now |
|-------------------------------------------|-----|
| Toolbar showed `Jobs \| My Website`       | Now strips the `\| <site-name>` suffix → `Jobs` |
| `Loading…`, `Back`                        | Localized (`Wird geladen…`, `Zurück`) |
| Apply modal labels in English             | Full DE dictionary; EN fallback (`I18N` map in `kj_widget.js`) |
| ESC didn't close apply modal              | Bound `keydown` on overlay — closes on ESC |
| No focus trap                             | Tab/Shift+Tab cycle stays inside the dialog |
| Focus not restored on close               | Restores to whatever triggered the modal (WCAG 2.4.3) |
| `Submission failed.` / `Submitting…`      | Localized |

## What's *not* parity (and why)

1. **Per-page count = 12, not 50.** Origin OWL JS auto-loads more cards
   when the user scrolls; the server response is the same 12. The widget
   doesn't auto-load — pagination is exposed instead. Conscious trade-off:
   the JSON enhancement endpoint is sturdier across affiliate domains than
   reimplementing OWL's intersection-observer loader.
2. **Login / signup open a new tab.** A KJ session on the affiliate's
   origin is meaningless (third-party-cookie blocked, anyway), and login
   forms expect KJ chrome around them.
3. **Save-search / job-alert / favorite stars stay hidden** unless
   `keep=chatter` — affiliates don't want their visitors save-searching
   inside the embed; the configuration lever exists when they do.
