# zotero-cache goal tree The same conventions as [the diigo goal tree](capsite-checklist.md): each `- [ ]` is a goal, its indented children must pass first, working notes sit under each goal. This tree is goal 7 of that one, carried out. - [ ] **The awesome-ocap bibliography is generated from Zotero** ([bib sync #195](https://github.com/dckc/madmode-blog/issues/195)): madmode (and a GitHub action, later) read a cache of [my Zotero library](https://www.zotero.org/connolly/library); the Zotero API key stays inside webapp, so neither reader holds a credential - [ ] **0. Decide the shape** (user's call before code): - [ ] 0-i. New worker `zotero-cache/` beside `diigo-cache/`, or generalize diigo-cache? Recommendation: a new worker, and extract shared pieces (fill job, webkey page, page cache) only once both work — two examples before an abstraction. - [ ] 0-ii. Does it need a credential at all? The library is public on zotero.org; the public API (`/users//items`, no key) may be enough for the bibliography. A key adds unpublished items, notes, and attachment metadata. If public is enough, webapp still earns its place as the rate-limited client, minted with no secret header. - [ ] 0-iii. Which collections make up awesome-ocap? (A Zotero collection per README section is the natural mapping.) - [ ] **1. Credential discipline**: a **read-only** key minted at zotero.org → Settings → Security → API keys ("allow library access" only; no write, no notes unless 0-ii says so) — attenuation at the source. Send it as `Authorization: Bearer`, not `Zotero-API-Key`: webapp's mint form blanks `authorization` as a secret header and would echo an unknown one. Key → webapp DO storage only; ids → password manager. - [ ] **2. The cache** (`zotero-cache/`, the 1c pattern from diigo): - [ ] 2-i. Mint and wire: `/_connect` → `/_mint` → `/_wire`, the base URL `https://api.zotero.org/users//` (`GET /keys/current` with the key returns the userID). - [ ] 2-ii. Storage: items keyed by Zotero `key`, each with its `version`; one library version per cache. **Durable state** from day one: a `SCHEMA_V1` step, so later shapes migrate. - [ ] 2-iii. Fill: `GET items?format=json&include=data,csljson` paged by `start`/`limit` (100 max) to `Total-Results`; honor `Backoff` and `Retry-After` (webapp's `msPerCall` is the floor, not the whole story). - [ ] 2-iv. Tests: `pnpm lint && pnpm test`, the real worker against a fake Zotero (fake-webapp style), including a `Backoff` response. - [ ] 2-v. Complete: a filled cache matches a Zotero export (File → Export Library → CSL JSON), the analogue of diigo's `scripts/compare-export.ts`. - [ ] **3. Freshness is cheap here** (diigo's 1f, solved by the API): Zotero versions every write. Refresh = `items?since=` (only what changed, `Last-Modified-Version` header gives the new version) plus `deleted?since=` for removals. A test: an item edited and one deleted in the fake both show after a refresh. A cron alarm in the cache DO can drive it. - [ ] **4. Read surfaces**: - [ ] 4-i. Public page `/public/` with `?collection=`, as HTML and JSON; JSON shape pinned by a test (the contract with readers). - [ ] 4-ii. CSL JSON and BibTeX out, from cached data (Zotero's own `format=bibtex` is a fetch; the cache should serve what it has). - [ ] **5. awesome-ocap README generated**: a script (or madmode page) renders the collections to the README's markdown; diff against the hand-maintained README, the way 1c-vi diffed the diigo export. Prerequisite from #195: the current README's entries imported into Zotero (BibTeX / CSL JSON import). - [ ] **6. Deployed**: after diigo's goal 5 (webapp already deployed); rate-limit `/public/*`; mint against prod; fill. - [ ] **7. Stretch (#195)**: a GitHub action regenerates the README from the public cache — public data, so no secret in the action. ## How to run TBD with goal 2; it should mirror [the diigo how-to-run](capsite-checklist.md#how-to-run-continuation-prompts). ## Working rules [capsite AGENTS.md](AGENTS.md): dev servers are the user's job; durable state, loud about breaking changes; secrets discipline; generated config types. ## Pointers - [bib sync #195](https://github.com/dckc/madmode-blog/issues/195) — the goal, and the import half (README → Zotero) - [Zotero Web API v3 basics](https://www.zotero.org/support/dev/web_api/v3/basics) and [syncing](https://www.zotero.org/support/dev/web_api/v3/syncing) — versions, `since=`, deletions, `Backoff` - [awesome-ocap](https://github.com/dckc/awesome-ocap) — the README to generate - [webapp/README.md](webapp/README.md) — the mint form and its secret header list