Skip to content

Add bookmark agent API (PUT/DELETE /docs/{slug}/bookmark, GET /bookmarks)#14

Merged
tnsardesai merged 2 commits into
mainfrom
hypeship/bookmark-api-handlers
Jul 25, 2026
Merged

Add bookmark agent API (PUT/DELETE /docs/{slug}/bookmark, GET /bookmarks)#14
tnsardesai merged 2 commits into
mainfrom
hypeship/bookmark-api-handlers

Conversation

@tnsardesai

@tnsardesai tnsardesai commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

The web bookmarks feature shipped its human UI, backend store/view layer, and migration — but not the agent-facing API described alongside it. There are no /api/v1 bookmark handlers, so the endpoints 404, and because no handler exists on disk spec:check’s cross-surface guard had nothing to flag — the surface was left undocumented in the OpenAPI spec, /llms.txt, and SKILL.md.

This PR adds the three endpoints on top of the bookmark store/view layer that already landed.

Endpoints

  • PUT /api/v1/docs/{slug}/bookmark — idempotent save. Requires view access (owner / grant / public / ?viewtoken=); an inaccessible doc 404s (no existence oracle). Persists the view token only when it is the actual basis for access, mirroring the web bookmark POST. Keyed by the key’s email, so it unifies with the account’s signed-in web bookmarks.
  • DELETE /api/v1/docs/{slug}/bookmark — idempotent remove; succeeds when nothing was bookmarked and still drops a bookmark for a revoked/deleted doc (removeBookmarkBySlug resolves the id past findBySlug’s soft-delete filter).
  • GET /api/v1/bookmarks?scope=owned|shared|all — newest first, each item’s access re-resolved at read time (owner|editor|commenter|viewer|public|link|revoked); a withdrawn doc reads revoked with a null url and the bookmark-time title snapshot. Defaults to scope=all. Scope is filtered in SQL so limit bounds the requested scope (no underfill).

Scope: docs.read for all three — bookmarking needs only to read the doc and writes the caller’s own personal state, not the document.

Also

  • Access re-resolution is factored into a pure resolveBookmarkView helper (no DB/session) with unit tests, matching the existing pure-view test pattern.
  • Registered the paths in the code-first OpenAPI registry and added a Bookmarks section to the single-source skill content (lib/skill-content.ts/llms.txt + SKILL.md).
  • Regenerated lib/openapi/generated.{yaml,json}, generated-spec.ts, and SKILL.md.

Verification

  • npm run spec:check — green (28 endpoints, 21 paths; spec ↔ on-disk routes ↔ /llms.txt all agree).
  • npx vitest run — 141 passed (135 prior + 6 new resolveBookmarkView cases).
  • npx tsc --noEmit — clean.
  • Local Postgres integration run (per DEVELOPMENT.md: docker postgres:16npm run migratedev-seed): exercised the DB paths end-to-end — saveBookmark (idempotency + view-token COALESCE persistence), listBookmarks scope/limit for owned/shared/all, and removeBookmarkBySlug on a soft-deleted doc. 11/11 checks passed, including the scope=owned&limit=2 regression below.
  • next lint is not configured in a fresh checkout (prompts for interactive setup), so it was not run; the typecheck + spec:check + tests + local DB run are the gates that did.

Review follow-up

Cursor Bugbot + Vercel VADE both flagged that GET /api/v1/bookmarks applied LIMIT before the in-memory scope filter, so a scoped request could underfill. Fixed in 5fbd2d1 by pushing the scope filter into the listBookmarks SQL; covered by the local Postgres run.

🤖 Generated with Claude Code

…rks)

The web bookmarks feature shipped without its agent-facing API: no
/api/v1 handlers, no OpenAPI spec coverage, and no /llms.txt or SKILL.md
docs. Add the three handlers on top of the existing bookmark store/view
layer, register them in the code-first OpenAPI spec, and document them
in the single-source skill content.

- PUT /api/v1/docs/{slug}/bookmark  — idempotent save; requires view access
- DELETE /api/v1/docs/{slug}/bookmark — idempotent remove; works on a
  revoked/deleted doc (removeBookmarkBySlug resolves the id past soft-delete)
- GET /api/v1/bookmarks?scope=owned|shared|all — access re-resolved per read
  (owner|editor|commenter|viewer|public|link|revoked)

Access re-resolution is factored into a pure resolveBookmarkView helper
with unit tests. Regenerated spec artifacts + SKILL.md; spec:check green
(28 endpoints, 21 paths).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
justhtml Ready Ready Preview, Comment Jul 25, 2026 1:00am

@tnsardesai
tnsardesai marked this pull request as ready for review July 24, 2026 23:53

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 082e79f. Configure here.

Comment thread app/api/v1/bookmarks/route.ts
Comment thread app/api/v1/bookmarks/route.ts Outdated
GET /api/v1/bookmarks applied the SQL LIMIT across the full bookmark set
and then filtered owned/shared in memory, so a scoped request could return
fewer than limit rows (or none) while more matching bookmarks existed.
Push the scope filter into listBookmarks' query so LIMIT bounds the
requested scope. Verified against a local Postgres, including the
scope=owned&limit=2 regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tnsardesai
tnsardesai merged commit 5a42ff4 into main Jul 25, 2026
5 checks passed
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