Skip to content

feat(extension): Connect OpenRouter, store screenshots over a real page, privacy policy v2 - #25

Merged
HackTuah merged 5 commits into
mainfrom
feat/connect-openrouter-and-screenshots
Oct 6, 2026
Merged

HackTuah merged 5 commits into
mainfrom
feat/connect-openrouter-and-screenshots

Conversation

@HackTuah

@HackTuah HackTuah commented Oct 6, 2026

Copy link
Copy Markdown
Member

What

1. "Connect OpenRouter (free)" is visible now that https://kotiko.org/connect/ is live (slice 11).

  • How it was gated: the background flow (oauth.start / oauth.code, lib/pkce.js, content/connect.js) was built and tested; the buttons had just never been put on the pages.
  • Welcome tab: it's the main choice, with "Paste a key instead" beside it.
    • While OpenRouter's tab is open: "Waiting for OpenRouter… Finish signing in on the other tab."
    • When the key arrives: "Connected to OpenRouter, free models."
    • No Test lookup runs then: it would spend one of the 50 free lookups a day.
  • Settings → Word lookups: a "Connect OpenRouter" button shows when OpenRouter is chosen and no key is saved, or while replacing the key. Choosing another service or pasting a key stops the wait.
  • Verified against OpenRouter's PKCE docs (fetched today): the callback_url, the S256 challenge, key_label, and POST /api/v1/auth/keys with {code, code_verifier, code_challenge_method}.
    • The live /connect/ page serves what connect.js expects.
    • test/e2e/connect.spec.mjs clicks the real buttons on both pages and checks the sign-in address, the waiting text, the exchange body (stubbed), the key saved, an expired code refused, and the same path on another site ignored.
  • Needs a real OpenRouter account to confirm once: that OpenRouter accepts https://kotiko.org/connect/ as the callback and the returned code yields a working key.

2. Store screenshots (SCR-758). The popup shot now opens over a real article tab, www.example.com served by a browser route (it never reaches the internet), so it no longer says "can't run on browser pages".

  • Two more fixes found by looking at the images:
    • the dashboard waited out a "pronunciations 0 of 1" banner;
    • the welcome shot showed "Another service" and now shows OpenRouter.
  • All five are 1280×800 RGB, and each was checked by eye.

3. Privacy policy → Version 2. A "Connecting OpenRouter" section:

  • the one-time code passes through kotiko.org in the page address;
  • the page sets no cookies and loads nothing else;
  • the code is single-use, expires after 10 minutes, and is useless without a secret that stays in the browser;
  • the site's hosts see the visit.

The extension's bundled copy is synced, and the site page regenerates from the same source.

Checked

  • npm run lint: 0 errors. npm run coverage: 1947 pass, 94.03 % lines, 84.47 % branches. npm run perf: all ok.
  • npx playwright test: 100 passed, 4 skipped.
  • check-old-name, check-base-neutral, sync-privacy --check, contrast (76/76) and reuse lint pass. Site npm test and npm run check pass.

Also noticed: the live /connect/ sends no CSP or frame headers (only the <meta> CSP). That's the Cloudflare hardening step in SCR-757.

🤖 Generated with Claude Code

Ayla Croft and others added 4 commits October 6, 2026 18:30
The database, its -wal and -shm files, the model list cache and a new data
folder were created with the process umask, and the systemd unit set no
UMask, so on a shared computer other accounts could read the word list
(SCR-450). The API token and backups were already private.

Now every file the server keeps in the data folder is readable only by its
own account. The BEAM can't give a mode when it creates a file, so files are
created empty, made 0600, and only then filled: the database before SQLite
first opens it (SQLite gives the -wal, -shm and -journal files the database's
mode; a test checks this), each backup and the copy from the old name before
VACUUM INTO, and the token and model cache through a private temporary file.
A data folder the server makes is 0700.

For existing installs, every start checks the folder before the database
opens: Kotiko's own files that others can open are made 0600 again (with one
warning naming them), and the folder 0700 when it holds nothing but Kotiko's
files. A folder with other files in it, or a link, may be shared on purpose,
so it is left as it is with a warning that gives the chmod to run. Nothing
here stops the start. run.sh now sets umask 077 and the unit from
install-service.sh UMask=0077, so files are private from the moment they
exist.

Docs: the configuration reference, security requirements and assurance case
(the umask gap leaves the residual risks), the site's server page, the
changelog and slice 29.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
…ttings

kotiko.org/connect/ is live over HTTPS, so the one-click OpenRouter
sign-in that slice 11 built can be shown. There was no flag: the button
simply wasn't in the pages yet.

The welcome tab's first choice is now "Connect OpenRouter (free)", with
"Paste a key instead" as the first link, as slice 22's sketch has it.
Settings, Word lookups shows "Connect OpenRouter" when OpenRouter is
chosen and no key is saved (or while replacing it). Both pages say they
are waiting while OpenRouter's tab is open, and update on their own when
the key arrives from the return page. Pasting a key still works.

Tests cover the visible buttons, waiting, the key arriving, a failed
exchange and a sign-in that can't start; the e2e test now starts from the
real buttons on both pages and checks the sign-in address, the return
page under the live page's CSP and the exchange body. The docs site's
OpenRouter and Get started pages describe the button instead of "coming".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
The popup screenshot opened popup.html as its own tab, so "This page"
said Kotiko can't run on browser pages (SCR-758). The script now opens
the article first and answers the popup's active-tab question with that
real tab, served at www.example.com by a browser route so the page line
names a website; it stops if the popup still describes a browser page.

The dashboard shot waits for the background pronunciation of the word
the popup adds, instead of showing "0 of 1", and the welcome shot shows
OpenRouter connected, as most learners will see it.

Adds the five 1280x800 English screenshots in brand/store/screenshots/en.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
With "Connect OpenRouter" now visible, the policy must say what it does:
OpenRouter sends the browser to https://kotiko.org/connect/ with a
one-time code in the address, which Kotiko trades for the learner's key.
The page sets no cookies and loads nothing else; the code works once,
expires in ten minutes and is useless without a secret that stays in the
browser; like any site, kotiko.org's hosts (GitHub Pages, Cloudflare)
see the visit and that address. Version 2, with a changelog line, and
the bundled copy in the extension synced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ayla Croft <dev@scriptkittyos.com>
@HackTuah
HackTuah requested a review from minitru as a code owner October 6, 2026 22:47
…/connect-openrouter-and-screenshots

# Conflicts:
#	CHANGELOG.md
@HackTuah
HackTuah merged commit ffc185a into main Oct 6, 2026
14 checks passed
@HackTuah
HackTuah deleted the feat/connect-openrouter-and-screenshots branch October 6, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant