Skip to content

Simplifying UI + sidecar-only manifest inspection#2

Open
deblanco wants to merge 36 commits into
mainfrom
simplifying-ui-sidecar
Open

Simplifying UI + sidecar-only manifest inspection#2
deblanco wants to merge 36 commits into
mainfrom
simplifying-ui-sidecar

Conversation

@deblanco
Copy link
Copy Markdown

@deblanco deblanco commented May 11, 2026

Summary

Brings in upstream contentauth/simplifying-ui (PR contentauth#15) plus a
new sidecar-only inspection mode for lone-.c2pa drops.

Two chunks:

  1. Upstream simplifying-ui — overview panel, rubrics, zoomable tree,
    sidecar+asset pairing.
  2. Sidecar-only validation delta (one commit, ~9 files).

Why the delta is needed

When user drops .c2pa alone, upstream falls back to @contentauth/c2pa-web
with a comment claiming from_manifest_data is used internally. c2pa-web
v0.6.1 only exposes fromBlob / fromBlobFragment, so that path feeds the
JUMBF bytes back as if they were the asset → assertion.dataHash.mismatch.

What the delta does

  • Rust binding (wasm/src/lib.rs): read_sidecar_integrity_only calls
    Reader::with_manifest_data_and_stream_async(manifest, "application/octet-stream", empty_stream). JUMBF, claim signature,
    hashedURI links, and cert chain validate normally; only asset hash is
    unresolvable.
  • Rebuilt local WASM (public/local-c2pa/*) exposes the binding.
  • JS routing (src/lib/c2pa.ts): SIDECAR_MIME goes through the new
    binding instead of broken packaged-SDK fallback.
  • Soft-vs-hard binding cross-check: when both c2pa.hash.data and
    c2pa.soft-binding exist and soft-binding alg is cryptographic SHA-2
    (e.g. org.monolith.sha256), compare byte values. Match proves the
    signer asserted the same digest in both bindings — internal consistency
    without the asset. Surfaced as
    assertion.softBinding.matchesHardBinding (success) or
    assertion.softBinding.mismatch (failure). Perceptual algs (pHash, pdq)
    skipped — by design their values won't equal the cryptographic hash.
  • UI (src/lib/ReportViewer.svelte):
    • New validationMode + sidecarFile props.
    • "Sidecar + Asset" (indigo) / "Sidecar Integrity Only" (amber) badges.
    • In sidecar-only mode, suppresses asset-binding failures so they don't
      block isTrusted.
    • "Skipped — asset not provided" informational row in their place, unless
      soft/hard match already proved consistency.
    • Allow-lists assertion.softBinding.matchesHardBinding so synthesized
      success row renders.
    • Stops filtering c2pa.hash.data out of assertions card.

Test plan

  • npm run check — 0 errors
  • npm run test:run — 139/139 passing
  • npm run build — clean
  • Drop .c2pa alone (UCFR fingerprint ZIP) → "Inspect sidecar only" →
    claimSignature.validated,
    assertion.softBinding.matchesHardBinding,
    assertion.dataHash.mismatch suppressed. Only red:
    signingCredential.untrusted (clears via Test Certificates).
  • Drop .c2pa + .zip pair → full validation, Sidecar + Asset badge.
  • Drop asset with embedded manifest → unchanged.

Upstream filing

Plan to file the integrity-only delta as follow-up to contentauth#15.

Andy Parsons and others added 30 commits April 19, 2026 09:45
- Remove AssetProfilePage.svelte and profileEvaluator.ts
- Rename hamburger menu item from "Asset Profiles" to "Asset Rubrics"
- Update page type and routing from 'asset-profiles' to 'asset-rubrics'
- Clean up all profile-related drag-and-drop and file routing logic
- Asset Rubrics page shows a "coming soon" placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the placeholder Asset Rubrics page with a working client-side
evaluator. YAML-authored rubrics from the Python reference now run
against the crJSON in-browser via JMESPath, with two evaluation modes:

- document mode (integrity, 3 conformance variants): whole-bundle
  expressions, pass/fail per statement, failIfMatched inversion
- per-manifest mode (signals-local): truthy-only emission grouped by
  id prefix, ingredient DAG construction, three-level mimeType
  resolution (own claim → own thumbnail → child back-fill)

Rubrics surface as a third tab in ReportViewer, gated on the manifest
being trusted AND having zero validation failures. The tab snaps back
to Formatted if the gate closes mid-session. RubricsPanel clears its
results when a new file loads in place so stale findings don't linger.

Golden parity is enforced via 18 fixture triples copied from upstream
(<name>.json + .conformance.json + .signals.json). goldens.test.ts
auto-discovers triples and runs subset-match (conformance) and
exact-match (signals) parity against the Python reference. Unit tests
cover coercion, failIfMatched, reportText fallback, ingredient
back-fill, and the allActionsIncluded strictness rule. 92/92 passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cessing

- App.svelte: show "drop matching asset" panel when a .c2pa is dropped
  without an asset, with buttons to select the asset, inspect sidecar-only,
  or cancel. Extract reprocessCurrentFile() so handleTestModeChanged and
  handleCertificatesUpdated re-run processSidecarWithAsset when
  validationMode === 'sidecar+asset'.
- FileUpload.svelte: add `label` prop for the compact button, add
  `filesselect` event for multi-file drops, enable `multiple` on the
  hidden input, and wire the new event in App.svelte.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switches the rubric engine from @metrichor/jmespath to @adobe/json-formula
(v2.0.0) to track upstream at ../../c2pa/conformance/asset-rubrics, which
moved to json-formula so rubrics can carry `variables:` ($globals) and
`expressions:` (reusable _name() functions with $argN positional params).

Changes:
- Add engine.ts — pre-compiles every expressions: entry on rubric load and
  registers each as a custom function, mirroring the Python reference's
  save/restore dance for $argN injection.
- Loader now extracts `variables:` and `expressions:` from doc 0 alongside
  rubric_metadata.
- evaluate.ts / perManifest.ts route all searches through the engine facade
  instead of calling jmespathSearch directly.
- Add json-formula.d.ts shim (the package ships no .d.ts files).
- Sync public/rubrics/*.yml and __fixtures__/* from upstream to pick up the
  variables/expressions-aware versions.
- Update unit tests that used JMESPath-flavored string-literal syntax:
  json-formula uses double quotes for strings and single quotes to quote
  dotted identifiers (e.g. assertions.'c2pa.actions').
- Refresh CLAUDE.md with the new expression-language section and engine
  description.

All 50 rubric tests pass (37 upstream goldens + 13 unit). Production build
succeeds; bundle adds ~200KB for json-formula.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Upstream changes picked up:
- cb52f44: Remove the c2pa:thumbnail_location rule (Conformance TF, 2026-04-24).
- 9a01d43: Refactored no_unsupported_assertions to a compact
  contains(startsWith(@, $allowed_assertions), true) helper.
- Assertion-name updates: c2pa.cert-status → c2pa.certificate-status,
  c2pa.alternativeContentRepresentation → c2pa.alternative-content-representation.

Rubric files synced from /Users/andyp/Desktop/Projects/c2pa/conformance/
asset-rubrics/ for the three conformance YAMLs. index.json description for
0.2-spec2.2 updated to drop the now-removed thumbnail-location mention.

Engine fix:
- Add normalizeExpression() in engine.ts — a string-aware walker that
  rewrites bare `true`/`false`/`null` keywords to their zero-arg function
  form (`true()` etc.) while leaving string literals ("..." / '...' / `...`)
  and identifiers (is_true, truely) alone.
- Why: @adobe/json-formula 2.0 parses bare `true` as field access (yielding
  null), but the reference Python json-formula tolerates the keyword form
  — and upstream rubrics use it (the new no_unsupported_assertions rule).
  Normalizing here keeps our YAMLs byte-identical to upstream.
- Applied at both compile() of named expressions and search() of statements.

New engine.test.ts (11 tests) locks in normalizer behavior plus core engine
features ($name globals, _name() functions, $argN injection save/restore).

All 107 tests pass; production build clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rubric-driven summary
- generateSummary.ts rewritten: detection layer now reads from a
  `ManifestSignalsResult` (per-manifest hits from the signals rubric)
  instead of inspecting raw assertions/source-types. The brittle
  SOURCE_TYPE / ACTION constants and getPrimaryDigitalSourceType /
  hasAIActions / getHumanReadableActions helpers are gone. Composition
  (article suffix, "and"-joining, capitalisation) stays here, but the
  truth source for "is this an AI image?", "was it captured?", "was it
  edited?" now lives in the rubric YAML maintained by the Conformance TF.
- New signals surfaced that the old code didn't reach: stitched capture
  ("a stitched photo taken with a Pixel 8"), blank canvas, partly-GenAI
  / composition-may-contain-GenAI ("composed by"),
  editorialPossiblyGenAI as another "modified using generative AI"
  trigger.
- Article-suffix grammar now done via vowel check on the next word
  rather than a hardcoded `mimeType.startsWith('image/')` test.
- New summarySignals.ts: module-level singleton that loads the
  signals rubric once per session, caches the parse, and evaluates
  it against any report. Failures resolve to null; the summary then
  falls back to a generic "{a/an} {media} from {signer}" line that
  doesn't claim any property the rubric would.
- ManifestSummary.svelte takes a new `signals: ManifestSignalsResult |
  null` prop. ReportViewer.svelte computes `activeManifestSignals`
  reactively from the report (with stale-result guarding) and passes
  it down. Sentence is sync; only the rubric load is async.
- 24 unit tests cover every origin/modification branch and the
  signals=null fallback.

Rubrics tab gate
- ReportViewer.svelte: drop the `hasNoValidationFailures` requirement
  from `rubricsAvailable`. The tab now appears whenever the cert is
  trusted (any list — official, ITL, or test). The previous gate hid
  the tab on test-cert assets that have benign trust-adjacent
  failures (signingCredential.ocsp.inaccessible, .expired,
  timeStamp.untrusted). The rubrics are designed to enumerate
  validation failures — hiding them when failures exist hid the very
  report meant to explain them.

131 tests pass; production build clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drop Algorithm from signature section
- Drop Instance ID and Label from Active Manifest section
- Filter hash.data assertions from the assertions list
- Remove expandable manifest details (claim signature, active manifest) from ingredient cards; also removes Expand All/Collapse All for ingredients

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace overflow-scroll tree with a Figma-like pan/zoom canvas:
  drag to pan, scroll-wheel/pinch to zoom around cursor, +/−/reset
  controls. Non-passive touch action handles pinch-to-zoom. Click vs
  drag is disambiguated so manifest navigation still works.

- Fix ingredient thumbnails: local WASM reader lacks resourceToBytes,
  so fall back to a cached packaged-SDK reader solely for thumbnail
  resolution. Guard unconditional .bind() on resourceToBytes that
  caused test failures when the reader did not expose the method.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clicking child nodes to zoom in conflicted with drag-to-pan. Since the
canvas already handles navigation, remove the interaction entirely:
onZoom is now optional on TreeNode (absent = all nodes non-interactive),
and OverviewPanel no longer passes it. Removes the focus history /
breadcrumb machinery that backed the feature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace CSS stem/bar/stem connectors with a single SVG per subtree.
  One cubic bezier per child (vertical tangents at both ends) produces
  smooth S-curves matching the verify.contentauthenticity.org style.
  Uses currentColor so curves adapt to dark mode automatically.

- Canvas height is now adaptive: fitToTree() measures the rendered tree,
  computes a zoom that fits both the available width and the viewport
  height (so zoom controls are always visible), and sets canvasHeight
  accordingly. Reset button returns to this fit state.

- Remove will-change: transform from the tree content div so the browser
  re-renders at display resolution on each frame instead of scaling a
  pre-rasterised GPU texture — fixes pixelated text and images on zoom.

- Remove dot-grid SVG background that caused a visible artifact in dark mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace fit-to-tree zoom with a fixed zoom (200/208 ≈ 0.96×) so the
root node is always a consistent, legible size on load and reset.
Pan centers on the root node rather than the full tree extent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Implement `getManifestValidationStatus` to extract validation results per manifest.
- Implement `getAllValidationFailures` to aggregate all failures across active and ingredient manifests.
- Redefine `isTrusted` to be false if there are any failures in the entire report.
- Refactor `ReportViewer.svelte` to group successes, failures, and informationals by manifest in the 'Validation Status Details' section.
- Display signature details (Common Name) for each manifest in the grouped UI.
- Map standard C2PA failure codes (including `assertion.bmffHash.mismatch`) to user-friendly descriptions in `constants.ts`.
- Simplify `validationStatus` calculation to a single reactive assignment to ensure reliable UI updates.
- Add comprehensive unit and component tests to verify failure aggregation and grouped rendering.
- Configure Vitest to support Svelte 5 browser resolution.

TAG=agy
CONV=651fee74-60bb-4d72-9cf8-a4b9333578d9
- Replace JS height measurement with CSS flex chain so the tree canvas
  fills available viewport height without page scrolling
- Fix tree canvas not rendering: card needs flex flex-col so children
  can use flex-1 for height (h-full doesn't resolve against flex items)
- Fix summary and rubrics tabs appearing narrower than crjson/report:
  add w-full to the max-w-7xl container so it stretches to fill its
  flex parent instead of sizing to content (mx-auto in a flex context
  makes items content-sized, not stretched)
- Condense footer to a single slim bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same mx-auto-in-flex-col issue as the report container — needs w-full
to stretch to fill the parent before max-w caps it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drops the ingredient tree visualization, all tree-building helpers,
the IngredientTree component import, and the Ingredients nav link.
ingredientsList is kept — still passed to ManifestSummary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CSS declared Inter as the font-family but never loaded it, causing
browsers to fall back to system-ui. Adds preconnect hints and the
variable-font link (covers all weights/italics in one request).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ResizeObserver isn't available in jsdom; Svelte's bind:clientWidth uses it
internally. The two ReportViewer tests also needed to click the Report tab
first since the default tab is now Summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use String(error) for non-Error SDK throws so pattern checks apply
- Add 'No C2PA manifest' (capital) and 'no JUMBF data found' to the
  no-manifest detection patterns
- Capitalize 'Content Credentials' consistently in the no-manifest message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Increase node card width from w-52 (208px) to w-[300px]; initial zoom = 1
- Move date out of in-card badge into label area below card (consistent for all nodes)
- Show relationship label (e.g. 'Component Of') above tool name for non-root nodes
- Show all inception and transformation action tags, not just the first of each
- Widen label area to match card width (was 13rem, now 300px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ingredients that have no Content Credentials were previously invisible.
They now appear as connected nodes with a dotted border, no CC badge,
muted label, and dashed connector line from their parent.

- Add isStub to OverviewNode type
- crJsonEdges now emits null-childIdx edges for no-manifest ingredients;
  also handles v2 active_manifest (snake_case string) so credentialed v2
  ingredients are no longer misidentified as stubs
- buildTree merges stub edges from crJsonEdges even when sigData is
  present (sigData.ingredients silently drops uncredentialed ingredients)
- TreeNode renders stubs: dashed border, no CC icon, italic
  "No Content Credentials" label, dashed SVG connector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced max-w-2xl with w-full so the error list fills its container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Conformance rubrics (0.1/0.2 spec2.2, 0.2 spec2.4): add
  $hard_binding_assertion_labels variable (covers bmff.v2/v3, boxes,
  multi-asset hashes in addition to hash.data); fix label
  c2pa.hash.collection.data → c2pa.collection-data; refactor named
  expressions to per-manifest $arg0 parametric helpers; fix backtick-
  quoted boolean literal in contains() call
- Signals rubric: same per-manifest $arg0 refactor for _hasActionsV2,
  _signalActions, _createdWithDst helpers
- Integrity rubric intentionally NOT updated — source has a bug
  referencing _manifest_validationResults which is undefined there
- Update i2v, t2i2v, capture2genai2v goldens: update_manifest_constraints
  now correctly passes for video manifests that have BMFF hashes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dragging an image inside the tree canvas was firing the browser's native
HTML5 drag, which App.svelte's global dragenter handler picked up and
showed the drop overlay. Fix: suppress dragstart on the canvas element
and set draggable=false on all img/video elements in TreeNode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
andyparsons and others added 4 commits May 8, 2026 14:59
- Remove processSidecarWithAsset, isSidecarWithAssetSupported, and
  fromSidecarAndBlob — sidecar+asset pairs now use processFile(sidecar)
- Add .json sidecar support: JSON files parsed directly as crJSON
- Fix isCrJson to not require @context (it's optional in the spec)
- Remove sidecarSupported gate so the "drop matching asset" prompt always
  shows for .c2pa and .json sidecars
- Fix canvas pan in tree view: document-level mousemove/mouseup listeners
  and remove disabled attribute from tree node buttons (Safari blocks
  mousedown on disabled buttons)
- Drop ValidationStatus import from SDK; infer types from CrJsonValidationStatus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The local WASM's read_manifest_store passes the sidecar JUMBF bytes as
the "asset stream" when verifying, so data hash assertions always fail
with assertion.dataHash.mismatch. The packaged SDK uses from_manifest_data
internally for application/c2pa, which reads the manifest without an
asset stream and skips asset-hash verification correctly.

- Extract wrapPackagedSdk() helper to avoid duplicating the legacy JSON
  → crJSON wrapping logic
- Add getPackagedC2pa() that lazily inits and caches the packaged SDK
  instance independent of the local WASM
- Route application/c2pa MIME type to getPackagedC2pa() in
  extractCrJsonWithMetadata
- Simplify initC2pa() using wrapPackagedSdk / getPackagedC2pa

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses read_sidecar_manifest_store (maps to c2pa-rs
with_manifest_data_and_stream) when validating a sidecar+asset pair,
so data hash assertions are verified against the actual asset bytes.

- Add read_sidecar_manifest_store to LocalC2paModule and wire
  fromSidecarAndBlob in createLocalC2pa when the export is present
- Add extractSidecarWithAssetCrJsonWithMetadata / processSidecarWithAsset
  with clear hash-mismatch error handling
- processSidecarPair and reprocessCurrentFile now call
  processSidecarWithAsset instead of processFile(sidecar)
- Standalone sidecar reads (.c2pa without asset) still go through
  the packaged SDK, which correctly skips asset hash verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a user drops just the `.c2pa` sidecar (no source asset), there's no way to
verify the `c2pa.hash.data` hard-binding — c2pa-rs needs the actual asset bytes
to recompute the SHA-256. Previously this path returned `dataHash.mismatch` and
looked like a hard failure, which it isn't: it's an unverifiable check, not a
failed one. This change reframes that path as an "integrity-only" inspection.

What changes:

- `wasm/src/lib.rs` — new `read_sidecar_integrity_only` binding that calls
  `Reader::with_manifest_data_and_stream_async(manifest, "application/octet-stream",
  empty_stream)`. JUMBF structure, claim signature, hashedURI links, and the
  cert chain validate normally; only the asset-hash assertion is left
  unresolved.
- `public/local-c2pa/*` — rebuilt WASM artifact exposing the new export.
- `src/lib/c2pa.ts` —
  - `LocalC2paModule.read_sidecar_integrity_only` + `C2paInstance.reader.
    fromSidecarIntegrityOnly` plumbing.
  - `extractCrJsonWithMetadata` routes `SIDECAR_MIME` through the new binding.
    The previous path went through the packaged `@contentauth/c2pa-web` SDK
    with a comment claiming it used `from_manifest_data` internally — c2pa-web
    v0.6.1 has only `fromBlob`/`fromBlobFragment`, so that path fed the JUMBF
    bytes back as if they were the asset and always produced `dataHash.mismatch`.
  - Soft-vs-hard binding cross-check: when both `c2pa.hash.data` and
    `c2pa.soft-binding` exist and the soft-binding alg is a cryptographic
    SHA-2 (e.g. `org.monolith.sha256`), compare their byte values. Match
    proves the signer asserted the same digest in both bindings — internal
    consistency without the asset. Result is injected into the report as
    `assertion.softBinding.matchesHardBinding` (success) or
    `assertion.softBinding.mismatch` (failure). Perceptual algorithms (pHash,
    pdq, etc.) are skipped silently — by design, their values won't equal
    the cryptographic hash.
- `src/lib/ReportViewer.svelte` —
  - Accepts `validationMode` ('embedded' | 'sidecar+asset' | 'sidecar-only')
    and `sidecarFile` props.
  - Renders "Sidecar + Asset" or "Sidecar Integrity Only" badge.
  - In sidecar-only mode, suppresses `assertion.dataHash.mismatch` /
    `bmffHash.mismatch` / `boxHash.mismatch` from the failure list so they
    don't block `isTrusted` and don't show as red rows.
  - Surfaces a "Skipped — asset not provided" informational row in their
    place, except when the soft/hard match check already succeeded (in which
    case the placeholder is redundant).
  - Allow-lists `assertion.softBinding.matchesHardBinding` so the synthesized
    success row actually renders.
  - Stops filtering `c2pa.hash.data` out of the assertions card — that
    assertion is the most diagnostic one for sidecar inspection.
- `src/lib/constants.ts` — failure description for
  `assertion.softBinding.mismatch`.
- `src/App.svelte` — passes `validationMode` and `sidecarFile` into
  `<ReportViewer>`.

For the UCFR-style `.c2pa` (`Advia Design System (3).c2pa` test file) the
report now shows: `claimSignature.validated`,
`assertion.softBinding.matchesHardBinding`, and only
`signingCredential.untrusted` red (clears by adding the UCFR cert via Test
Certificates).
deblanco added a commit that referenced this pull request May 11, 2026
Brings in changes from PR #2 (simplifying-ui-sidecar) without closing
the PR. Includes sidecar file support, asset rubrics, tree-view UI,
font switch to Inter, validation grouping by manifest, and related
fixes/tests.

Ref: #2
deblanco added 2 commits May 12, 2026 19:22
The cross-check compared c2pa.soft-binding.value to c2pa.hash.data.hash
in sidecar-only mode to prove internal manifest consistency without
the asset. Removed since it could not prove asset integrity (a tampered
manifest could set both fields) and added UI complexity that obscured
the simpler 'asset not provided' message.

Sidecar-only mode now always shows asset-binding failures as skipped
informational rows, regardless of soft-binding presence.
Previously the UI restricted file inputs to images, video, audio, PDF,
and raw camera formats. C2PA hard-binding (c2pa.hash.data) is just a
byte-level hash, so any file can be validated against its .c2pa sidecar.

- Drop restrictive accept= filters from the upload inputs.
- Fall back to application/octet-stream when the asset extension is
  unknown so c2pa-rs's with_manifest_data_and_stream_async streams
  raw bytes for hashing.
- Rewrite the unsupported-format error to point users at the
  sidecar-pair flow instead of listing supported media types.
- Refresh the drop-zone copy and badges to mention any-asset+sidecar.
deblanco added a commit that referenced this pull request May 12, 2026
…idecar

Squashes the two latest commits from simplifying-ui-sidecar onto main
without closing PR #2:

  - Remove synthetic soft-vs-hard binding consistency check.
  - Support arbitrary asset types in sidecar+asset validation.

Ref: #2
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.

3 participants