capsite: goal trees for a zotero cache and a social.coop cache
Both follow the diigo-cache pattern (webapp holds the credential, read-only and minted narrow at the source; the cache serves a public page). Zotero's versioned sync answers diigo's stale-head problem; Mastodon's doesn't announce edits/deletes, so its tree carries that as a design question. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
279ddb4caa
commit
99ce5b6964
3 files changed
+216
-1
No files matched your search
@@ -0,0 +1,104 @@
|
||||
# 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/<userID>/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/<userID>/`
|
||||
(`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=<library
|
||||
version>` (only what changed, `Last-Modified-Version` header
|
||||
gives the new version) plus `deleted?since=<version>` 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/<name>` 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
|
||||
Reference in new issue
Block a user