# Playbook: JST connectors (email-gated CAD)

**Read this for any JST part** (PHR-/XHP-/ZHR-/SHR-/EHR-/VHR-/PAP-/GHR-/NHR-
housings, or B\*B-/S\*B-/BM\*B-/SM\*B- board/SMT headers). JST is the
canonical example of a **manufacturer that emails you the CAD** instead of
serving it as a download. Proven end-to-end on PHR-2, 2026-06-27.

> One-line shape: native browser → jst-mfg.com download link → accept cookies
> → fill agreement form → JST **emails** the `.zip` → fetch the email
> attachment → import. There is **no account login** for CAD.

---

## 0. Why JST is special (don't skip)

- **jst.com (USA) is Cloudflare-walled against pup.** A pup navigation to any
  deep product page returns a "Just a moment… / Performing security
  verification" interstitial that never clears. **You MUST use the
  native-browser bridge** (`nbrowser_*`, the user's real signed-in Chrome via
  `adom-browser-extension`) — it carries human-trust and passes Cloudflare.
  If the bridge is absent, tell the user to install it
  (https://wiki.adom.inc/adom/adom-browser-extension); pup will not work here.
- jst.com is only marketing + spec tables + a **"Distributor Login"** (that
  login is for distributors, NOT a CAD gate). The actual CAD + catalogs live
  on the **global site jst-mfg.com**, which jst.com links to.
- **No login is required to get CAD.** The gate is a per-download **License
  Agreement form** whose submit triggers an **email**.

## 1. Resolve the part → series

JST CAD is organized by **series**, not by individual MPN. Each series has a
numeric id (PH = 199, PH high-box = 200, SH = 231, …; ~600+ series).

- Master index ("Series Alphabet Search"):
  `https://www.jst-mfg.com/product/index.php?type=5` — every series is an
  `index.php?series=<id>` link. This page is also your **scope list** for a
  batch (one row per series).
- Or keyword-search a known MPN: `https://www.jst-mfg.com/search/?q=<MPN>`
  (`sources::jst::search_url`). Strip ordering suffixes first with
  `sources::jst::base_part` — drop `(LF)(SN)` and any `(…)` option group, and
  a trailing suction-cap `T` on SMT headers
  (`B10B-PH-K-S(LF)(SN)` → `B10B-PH-K-S`).

## 2. Read the CAD columns on the series page

Open the series page in the native browser. Each part row has **Download**
buttons under format columns. Map them by reading the **column header** — the
`doc=` number is **per-series**, not universal:

| Common (PH, series=199) | doc= | format | inner file |
|---|---|---|---|
| IGES  | 1 | 3D    | `<PART>.IGS`  |
| STEP  | 2 | 3D    | `<PART>.STEP` ← **prefer this** |
| 3D-PDF| 3 | 3D pdf| `<PART>.pdf`  |
| 2D-PDF| 4 | drawing pdf | `<PART>.pdf` |

⚠️ **Coverage varies**: some older series offer **only IGES** (no STEP). If a
series has no STEP column, take IGES and convert with `step2glb`/service-kicad
downstream. Always read the header — never assume `doc=2` is STEP.

The link itself is built by `sources::jst::download_url(series, doc, filename)`:
`…/product/index.php?type=10&series=<S>&doc=<D>&filename=<PART>.zip`

## 3. The download link returns a FORM, not a file

GET-ing the download link does two things server-side:
1. binds `(series, doc, filename)` into the **PHP session**, and
2. returns a **License Agreement** HTML page (no file).

The page has required fields **company, section, name, address, tel, email**
(+ optional **url**) and an **"I agree"** button wired to a JS `download()`
function. `download()` collects every form value and POSTs them to
`ajax_download.php` (jsonp). The server reads the file to send from the
**session set in step 1** — so the GET and the POST **must share the browser
session** (same native-browser window = automatic).

**Accept cookies first.** The cookie banner literally says *"accept cookies in
order to submit any of our forms."* Click "Allow all cookies" before filling.

### Driving it (native browser)
1. `nbrowser_navigate` to the `download_url` → wait ~2.5s.
2. `nbrowser_eval`: accept cookies, set the 7 fields by id (dispatch
   `input`+`change`), then call `download()` (or click "I agree"). The success
   callback fires `alert("The data has been sent to your email address")` then
   navigates to `index.php?series=<S>` — hook `window.alert` into
   `sessionStorage` first since navigation wipes globals, then read it back.

Use the user's real business profile for the fields (see
`user_profile.md`). For a **batch**, set the email to a plus-alias like
`john+jst@adom.inc` with a Gmail filter/label so the inbox isn't buried (the
file still arrives; adom-google can still read it).

