Skip to main content

arc#6715 · not a published ARC tag

Bookmark and note (save capability)

Declare page bookmark and selection notes on blocklet.yaml save:. Default on, hidden until login, floating chrome or page buttons.

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.

InputNormalized { 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" }samefloating

yaml
# default: omit the field

save: false

save:
  enabled: false

save:
  chrome: buttons

Field reference

KeyTypeDefaultNotes
saveboolean or objectomitted = onTop-level blocklet.yaml key. Spec v1 and v2.
save.enabledbooleantrue unless the value is exactly falseOnly 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 keysn/an/aRejected (.strict()). save: { chrome: floating, extra: 1 } fails parse.

Boolean save: true is accepted and equals the default.

Auth gate

SignalEffect
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: falseSame as off for chrome. Capability declaration is unchanged.
Session authenticated: true and capability onSets 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:

Controldata-arc-saveAction
Bookmarkbookmarkexec("/.actions/bookmark", { url, title })
My saveslibrarynavigate 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-saveRequiredBehavior when authed
bookmarkyes for this modesame bookmark exec; sets aria-pressed
annotateoptionalopen note popover (uses current selection if any)
libraryoptionalgo to /{locale}/my/
barruntime-onlyignored as a click target

html
<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

AttributeOnValuesWho writes it
data-arc-save-capability<html>off | floating | buttonsWeb Device finalizePageHtml / stampSaveCapabilityHtml. AUP handleAup when msg.save is one of those three.
data-arc-save-ready<html>floating | buttonsClient, only after authenticated + capability on. Removed when off or anonymous.
data-arc-savecontrolsbar | bookmark | annotate | libraryFloating 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

ArgRequiredNotes
urlyesAbsolute http: or https: URL. Becomes objectId.
titlenoRecord name. Untitled pages derive a label from the URL (last path segment or hostname). Label is not provenance.
ctype / contentType / schemaTypenoABOUT types only: link (default), place, product. Unknown claimed types are refused, not coerced to link.
notes / text / descriptionnoOptional 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.codeWhen
unauthorizedNo callerDid, or write forbidden / not found on /user
invalid-urlMissing or not absolute http(s)
unknown-typeClaimed type is not link / place / product
invalid-recordBuiltin schema validation failed
workbench-private-fieldPayload still has a private Aside field

/.actions/annotate

ArgRequiredNotes
urlyesAbsolute http(s) page URL → isBasedOn
quoteyes unless textSelected text → record text
textalias of quoteUsed if quote is empty
notenoOptional; 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.codeWhen
unauthorizedNo caller, or /user write denied
invalid-urlMissing or not absolute http(s)
invalid-quoteBoth quote and text empty
invalid-recordSchema validation failed
workbench-private-fieldPrivate 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:

idlabelpath
bookmarksBookmarks/user/bookmarks
notesNotes/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

BoundaryBehavior
Parse unknown save.chromeManifest parse throws (enum)
Parse unknown object keyManifest parse throws (strict)
Normalize garbage (array, random object)Runtime treats as default on / floating, except enabled: false
Anonymous visitorNo chrome; [data-arc-save] hidden
save: falseAttribute off; /my/ drops builtin sections
Action without loginunauthorized
Non-http(s) urlinvalid-url
Selection length 0 or 1 in floating modeNo popover
Cross-site reading listNot this capability. Records are this site's /user/** for this caller
In-page reviewDifferent overlay. See In-page review mode