Problem: The submission language ID is hardcoded per platform in
`language_ids.py` (e.g. CF `"89"` = GNU G++17 7.3.0). Users have no
way to select a different version like G++20 or C++23.
Solution: Add `submit_id?: string` to `CpPlatformOverrides` and
`CpLanguage`. When set, `submit.lua` passes the resolved `submit_id`
directly to the scraper instead of the generic language key. The
existing `get_language_id() or args[4]` fallback in `base.py` handles
pre-resolved IDs without any Python changes.
## Problem
Kattis and USACO login and submit were broken in multiple ways
discovered
during manual end-to-end testing. Neither platform could successfully
authenticate or submit through the plugin.
## Solution
**Kattis:** switch login from `POST /login/email` (requires CSRF fetch)
to
`POST /login` with `script=true` (200 = success, 403 = bad credentials);
remove `_check_kattis_login` entirely since Kattis blocks all GET
requests
from httpx; add submit retry on `"Request validation failed"` to handle
expired sessions; fix language ID `"C++17"` → `"C++"`.
**USACO:** fix login field `user` → `uname`; fix success check to
`code==1`;
fix submit endpoint to `submit-solution.php`, file field to
`sourcefile`,
hidden field extraction off-by-one (`group(2)` → `group(1)`); fix
`_pick_lang_option` loop order (keywords outer, options inner) so
specific
keywords like `"c++17"` match before broad ones like `"c++"`.
**`submit.lua`:** absolutize source file path via `fnamemodify(...,
':p')`
before passing to the scraper — Python is spawned with `cwd=plugin_path`
so relative paths silently fail with `FileNotFoundError`.
**Both platforms:** remove cookie fast path from `login` subcommand so
credentials are always validated, preventing stale cookies from masking
wrong credentials.
## Problem
After the initial submit hardening, two issues remained: source code was
read in Lua and piped as stdin to the scraper (unnecessary roundtrip
since
the file exists on disk), and CF's `page.fill()` timed out on the hidden
`textarea[name="source"]` because CodeMirror owns the editor state.
## Solution
Pass the source file path as a CLI arg instead — AtCoder calls
`page.set_input_files(file_path)` directly, CF reads it with
`Path(file_path).read_text()`. Fix CF source injection via
`page.evaluate()`
into the CodeMirror instance. Extract `BROWSER_SUBMIT_NAV_TIMEOUT` as a
per-platform `defaultdict` (CF defaults to 2× nav timeout). Save the
buffer
with `vim.cmd.update()` before submitting.
## Problem
CSES submit was a stub returning "not yet implemented".
## Solution
Authenticate via web login + API token bridge (POST `/login` form, then
POST `/api/login` and confirm the auth page), submit source to
`/api/courses/problemset/submissions` with base64-encoded content, and
poll for verdict. Uses the same username/password credential model as
AtCoder — no browser dependencies needed. Tested end-to-end with a real
CSES account (verdict: `ACCEPTED`).
Also updates `scraper.lua` to pass the full ndjson event object to
`on_status` and handle `credentials` events for future platform use.
## Problem
`_submit_sync` was a 170-line nested closure with `_solve_turnstile` and
the browser-install block further nested inside it. Status events went
to
stderr, which `run_scraper()` silently discards, leaving the user with a
10–30s silent hang after credential entry. The NDJSON spawn path also
lacked stdin support, so submit had no streaming path at all.
## Solution
Extract `_TURNSTILE_JS`, `_solve_turnstile`, `_ensure_browser`, and
`_submit_headless` to module level in `atcoder.py`; status events
(`installing_browser`, `checking_login`, `logging_in`, `submitting`) now
print to stdout as NDJSON. Add stdin pipe support to the NDJSON spawn
path in `scraper.lua` and switch `M.submit` to streaming with an
`on_status` callback. Wire `on_status` in `submit.lua` to fire
`vim.notify` for each phase transition.
Problem: credentials were stored in a separate file,
cp-nvim-credentials.json, alongside the main cp-nvim.json cache.
Two files for one plugin's persistent state was unnecessary.
Solution: add get_credentials/set_credentials to cache.lua, storing
credentials under _credentials[platform] in the shared cache. Update
clear_all() to preserve _credentials across cache wipes. Remove the
separate file, load_credentials, and save_credentials from submit.lua.
Add submit.lua that reads credentials from a local JSON store (prompting
via vim.ui.input/inputsecret on first use), reads the source file, and
delegates to scraper.submit(). Add language_ids.py with platform-to-
language-ID mappings for atcoder, codeforces, and cses.