Skip to content

feat(sweetlink): optional capture sinks (s3/http/module) - #46

Merged
ytspar merged 1 commit into
mainfrom
feat/sweetlink-capture-sinks
Oct 20, 2026
Merged

ytspar merged 1 commit into
mainfrom
feat/sweetlink-capture-sinks

Conversation

@ytspar

@ytspar ytspar commented Oct 20, 2026

Copy link
Copy Markdown
Owner

What

Adds an opt-in, pluggable capture-sink system to the Sweetlink daemon. Every capture artifact the screenshot handler saves locally (screenshot JPG, metrics JSON, console logs text+JSON, a11y report) can now also be fanned out to remote storage.

Configuration

Env var SWEETLINK_CAPTURE_SINKS — a JSON array:

[
  {"type":"s3","bucket":"captures","prefix":"devbar",
   "endpoint":"https://<account>.r2.cloudflarestorage.com","region":"auto",
   "accessKeyId":"...","secretAccessKey":"..."},
  {"type":"http","url":"https://example.com/hook","headers":{"authorization":"Bearer ..."}},
  {"type":"module","path":"./my-gdrive-sink.mjs"}
]

Sink types

  • s3 — any S3-compatible service (AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces) via a zero-dependency SigV4 PUT — no new package dependencies. endpoint for non-AWS services; {bucket} placeholder for virtual-hosted endpoints; region: "auto" for R2.
  • http — POST raw bytes with x-capture-filename/x-capture-kind headers to any webhook (generic receivers, Google Apps Script → Drive, the Factory workspace drain host); surfaces a permanent link from a {"url"} body or Location header.
  • module — dynamic import() of a user module exporting sink.upload(artifact) — the "whatever else" escape hatch (real Google Drive OAuth sink, Dropbox, internal CDN, …) in ~10 lines.

Semantics

Sinks are best-effort fan-out: the local disk save is unchanged and remains the source of truth; sink failures log ([Sweetlink] Capture sink "x" failed…) and never block or fail the browser response; one malformed config never disables the others.

Validation

  • 20 new tests (env parsing, SigV4 credential scope/signed-headers vector, R2-style + {bucket} endpoint URL construction, live local HTTP server round-trip, module sinks incl. default export, dispatcher failure isolation).
  • tsc --noEmit clean; the only full-suite failures reproduce on a clean tree (load-related flakes in playground/auto tests); all touched-area suites pass.
  • Release note for the computed 1.29.0 added (release-notes gate).

Downstream

Enables the DEV-14160 slice "Devbar capture persistence": a factory workspace sets SWEETLINK_CAPTURE_SINKS with an http sink pointed at the workspace drain host — captures taken on a wip preview become durable instead of landing on the container's ephemeral disk.

Fan every capture artifact (screenshot, metrics, console logs, a11y
report) out to remote storage in addition to the local disk save,
configured with SWEETLINK_CAPTURE_SINKS (JSON array of sink configs).

Built-in sink types:
- s3: any S3-compatible service (AWS S3, Cloudflare R2, MinIO,
  Spaces) via a zero-dependency SigV4 PUT; endpoint + {bucket}
  placeholder supported, region 'auto' for R2
- http: POST raw bytes + capture headers to a webhook URL; parses
  {"url"} / Location for a permanent link
- module: dynamic import() of a user module exporting sink.upload(),
  so any backend (Google Drive, Dropbox, internal CDN) is a ~10-line
  file

Sinks are best-effort fan-out: local saving is untouched, a failing
sink logs and never blocks the browser response, and one malformed
config never disables the others. Signature verified against the AWS
SigV4 test-suite vector; 20 new tests.

Enables durable Devbar/Sweetlink captures on factory preview
workspaces when paired with an http sink pointed at the workspace
drain host (DEV-14160).
@ytspar
ytspar merged commit 36d3693 into main Oct 20, 2026
1 check passed
@ytspar
ytspar deleted the feat/sweetlink-capture-sinks branch October 20, 2026 19:18
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