Page bookmark and selection notes are a blocklet-level capability. Omit save: and it is on. Chrome stays hidden until the visitor is authenticated. This is not in-page review (?review=1), not comments, and not connect-service User Center.
Evidence for this page is the parser and runtime on Arc feat/save-capability (arc#6715, PR #6716): packages/core/src/blocklet/save-capability.ts, parse-manifest.ts save:, packages/aup/src/save-surface.ts, packages/aos/src/session/bookmark.ts, annotate.ts. It is not a released arc --version tag. Do not treat this contract as present on an older host.
Motivation: Bookmarking is a runtime capability, not a site plugin.
Declare it
Write save: on the blocklet blocklet.yaml, not on .web/site.yaml and not on a single page.
| Input | Normalized { enabled, chrome } | HTML data-arc-save-capability |
|---|---|---|
omitted / null / true | { enabled: true, chrome: "floating" } | floating |
false | { enabled: false, chrome: "floating" } | off |
{ enabled: false } | { enabled: false, chrome: "floating" } | off |
{ chrome: "buttons" } | { enabled: true, chrome: "buttons" } | buttons |
{ enabled: true, chrome: "floating" } | same | floating |
# default: omit the field
save: false
save:
enabled: false
save:
chrome: buttonsField reference
| Key | Type | Default | Notes |
|---|---|---|---|
save | boolean or object | omitted = on | Top-level blocklet.yaml key. Spec v1 and v2. |
save.enabled | boolean | true unless the value is exactly false | Only false opts out. Missing key in the object still enables. |
save.chrome | "floating" | "buttons" | "floating" | Any other string is treated as floating at normalize time. The parser rejects unknown enum values. |
| object extra keys | n/a | n/a | Rejected (.strict()). save: { chrome: floating, extra: 1 } fails parse. |
Boolean save: true is accepted and equals the default.
Auth gate
| Signal | Effect |
|---|---|
save off (data-arc-save-capability="off") | No chrome. Page [data-arc-save] controls stay hidden (html:not([data-arc-save-ready]) [data-arc-save] { display: none }). |
Session missing or authenticated: false | Same as off for chrome. Capability declaration is unchanged. |
Session authenticated: true and capability on | Sets data-arc-save-ready to floating or buttons and mounts that mode. |
Unauthenticated clicks on a save control that somehow remains visible open the header Login control, or /login?redirect=<current path+query>.
Web Device publishes session through window.__arcSessionCtx / __arcSessionWaiters. AUP also sends msg.save (off | floating | buttons) on render/stage frames when the blocklet module set save. Client copies that onto window.__arcSave and the HTML attribute before draining waiters.
Override order in the shared IIFE: window.__arcSave (string or { enabled, chrome }) wins over the HTML attribute. Missing both → floating.
Display modes
floating (default)
After login, a side bar (.arc-save-bar, data-arc-save="bar") with:
| Control | data-arc-save | Action |
|---|---|---|
| Bookmark | bookmark | exec("/.actions/bookmark", { url, title }) |
| My saves | library | navigate to /{locale}/my/ |
| Selection | (mouseup) | if selected text length ≥ 2, open note popover → /.actions/annotate |
url is window.location.href. title is document.title when non-empty.
Locale for My saves: if location.pathname matches ^/([a-z]{2}(?:-[A-Za-z0-9]+)?)/, use /{that}/my/. Otherwise /my/.
buttons
No side bar. No automatic selection popover. Bind page-authored controls:
data-arc-save | Required | Behavior when authed |
|---|---|---|
bookmark | yes for this mode | same bookmark exec; sets aria-pressed |
annotate | optional | open note popover (uses current selection if any) |
library | optional | go to /{locale}/my/ |
bar | runtime-only | ignored as a click target |
<button type="button" data-arc-save="bookmark">Bookmark</button>
<button type="button" data-arc-save="annotate">Annotate</button>
<a data-arc-save="library">My saves</a>Clicks bubble: the handler walks from the event target up until it finds a data-arc-save other than bar.
HTML attributes
| Attribute | On | Values | Who writes it |
|---|---|---|---|
data-arc-save-capability | <html> | off | floating | buttons | Web Device finalizePageHtml / stampSaveCapabilityHtml. AUP handleAup when msg.save is one of those three. |
data-arc-save-ready | <html> | floating | buttons | Client, only after authenticated + capability on. Removed when off or anonymous. |
data-arc-save | controls | bar | bookmark | annotate | library | Floating bar (runtime) or page author (buttons mode). |
Stamp is idempotent: if data-arc-save-capability is already on <html>, it is not rewritten. The attribute is appended before the tag closer so lang / data-tone stay in place.
Actions
Both actions write into this session's caller DID Space. Wire path / target / collection are ignored. Workbench-private fields (tagIds, status, collectionIds, dataVersion, version) are rejected.
/.actions/bookmark
| Arg | Required | Notes |
|---|---|---|
url | yes | Absolute http: or https: URL. Becomes objectId. |
title | no | Record name. Untitled pages derive a label from the URL (last path segment or hostname). Label is not provenance. |
ctype / contentType / schemaType | no | ABOUT types only: link (default), place, product. Unknown claimed types are refused, not coerced to link. |
notes / text / description | no | Optional body fields on the L1 record. Floating chrome does not send them. |
Write path: /user/bookmarks/{id} with id = b_ + first 16 bytes of SHA-256(url) as hex.
Success: { ok: true, id, path, copied }. Duplicate URL: copied: false (same id).
error.code | When |
|---|---|
unauthorized | No callerDid, or write forbidden / not found on /user |
invalid-url | Missing or not absolute http(s) |
unknown-type | Claimed type is not link / place / product |
invalid-record | Builtin schema validation failed |
workbench-private-field | Payload still has a private Aside field |
/.actions/annotate
| Arg | Required | Notes |
|---|---|---|
url | yes | Absolute http(s) page URL → isBasedOn |
quote | yes unless text | Selected text → record text |
text | alias of quote | Used if quote is empty |
note | no | Optional; stored as record name |
Write path: /user/notes/{id} with id = n_ + first 16 bytes of SHA-256(url + "\n" + quote + "\n" + note) as hex. contentType: note.
error.code | When |
|---|---|
unauthorized | No caller, or /user write denied |
invalid-url | Missing or not absolute http(s) |
invalid-quote | Both quote and text empty |
invalid-record | Schema validation failed |
workbench-private-field | Private Aside field present |
Floating chrome only opens the note popover when the current selection length is ≥ 2.
/my/ compositor
Built-in route: /{locale}/my/ (same locale rule as My saves). Not /.well-known/service/user.
When save is on, sections always start with:
| id | label | path |
|---|---|---|
bookmarks | Bookmarks | /user/bookmarks |
notes | Notes | /user/notes |
Then collections that set faces.my: true and live under /user. Duplicate id or path is skipped. Pass { save: false } into composeMyPageSections (runtime does this when save.enabled is false): builtin bookmark/note sections disappear; extra faces.my collections remain.
Failure and limits
| Boundary | Behavior |
|---|---|
Parse unknown save.chrome | Manifest parse throws (enum) |
| Parse unknown object key | Manifest parse throws (strict) |
| Normalize garbage (array, random object) | Runtime treats as default on / floating, except enabled: false |
| Anonymous visitor | No chrome; [data-arc-save] hidden |
save: false | Attribute off; /my/ drops builtin sections |
| Action without login | unauthorized |
Non-http(s) url | invalid-url |
| Selection length 0 or 1 in floating mode | No popover |
| Cross-site reading list | Not this capability. Records are this site's /user/** for this caller |
| In-page review | Different overlay. See In-page review mode |
Related pages
- Restrict who can read a page: public HTML vs logged-in writes
- In-page review mode: authoring-time overlay, not reader save
- Manifest and capabilities:
save:on the package - Feature article: Bookmarking is a runtime capability, not a site plugin