## 4. Retrieve the emailed file (the part most tools miss)

Since **April 2025** JST sends the CAD as an **email attachment** (`<PART>.zip`,
inner `<PART>.STEP`/`.IGS`), from a JST send-only address, ~30-60s later. This
is where chip-fetcher needs an **email-reading capability** —
see [`jst-email-retrieval.md`](jst-email-retrieval.md) for the Gmail
(adom-google) path and the **non-Gmail** build-your-own path.

Gmail quick path (adom-google):
```bash
adom-google api "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
  -q q="JST newer_than:1d" -q maxResults=5          # strip leading "HTTP 200…" line before JSON
adom-google gmail attachments <messageId>            # find <PART>.zip + its attId
adom-google gmail read <messageId> <attId> -o <PART>.zip
```
Pick the **newest** message whose inner file matches the format you requested
(IGES and STEP both arrive as `<PART>.zip` — distinguish by the inner
`.STEP` vs `.IGS`).

## 5. Datasheet = series catalog (DIRECT download, no gate)

JST has no per-part datasheet. Use the **series catalog PDF**, a plain direct
download: `sources::jst::catalog_pdf_url("PH")` →
`https://www.jst-mfg.com/product/pdf/eng/ePH.pdf`. Fetch with
`nbrowser_fetch_url` (returns base64 for binary — decode before writing) and
import as the part's `.pdf`. The 2D-PDF drawing (doc=4) is the per-part
mechanical print if you want it too.

## 6. Import + what JST does NOT give you

```bash
adom-chip-fetcher import <PART>.STEP --mpn <PART>
adom-chip-fetcher import <catalog>.pdf --mpn <PART>
```
JST supplies **3D (STEP/IGES) + drawings + catalog datasheet only**. It does
**NOT** supply KiCad `.kicad_sym` / `.kicad_mod`. Those come from the ladder.

## 6z. Series-ID table + full connector component-page pipeline (proven 2026-06-28)

**Series IDs** (jst-mfg.com `index.php?series=<id>`): PH **199**, XH **277**,
SH **231**, GH **105**, ZH **287**, VH **262**, SH-high-box etc. via the
alphabet search (`type=5`). Per series the 2-circuit set:

| Series | Board header | Wire housing | Crimp contact (PDF only, no STEP) | Pitch |
|---|---|---|---|---|
| PH | `B2B-PH-K-S` | `PHR-2` | `SPH-002T-P0.5S/L` (AWG 30-24) | 2.0 |
| XH | `B2B-XH-A` | `XHP-2` | `SXH-001T-P0.6N` (AWG 30-22) | 2.5 |
| SH | `SM02B-SRSS-TB` | `SHR-02V-S-B` | `SSH-003T-P0.2` (AWG 32-26) | 1.0 |
| GH | `BM02B-GHS-TBT` | `GHR-02V-S` | `SSHL-002T-P0.2` (AWG 32-28) | 1.25 |
| ZH | `B2B-ZR` | `ZHR-2` | `SZH-002T-P0.5` (AWG 32-26) | 1.5 |

**The component-page pipeline (the right way — JST 3D is always the part):**
1. **3D = JST manufacturer STEP** via the email flow (§3-4) for BOTH the board
   header and the wire housing. This is the whole point — never ship a KiCad 3D
   as the deliverable. (License: don't rehost the STEP; the viewer uses a lossy
   GLB preview + "run the skill" to fetch the real STEP.)
2. **Symbol + footprint = `ds2sf`** run on the JST datasheet
   (`adom-ds2sf extract library/<MPN> --source-tier mfr`). ds2sf **works on
   connectors** (2 pins, generic_connector_pin_function). Render its SVGs:
   `adom-ds2sf svg <dir>` (footprint) + `adom-symbol render-ds2sf --file
   <MPN>-symbol.extracted.json --out <MPN>-symbol.ds2sf.svg`.
3. **KiCad sym/fp/3D = the comparison foil only**, never the deliverable. Fetch
   the KiCad footprint/symbol (GitLab raw, §6a) + KiCad 3D (`service-kicad model
   fetch`). NOTE: KiCad has **no ZH series** (it ships ZE for 1.5 mm) — ZH is
   JST-only, footprint from ds2sf.
