# Jobs Grazyna - Memory ## Project Structure - Grazyna's job search materials at `/Users/i052341/Daten/Cloud/04 - Ablage/Ablage 2020 - 2029/Grazyna/Arbeit/Jobs Grazyna/` - Subfolders: 00 - CV, 04 - ORP, 05 - Caisse de Chomage, 06 - Annexes, 10 - Discussions, 99 - Archived, Notes, Zeugnisse ## Email Application Attachments - **Always attach annexes from `06 - Annexes/`** when sending email applications for Grazyna - Current contents: `Zeugnisse kombiniert.pdf` (combined work certificates) - Rename to `Certificats de travail.pdf` in the email attachment filename - Standard attachment set: Lettre de Motivation.pdf + CV Grazyna Nott.pdf + Certificats de travail.pdf (from 06 - Annexes) - Claude.ai project ID: `019a5a62-fee2-76df-8230-bbd43f009cf0` - Download scripts in `10 - Discussions/` (fetch_chatlist.py, download_chats.py, fix_artifacts.py) ## Obsidian Formatting Rules - **YAML frontmatter must have an empty line after the closing `---`**. Obsidian breaks if content follows immediately after frontmatter. Always add a blank line between `---` and the first content line. ## Grazyna's Personal Details - **Address:** Chemin de la Tarpa 8a, CH-1872 Troistorrents - **Phone:** +41797844554 - **Email:** gina@mnsoft.org - All contact details are also stored in her SeriousLetter account ## Grazyna's Date of Birth - **DOB:** 1979-07-28 (28 July 1979) - Note: SeriousLetter profile API is read-only — DOB cannot be saved there via API ## Application Defaults - **Start date:** Always choose the next possible/earliest date - **Pays:** Suisse ## Cover Letter Format - **NO address header** — do not include sender address, date, or recipient address - Start directly with the subject line as a **level 2 markdown heading** (`## Candidature au poste de...`) - Two line breaks after the heading, then the letter body - End with `Grazyna Nott` (no address block at the end either) - Always verify company facts — don't assume "family company" etc. unless confirmed ## SeriousLetter API - **Multi-user is live:** Run `sl_switch_user(name: "grazyna")` at session start, then use MCP tools directly. - **Fallback:** If MCP fails, use `curl` with her token and header `X-API-Token`. - Grazyna's token: `a073d1c4f09049c4d7750a99344a6f9995b0271afff68acb6c85fb636f0b914b` - Grazyna's profile UUID: `a6dd4a8e-d615-4077-9d16-c02d165ee627` (name: "Grazyna Nott (French)") - Base URL: `https://jobs.seriousletter.com/api/v1` - API uses `X-API-Token` header (not `Authorization: Token`) - PUT for updates (not PATCH), POST for creates, DELETE for removals - The token referenced in NewJob.md Step 10a (`a073d1c4...`) refers to this full token ## Browser Automation Learnings ### MANDATORY: Disable 1Password Before ANY Form Interaction **Run this FIRST on every page before clicking or typing into any field:** ```js document.querySelectorAll('input, textarea, select').forEach(el => { el.setAttribute('data-1p-ignore', 'true'); el.setAttribute('autocomplete', 'off'); }); ``` This prevents 1Password from intercepting fields and crashing the Playwright CDP connection. Without this, 1PW popup triggers "Cannot access chrome-extension://" errors and kills the browser session. - **1PW kills Playwright MCP on forms** - even with `addInitScript`, 1PW can crash CDP on navigation to form pages. The `data-1p-ignore` attribute is the simple, reliable fix. - **Fallback: Use clean Chromium via CDP** - if 1PW still causes issues, launch `/opt/homebrew/bin/chromium --remote-debugging-port=9222`, then connect via `playwright.chromium.connect_over_cdp("http://127.0.0.1:9222")` in a Python script. No extensions = no crashes. - **ServiceNow date fields** need the calendar picker (click calendar icon, navigate months, click day, click OK). Typing text into the date input does NOT work - it shows a dash. - **ServiceNow file inputs** are in shadow DOM at depth 5, all with id `hvs-ats-file-btn`. Walk shadow roots to find them, store as `window.__fileInputs`, then trigger `.click()` and catch `filechooser` event. - **ServiceNow form URL pattern**: replace `annonce-details-page` with `annonce-candidature` in the URL and append `/sp-application/0` to go directly to the form (skipping the POSTULER button). - **CAPTCHA** on ServiceNow forms must be solved manually - cannot be automated. - **Native value setters** (`Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set`) fill the visible field but ServiceNow's framework doesn't register them for validation. Use `pressSequentially()` with actual keystrokes instead. - **NEVER EVER kill the browser without EXPLICIT user permission.** The user may be logged into sites (ORP/job-room.ch, eIAM, etc.). Killing the browser destroys those sessions. ALWAYS ASK FIRST. Do NOT run `pkill` on Chrome/Chromium. Do NOT run `browser_close`. If Playwright crashes, ASK the user what to do. This is a HARD RULE - no exceptions. - **ORP (job-room.ch)** requires eIAM login (Swiss government SSO). Playwright MCP launches a fresh browser with no session. If the user says "ORP is logged in", they mean in their own browser, not Playwright's. Ask which browser to use or have user log in first. ## Known Limitations - **Voice mode lost after auto-compact**: After context compaction, the session loses awareness that messages were arriving via `[Whazaa:voice]` prefix and defaults to text replies. The compacted summary doesn't carry forward the voice-mode context. Workaround: user sends next message via voice and the per-message toggle picks it back up. ## job-room.ch (ORP) API Access - **User ID:** `703d651c-0412-11f1-a99a-462689f09ec3` - **External ID:** `16660479` - **User Profile Ext ID:** `2288523` - **March 2026 proofId:** `27af9f1f-06ee-11f1-8c9b-967beb1d3317` - **Roles:** ROLE_JOBSEEKER, ROLE_AVP - **Auth header:** `Authorization: ` — JWT from `sessionStorage.getItem('authenticationToken')` (the stored value already includes any prefix; no "Bearer" needed) - **JWT is session-based** — expires ~10h after login. Must re-extract from browser when expired. - **To extract:** Navigate to job-room.ch while logged in, run `sessionStorage.getItem('authenticationToken')` via Playwright evaluate - **Playwright profile:** `~/.claude/playwright-profiles/grazyna/` (persistent session — already logged into job-room.ch after first login) - **API base:** `https://www.job-room.ch/` (same origin as the portal) - **CRITICAL: curl returns 401 — use browser-context fetch() only.** The session is bound to browser internals (eIAM SSO cookies). External HTTP clients cannot replicate this. - **Working create endpoint:** `POST /onlineform-service/api/npa/_action/add-work-effort?userId={userId}&_ng=ZGU=` - **Working update endpoint:** `POST /onlineform-service/api/npa/_action/update-work-effort?userId={userId}&_ng=ZGU=` (full replace - send complete payload with updated fields) - **Working search endpoint:** `GET /onlineform-service/api/npa/_search/by-owner-user-id?userId={userId}&_ng=ZGU=` (response: `{ content: [{ workEfforts: [...] }] }`) - **Working delete endpoint:** `DELETE /onlineform-service/api/npa/{proofId}/work-efforts/{workEffortId}?_ng=ZGU=` (returns 204) - **_ng parameter:** `ZGU=` = `base64("de")` = German language key. NOT a CSRF token. Always use `ZGU=`. - **CRITICAL: NEVER use UI clicks for ORP — ALWAYS use the API via page.evaluate(fetch())** - **ORP status update workflow** (confirmed working 2026-03-17): 1. Navigate to job-room.ch (any page, just needs logged-in session) 2. Search: `GET /_search/by-owner-user-id` to find the work effort 3. Update: `POST /_action/update-work-effort` with full existing payload, changing `applyStatus` to `["REJECTED"]` and setting `rejectionReason` 4. Returns 201 on success ```javascript // Search + Update in one page.evaluate() await page.evaluate(async ({ companyName, reason }) => { const token = sessionStorage.getItem('authenticationToken'); const userId = '703d651c-0412-11f1-a99a-462689f09ec3'; const searchResp = await fetch( `/onlineform-service/api/npa/_search/by-owner-user-id?userId=${userId}&_ng=ZGU=`, { headers: { 'Authorization': token, 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } } ); const data = await searchResp.json(); let effort = null; data.content.forEach(p => { if (p.workEfforts) p.workEfforts.forEach(w => { if (w.applyChannel?.address?.name?.includes(companyName)) effort = w; }); }); if (!effort) return { error: 'not found' }; effort.applyStatus = ['REJECTED']; effort.rejectionReason = reason; const updateResp = await fetch( `/onlineform-service/api/npa/_action/update-work-effort?userId=${userId}&_ng=ZGU=`, { method: 'POST', headers: { 'Authorization': token, 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify(effort) } ); return { status: updateResp.status }; }, { companyName: 'Company', reason: 'Absage erhalten' }); // Returns { status: 201 } on success ``` - **Full payload schema and all endpoints:** see Matthias's `memory/orp-internal-api.md` (same API, different userId) ## End-to-End Job Application Workflow for Gina **When applying for a new job, follow these steps in order:** 1. **Create job in SeriousLetter** — use `curl` with Gina's `X-API-Token` (NOT the MCP tools, those are Matthias's account) 2. **Copy CV to job** — `POST /api/v1/jobs/{job_uuid}/cvs/copy/{profile_uuid}` with profile `a6dd4a8e-d615-4077-9d16-c02d165ee627` 3. **Write cover letter** — conversational French tone, same style as Vevey letter #370. NO formal headers. Save via `POST /api/v1/jobs/{job_uuid}/letters` 4. **Export letter PDF** — `GET /api/v1/export/letters/{letter_id}/pdf`, CV: `GET /api/v1/export/cvs/{cv_uuid}/pdf`. Save as `Lettre de Motivation.pdf` / `CV Grazyna Nott.pdf` 5. **Submit application** — via the employer's portal (SAP SuccessFactors, ServiceNow, etc.) using Playwright 6. **Update job status** — `PUT /api/v1/jobs/{job_uuid}` with `{"status": "applied", "applied_date": "YYYY-MM-DD"}` 7. **Register ORP entry** — via `page.evaluate()` fetch to job-room.ch API (see ORP section above). **CHECK existing entries first** with the search endpoint to avoid duplicates! ### Key Lessons - **Always check ORP entries before creating** — the other agent or a previous session may have already added them - **ORP delete endpoint:** `DELETE /onlineform-service/api/npa/{proofId}/work-efforts/{workEffortId}?_ng=ZGU=` (returns 204) - **ELECTRONIC applyChannel requires `formUrl`** — cannot be empty, use the application URL - **Cover letter style:** User wants conversational, personal tone (like letter #370 for Vevey). NOT formal corporate French. When re-applying, reference previous application and any trial days. - **File naming:** Always `Lettre de Motivation.pdf` — never include letter numbers or job references in the filename - **User often edits letters directly in SeriousLetter** — after generating, fetch the latest version from SL before exporting PDF - **NEVER invent work experience** — only reference jobs from her actual CV profile, not from applications she submitted. Otto's Collombey was an application, NOT past employment. Always verify against the CV profile before writing cover letters. - **Company address must be tracked in SL** — when creating companies/jobs, ensure `address_line1`, `postal_code`, `city` are set. The `street`/`zip` fields on company create may not map correctly; use PUT to update after creation. - **Country field = "Suisse"** — not "CH". The `country` display field should be "Suisse" (French), `country_code` is "CH". - **SL job creation requires `company` + `position_title`** — not `title`. These are mandatory fields. - **SL letter `final_content` is the source of truth** — updating `content` alone doesn't change what's displayed/exported. Always update `final_content` for letter edits. - **Personalize salutations** — use "Chère Madame [Name]" in both cover letters and email body, not generic "Madame [Name]". - **Wait for user go-ahead before creating Gmail drafts** — generate letters in SL first, let user review, then export PDFs and create drafts only when told to. - **Both ORP entries can be created in a single `page.evaluate()`** — no need for separate navigations, just chain the fetch calls. - **Always create a job note directory** — for every job application, create `Notes/YYYY/MM/NNNN - YYYY-MM-DD - Company Position/` with a matching .md file inside. Include job details, SL UUIDs, application method, and ORP entry. Follow the format of note #0015. - **SL MCP multi-user is live** — use `sl_switch_user(name: "grazyna")` at session start to use MCP tools directly. No more curl needed. Both `matthias` and `grazyna` are registered. ## Related Projects - Previous job search project: `/Users/i052341/Daten/Cloud/09 - Job Search/` (183 conversations, same download scripts pattern) - Org ID for Claude.ai API: `64c6f22d-c2a7-431e-9f81-dad785ac50aa`