Skip to content

feat: remove uploadUnit — a pulse uploads as one video (#64) - #66

Merged
morepriyam merged 4 commits into
mainfrom
fix/64-remove-upload-unit
Sep 23, 2026
Merged

morepriyam merged 4 commits into
mainfrom
fix/64-remove-upload-unit

Conversation

@morepriyam

@morepriyam morepriyam commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #64

A pulse is one video (the artifact named by the pairing link) plus its related captions, beat manifest and thumbnail. A beat is a timestamp range inside that video, not an upload strategy, so uploadUnit is removed everywhere.

Release order

Ship after / together with mieweb/pulse#213. Older Pulse builds require uploadUnit in /capabilities, so a server running this without the matching app update would break pairing for them.

Library

  • uploadUnit option removed from the Fastify plugin (src/app.ts) and the core (src/core.ts). Passing it now throws a TypeError at boot: "uploadUnit was removed — delete the option". This is one shared check (rejectRemovedOptions in src/lib/options.ts), called from both entry points. validateUploadUnit is gone.
  • The /capabilities body (built only in the core; the plugin delegates to it) moved into its own module, buildCapabilities in the new src/lib/capabilities.ts, next to the protocol-version constants, and no longer includes uploadUnit. The field is also gone from the OpenAPI schema. PROTOCOL_VERSION is still exported from core and routes.
  • buildUploadLink no longer accepts or emits uploadUnit.
  • Comments in capability-token.ts, storage/types.ts and the routes OpenAPI text no longer describe segment sessions.

Protocol / docs

  • PROTOCOL.md §2: removed uploadUnit from the /capabilities fields. §3: removed the link param and added a SHOULD saying clients ignore unknown params. §8 is retitled "Artifact relationships (relatedTo)" and now describes the one upload set: video, captions, beat manifest and thumbnail. A beat is defined there as a timestamp range inside the video. The beat manifest JSON shape is unchanged.
  • README: removed the "Upload unit" section, the option row and the buildUploadLink override example, and updated the /capabilities row and example. Added a paragraph under the pairing flow describing the upload set and what a beat is.
  • CHANGELOG: breaking entry under [Unreleased].

Examples, scripts, tests

  • fastify-demo, fastify-auth-demo, meteor-demo: removed UPLOAD_UNIT (.env.example, compose.yaml), the option, the ?uploadUnit= query param, the per-link selector and the "Upload unit default" chip.
  • The feed/library viewers play a pulse as one looping video. The beat manifest splits the progress bar into one track per beat, sized to the beat's length, and the badge counts beats ("pulse · 3 beats"). The clip-by-clip playback, ordering-manifest parsing and per-clip progress bars are gone. A group uploaded in the old segment mode now shows one of its clips.
  • The demo servers pair captions with their video through relatedTo, not by matching filenames.
  • scripts/e2e-tus.mjs now asserts that uploadUnit is absent from /capabilities.
  • scripts/e2e-tus.mjs also resolves the TUS create Location against the server base. The server returns a path-only Location, which fetch() rejects, so the e2e failed at the first PATCH. This was already broken on main.
  • Tests: the option is rejected at boot on the plugin and on the core, links never carry the param (even when a stale caller passes it), and /capabilities has no uploadUnit (plugin and core).

Verification

  • npm run build (tsc): clean
  • npm test: 127 tests, 124 pass, 0 fail, 3 skipped (the existing ffmpeg-dependent web-ready tests)
  • fastify-demo booted locally with no UPLOAD_UNIT: /capabilities has no uploadUnit, /deeplinks emits v/artifactId/server only, and the pairing page loads.
  • npm run e2e against a local Postgres (DATABASE_URL set): all checks pass, including /capabilities with no uploadUnit.
  • All four examples run locally (the auth demo against a local Postgres). A pulse uploaded over TUS the way the app sends it (video, captions, beat manifest, thumbnail) plays in each feed with its beat tracks and captions. Two real app uploads also render correctly.

A pulse is one video plus related captions, beat manifest and thumbnail;
a beat is a timestamp range inside that video, not an upload strategy.

- Drop the `uploadUnit` option from the Fastify plugin and the core.
  Passing it now throws at boot via one shared `rejectRemovedOptions`
  check in lib/options.ts, called from both entry points.
- Build the /capabilities body once in lib/capabilities.ts, without
  `uploadUnit`; drop it from the OpenAPI schema.
- `buildUploadLink` no longer accepts or emits `uploadUnit`.
- PROTOCOL §2/§3/§8, README and CHANGELOG describe the one upload set.
- Tests: option rejected at boot on plugin and core, links never carry
  the param, /capabilities has no `uploadUnit`; e2e asserts it's absent.

BREAKING CHANGE: `uploadUnit` option, link param and /capabilities field
are removed. Release together with mieweb/pulse#213.
…de (#64)

- fastify-demo, fastify-auth-demo, meteor-demo: remove UPLOAD_UNIT from
  .env.example / compose.yaml, the `uploadUnit` plugin/core option, the
  `?uploadUnit=` query param on /deeplinks, the per-link selector and the
  "Upload unit default" chip.
- Feed/library viewers: describe a pulse as one video plus captions,
  beat manifest and thumbnail; the multi-clip playback path stays only
  for uploads made by older Pulse builds.
Copilot AI lite review requested due to automatic review settings September 22, 2026 16:24

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The server returns a path-only Location (/pulsevault/upload/...), which
fetch() rejects as an invalid URL, so the e2e smoke test failed at the first
PATCH. This was already broken on main.
Copilot AI review requested due to automatic review settings September 23, 2026 18:28

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…ress bar

The feeds in fastify-demo, fastify-auth-demo and meteor-demo played a pulse
clip by clip (ordering manifest, clip index, one progress bar per clip). A
pulse is now one looping video: the beat manifest splits the progress bar
into one track per beat, sized to its length, and the badge counts beats.
The servers pair captions with their video through relatedTo instead of by
filename.
Copilot AI review requested due to automatic review settings September 23, 2026 18:54

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@morepriyam morepriyam changed the title feat!: remove uploadUnit — a pulse uploads as one video (#64) feat: remove uploadUnit — a pulse uploads as one video (#64) Sep 23, 2026
@morepriyam
morepriyam merged commit 718c41e into main Sep 23, 2026
3 of 4 checks passed
morepriyam added a commit that referenced this pull request Sep 24, 2026
…, checked in CI (#68)

* feat!: protocol 2, with the version read from package.json and a client version check

- The protocol this release implements is package.json `pulseProtocol`
  ({ version: "2.1", min: 2, max: 2 }). src/lib/protocol.ts reads it;
  /capabilities and the Protocol-Version header come from it, and
  /capabilities adds `protocolRevision`. Protocol 2 records the breaking
  change in #66 (uploadUnit removed); 2.1 adds the items below.
- Clients may send `Pulse-Client: <product>/<version>; protocol=<min>-<max>`.
  A client whose newest protocol is older than this server's oldest gets
  426 Upgrade Required with the supported range, on uploads and artifact
  GET/DELETE. /capabilities always answers. No header, no change.
- `Upload-Metadata.appVersion` is trimmed, capped at 64 code points, stored
  with the artifact by both storage adapters, and reported on the
  complete/reject events.

Tests read the expected protocol from package.json, so a version bump
doesn't need test edits.

* feat: the protocol written down as schema files

protocol/ is now the single source for the wire contract (PROTOCOL.md §7):

- protocol/schemas/*.schema.json (JSON Schema 2020-12): the /capabilities
  body, pairing link params, Upload-Metadata keys, the beat manifest,
  capability-token claims and the Pulse-Client header. Every field has a
  description; the /capabilities route schema is loaded from its file.
- protocol/openapi.json: the HTTP surface, generated from the plugin's
  route schemas by scripts/gen-protocol.mjs (npm run protocol).
- PROTOCOL.md: the version, the /capabilities and Upload-Metadata field
  tables and the list of schema files are generated into marked sections.
  §7 now covers major.minor versioning, the rules for changing the
  protocol, Pulse-Client and 426, and a version history (1.0, 2.0, 2.1).
- npm run protocol:check fails if openapi.json or PROTOCOL.md are stale.
- New tests validate what the code produces against the schemas: the live
  /capabilities body, buildUploadLink params, token claims, upload
  metadata, a beat manifest and Pulse-Client values.

* ci: tests, protocol versioning checks, e2e and the Pulse contract suite

- scripts/check-protocol.mjs enforces PROTOCOL.md §7.1 against a base ref:
  any change under protocol/ (descriptions and titles aside) needs a
  pulseProtocol.version bump, a breaking change needs a major bump, the
  version never goes backwards, and PROTOCOL.md needs a history row for it.
  oasdiff decides what's breaking in protocol/openapi.json; a conservative
  diff decides it for protocol/schemas/ (removed or newly required fields,
  removed values, changed types/formats/patterns/defaults are breaking).
- .github/workflows/ci.yml, on PRs and pushes to main:
  - test: npm test on Node 22 and 24
  - protocol: npm run protocol:check, then the versioning check with oasdiff
  - e2e: npm run e2e against a Postgres service container
  - pulse: mieweb/pulse's contract suite (the app's real pairing and upload
    code) against this build. Tests Pulse main, or the branch named by a
    `Pulse-Ref: <branch>` line in the PR description when the server and
    the app change together. Skips if that Pulse has no suite yet.

* docs: protocol versioning in the README and CHANGELOG

README: a Protocol versioning section (pulseProtocol, Pulse-Client and 426,
the protocol/ folder, what CI enforces), the protocol-2 /capabilities
example, appVersion in the Upload-Metadata table and the artifact event,
and the npm scripts. CHANGELOG: the protocol 2.1 entries under Unreleased.

* fix(protocol): escape backslashes in generated PROTOCOL.md table cells

A description with a backslash before a pipe would have unescaped the
pipe and broken the table. Escape backslashes first. (CodeQL
js/incomplete-sanitization.) No change to the generated output today.
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.

Remove uploadUnit — a pulse uploads as one video; beats are timestamps within it

2 participants