4. **chip-fetcher layout:** one project per mating system (`JST-<series>-system`)
   holding 3 cards — **wire housing**, **JST board** (ds2sf+KiCad sym/fp via the
   source ▾; JST "Mfr direct" 3D), and **`<MPN>-KICAD`** (KiCad-only sym/fp +
   KiCad 3D). Housings = JST 3D + PDF only (wire parts have no PCB footprint).
   Tag provenance on import: `--content-origin manufacturer --discovery-source
   jst` for JST files, `--content-origin kicad` for KiCad.
5. **Wiki component page** (`adom/<mpn-slug>`, type=component): the native 3D
   viewer's `model_3d` is a **merged comparison GLB** built with trimesh —
   KiCad model + JST model side by side, **each oriented per the user's
   chip-fetcher `chosen_up_axis` pick** (JST headers = yUp; KiCad per-part, can
   differ e.g. SH-KICAD=z), **a billboard label above each**, and **the
   footprint laid flat UNDER each model** (KiCad fp under the KiCad part, ds2sf
   fp under the JST part) so the pad layout is visible. Plus a static
   ds2sf-vs-KiCad sym/fp grid + the mating BOM table.

**GLB gotchas (critical):** step2glb emits **Draco-compressed** GLBs; the wiki
Babylon viewer **cannot fetch the Draco decoder** (CSP-blocked → "Failed to load
model"), so **decompress to plain GLB before publishing** (gltf-transform:
`mergeDocuments` + `unpartition` + dispose the `KHR_draco_mesh_compression`
extension; trimesh can't read Draco). trimesh bakes label textures **180°-rotated**
— pre-flop+flip the label PNGs. See discussion #18 on `adom/wiki` (decoder bug).

## 6a. Connectors: wire vs board, and the symbol/footprint source (proven 2026-06-27)

**Wire side vs board side.** A JST family has a wire-side crimp **housing**
(e.g. `PHR-2`) and a mating board-side **header** (e.g. `B2B-PH-K-S`). The
part that solders to the PCB — and the one a board design needs — is the
**board-side header**. If the user asks for "the connector for the board,"
they mean the header, not the housing. Housing-N mates the header-N (pin count
matches: `PHR-2` ↔ `B2B-PH-K-S`).

**Best symbol+footprint source for JST connectors = KiCad standard libraries**
(fast, no login — prefer over SnapMagic, which needs the user's password):

```bash
# Footprint (.kicad_mod) — KiCad footprints repo, Connector_JST.pretty
#   names look like JST_PH_B2B-PH-K_1x02_P2.00mm_Vertical
curl -s "https://gitlab.com/api/v4/projects/kicad%2Flibraries%2Fkicad-footprints/repository/files/Connector_JST.pretty%2F<FPNAME>.kicad_mod/raw?ref=master" -o <MPN>.kicad_mod
# Symbol — connectors use the GENERIC N-pin symbol (correct + standard).
#   kicad-symbols stores libs as DIRECTORIES: Connector_Generic.kicad_symdir/
curl -s "https://gitlab.com/api/v4/projects/kicad%2Flibraries%2Fkicad-symbols/repository/files/Connector_Generic.kicad_symdir%2FConn_01x0<N>.kicad_sym/raw?ref=master" -o <MPN>.kicad_sym
# Matched 3D STEP — read the (model ...) path from the .kicad_mod, then:
service-kicad model fetch "Connector_JST.3dshapes/<FPNAME>.step" --out <MPN>.step
```

**Gotchas (all hit on 2026-06-27):**
- KiCad ships **ZE (not ZH)** for the 1.5 mm series. ZH proper needs SnapMagic.
- `kicad-packages3D` only carries ~159 of the 500 footprints' models — **many
  series (VH, etc.) have NO KiCad 3D**. For those, get the STEP via the JST
  **email flow** (§3–4) and pair it with the KiCad footprint.
- Import connectors with **`--no-validate`**: the `gate`/validate step is
  chip-centric — it flags a connector's catalog PDF as "no pin-table, ds2sf
  can't extract a symbol" and marks the card incomplete. That's a false
  negative for connectors (the symbol is the generic N-pin one). A connector
  card with step+mod+sym+pdf IS complete; ignore that specific gate complaint.

Proven set (project `JST-common`): PH `B2B-PH-K-S`, XH `B2B-XH-A`,
SH `SM02B-SRSS-TB`, GH `BM02B-GHS-TBT`, VH `B2P-VH` (VH STEP via email).

## 7. Batch notes / hazards

- **The full catalog is ~600 series × tens of thousands of parts** = that many
  form submits + emails. Throttle hard (2-5s human-like delays, per critical
  rule #9) and expect JST to rate-limit. Do NOT blast the whole catalog without
  the user scoping it.
- One email per file, one file per `(series, doc, filename)` GET→POST. STEP and
  IGES are separate requests/emails.
- Heartbeat each stage: `adom-chip-fetcher heartbeat <PART> "probing jst-mfg.com"`
  / `"requested STEP — awaiting email"` / `"imported"`.

### 7a. Inbox hygiene — DEDUP before every request (mandatory)

Every JST request emails the user a file. Re-requesting spams their inbox.
Before submitting the agreement form for any `(part, format)`:

1. **Skip if already in the library.** If `library/<MPN>/<MPN>.step` exists (or
   the format you want), do NOT request it again. Check first, every time.
2. **STEP only by default.** Request `doc=2` (STEP). Only fall back to `doc=1`
   (IGES) when the series has no STEP column. Never grab both "to be safe."
3. **One submit per file, ever.** Do not re-submit a part you already requested
   this session (track what you've sent). A double-click / retry = a second
   email. The form's success alert is your confirmation — don't resubmit to
   "make sure."
4. **Route to a filtered alias, not the raw inbox.** Use the user's plus-alias
   (e.g. `you+jst@domain.com`) with a label/filter so the batch doesn't bury
   their inbox. See [`jst-email-retrieval.md`](jst-email-retrieval.md).
5. **Clean up after a proof/demo.** Test/demo runs that submit the form generate
   real emails — trash the duplicates afterward
   (`adom-google api -X POST .../messages/<id>/trash`).

Skipping dedup is how a 5-part batch becomes a 12-email inbox flood (learned the
hard way 2026-06-27: 6 emails for 3 unique parts due to IGES+STEP grabs, a
demo re-submit, and a double-submit).

## Source plugin

`src/sources/jst.rs` — `series_index_url`, `series_url`, `search_url`,
`download_url`, `catalog_pdf_url`, `DocKind`, `FORM_FIELDS`, `base_part`.


## Building animated 3D mating component pages (wiki components)

This is the high-value deliverable: a wiki **component page** whose 3D viewer shows
the connector **assembling itself in a looping animation** — board sliding into the
PCB (THT pins passing through to the underside), then the wire housing mating into
the board. KiCad sits beside it as a comparison foil. Learned end-to-end 2026-06-28.

### Scene recipe (per system)
- **Columns:** left = KiCad library model (foil), right = JST manufacturer model.
  If KiCad has **no 3D model** for the part (e.g. ZH `B2B-ZR` → `kicad-packages3d`
  404s), make it a **single JST column** and say so on the page; KiCad sym/fp still
  compare below.
- **Colour = source:** JST renders blue, KiCad renders tan/grey. Plus an **opaque
  nameplate** per column ("JST manufacturer" / "KiCad library"). Users WANT to know
  which model is which — never drop the labels.
- **PCB:** a thin **floating** slab (≈1.6 mm), footprint texture on top, **no riser
  block underneath** — a riser hides the pin tips poking through, which is the whole
  point. The footprint pads sit a few mm above the viewer's ground plane.
- **THT vs SMT seating:** THT parts (`B2B-*` PH/XH/ZH) seat with **pins through the
  PCB, protruding ≈2 mm out the bottom** (the money shot — user checks pins line up
  with holes). SMT parts (`SM02B/BM02B` SH/GH) rest tabs on the pad surface (no
  through-pins — say so).

### Mate orientation (which way the wire slides in)
Encoded in the JST part number AND drawn in the datasheet:
- `B…` prefix (B2B, BM) = **top entry** → wire mates **straight down (+Z)**.
- `S…` prefix (SM, S2B) = **side entry / right-angle** → wire mates **horizontally (+Y)**.
Confirm per part. Animate the wire along that axis.

### Getting each model's orientation right
STEP frames are inconsistent. Don't guess from bbox (connectors are ~cubic). Use:
- `step2glb features <step>` → floor_z / longest-axis hints (treat as hints, verify).
- `step2glb thumbnail <step|glb> --pose front|top|side` → read the pin axis directly.
- KiCad models are reliably **Z-up, pins -Z** (identity). JST THT boards here needed
  **R_x(+90)** (pins along native -Y); each JST wire housing differs — check its orthos.
- Seat board so `bbox.min_z` (pin tip) = `PCB_bottom - protrude` (THT) or `PCB_top` (SMT).

### Authoring the animation (GLB node channels)
- Build the scene in **trimesh**, giving the board/wire their own **named nodes**
  (`add_geometry(..., node_name="board1")`), geometry baked at the **seated** pose so
  node base-translation = 0. (If a viewer ignores animation, it still looks correct.)
- Add animation with **@gltf-transform** (`createAnimationSampler` / `createAnimationChannel`,
  target path `translation`, LINEAR). Each node lerps from a small **retract offset**
  back to 0 (seated). Keep lifts **small** (~board: clear the PCB top + ~2.5 mm; wire:
  ~its height + 4 mm) — big lifts waste the loop and bury the money shot.
- Timeline: board drops, then wire mates, then **dwell long on the fully-mated state**,
  quick reset, loop. The wiki 3D viewer (Babylon) **autoplays + loops** embedded GLB
  animations — no play button needed.

### CRITICAL gotchas
- **Viewer grounds to the model's lowest geometry.** So a footprint quad that is the
  lowest thing sits coplanar with the ground plane → **z-fighting**. Fix by giving the
  PCB real geometry and letting **pins** be the lowest element (also what we want).
- **The 3D viewer is an `<iframe>`** (`/viewer/3d/component/<slug>`). Reloading the
  parent page does **not** refresh the iframe's cached GLB. To see an update: bump the
  GLB **filename** (`-v5.glb`) and/or `iframe.src = src.split('?')[0]+'?cb=…'`. Burned
  ~4 republishes chasing "my fix isn't showing" — it was always iframe cache.
- **Transparent label PNGs bleed through** a back-to-back double-sided sign. Composite
  the label onto an **opaque** background before texturing; put the normal image on the
  front quad and a `ImageOps.mirror` copy on the back quad, separated ~0.4 mm.
- **STEP→GLB emits Draco-compressed** GLBs the merge tools can't read — decompress with
  gltf-transform (`unpartition` + dispose `KHR_draco_mesh_compression`) first.

### Pipeline & publish
`step2glb convert` (JST STEP + KiCad STEP) → gltf-transform decompress → trimesh merge
(`merge_mate.py`: pedestal + seat + nameplate, emits `<out>.anim.json`) → `add_anim.mjs`
→ component repo (`parts.model_3d`) → `adompkg publish --org adom` (components are
org-owned; publishing as your user 409s). ZH note: `service-kicad fp fetch Connector_JST
JST_ZH_B2B-ZR_1x02_P1.50mm_Vertical` gives the footprint even though no 3D model exists.


### Refinements learned the hard way (2026-06-28, v0.8.9)
- **bbox-check every object; never eyeball the math.** The merge prints each part's
  bbox and asserts: connector **body_bottom == PCB_top** (housing sits ON the PCB, not
  sunk into it), THT **pin_tip < PCB_bottom** (pins truly protrude), wire stays above
  the PCB. Detect the **body bottom** (not bbox-min, which is the pin tips) by walking z
  slices and finding where the cross-section widens to the body — seat THAT on the PCB.
  Seating by bbox-min sinks the housing into the board; this was a real bug.
- **Insert the wire DEEP — ~80% of the board-body depth**, not just touching. The user
  needs to see it truly seat and see JST's contact-lock slots engage. (`INSERT=0.80`.)
- **Float the PCB high (top ≈8 mm) with a tiny ground anchor.** The viewer grounds to
  the lowest geometry, so drop a ~0.3 mm speck at z=0 under each column — the viewer's
  ground (and shadow) sit there, the PCB floats ~6 mm up, and you can angle the camera
  **under** the board to inspect the protruding pins. No riser block (it hides the pins).
- **Shadows are viewer-side, already on:** the Babylon viewer has a SpotLight + 1
  shadowGenerator (that's the ground shadow). You can't toggle it from the GLB; raising
  the PCB is what makes the descending part's shadow read clearly. Richer per-part
  inter-shadowing would be a wiki-viewer change, not a GLB change.
- **If KiCad has no 3D model** for a JST part (e.g. ZH `B2B-ZR`), don't silently drop
  the column — render a standing **red placard** ("KiCad: no 3D model — ships sym+fp only")
  in the KiCad slot so the user knows *why* it's absent. (Add a `{"placeholder":true}`
  column to the merge config.)
- **Animation dwell:** keep lifts small and dwell long on the fully-mated state — that
  (pins through + wire seated) is the money shot, not the parts hovering far above.
