The real fill failed at "diigo returned 44 items for page 167 of 168":
diigo's `total` overstates what load_user_items returns, so the true
last page looked short and the guard took it for a capped page size.
A short page that isn't the last by `total` now fetches the next page
(paced like any diigo call): empty means the data really ends there —
accepted, with a "total-overstated" notice logging total vs actual;
anything else means items would be skipped — refused, the message now
giving both counts. The fake gains an account whose total claims 60
more than it has; a fill on it runs to done.
Generated-by: Claude Opus 5.5
Asked for 128, diigo returned 100 (the short-page guard caught it on the
first real fill), so PAGE_SIZE = 100: ~168 pages for 16.7k bookmarks.
The same run showed a fill job outliving a page-size change: progress
counted in 24s ("page 25 of 698") resumed as if counted in 128s. Stored
state reinterpreted, so: FillStatus records pageSize (additive; jobs
saved without it counted in 24), and a job whose size differs from
PAGE_SIZE fails with "page size changed …; start the fill again"
instead of resuming; starting again replaces it. Tested with the old
shape in storage.
Generated-by: Claude Opus 5.5
PAGE_SIZE = 128 (was 24), one constant for the fill job, public pages,
dumps, and DiigoBridge, so a cache never mixes sizes: ~131 pages instead
of ~698 for 16.7k bookmarks.
Cache invalidation, agreed beforehand: pages are cached by (page number,
size), so pages cached at 24 become misses and refetch at 128. Items are
keyed by link_id and just re-upsert; nothing is lost.
Unknown whether diigo honors count=128, so a short page that isn't the
last is refused loudly (logged, thrown; a fill fails with the reason)
rather than silently skipping items. The fake gains a capped account to
test it; the plain account grows to 300 (3 pages), the deep one to 6000.
Generated-by: Claude Opus 5.5
The public page's filters, toward the weekly PESOS roundup (#23):
?days=N (7/30/90/365 presets), gmail-style after/before (UTC days,
half-open: after inclusive, before exclusive; yyyy/mm/dd accepted), and
q (words, "phrases", #tags exact against diigo's comma-separated tags,
- negation); q alone searches the last 365 days and says so; malformed
filters are a 400. Filtering walks diigo's updated-desc pages from 1 and
stops at the window, so ?days=7 costs one page.
src/query.ts is the typed, JSDoc'd API (stubs that throw); 22 specs are
describe.skip until it lands. The fake webapp gains a dated account.
Generated-by: Claude Opus 5.5
A hit must replay diigo's order as fetched (page_members.pos), not a
column sort. The fake webapp's link_id and updated_at are scrambled so
neither column sorts back into diigo's order; ordering the replay by
updated or item_id fails the new test.
Generated-by: Claude Opus 5.5
The dump worker becomes a page cache: a hit is served from DO SQLite
storage with no diigo call; a miss asks the webapp instance in the
cache's wiring and etches the page.
- two independent caches, one behavior (PagedCache): DiigoCache keeps
every item; PublicCache keeps shared items only, dropping private
rows before they are stored; served at /public/<username> (HTML page
form, or JSON)
- create flow on one port: /_connect relays webapp's mint form, /_mint
proxies the mint, /_wire wires a private or public cache; /_publish
adds a public cache later
- ids are webkeys: kept out of URLs (POST bodies), shown and logged
only as a hash fingerprint; saved to the password manager under the
holder's petname (never sent to the server)
- items keyed on diigo's link_id; username from u_name; unkeyable
items and every failure path are logged; each page read logs hit/miss
- tests run the real worker + DOs in the vitest pool against a fake
webapp (tests/fake-webapp.js)
Generated-by: opencode (GLM)
Generated-by: Claude Opus 5.5