Uh oh!
There was an error while loading. Please reload this page.
[codepress#5858 release] codepress-main is the release branch (OIDC publish); merge staging - #16
Conversation
Merge PR2 provenance and reproducible packaging baseline for CodePress issue #5319.
feat: publish host-backed FreeCut editor surface
* add caption command and editor ui * fix(editor): mount caption editor in production shell * feat(editor): route captions through host surface * fix(editor): preserve caption background opacity
* feat(editor): add host-backed transcript consumer * fix(editor): keep host-visible sidebar tab across authoritative snapshots Applying a transcript edit installs the authoritative snapshot, and the host runtime reset the active sidebar tab to Media on every install. That unmounted the transcript panel before it could show its applied state or its inline revision-conflict error (the conflict only reached the user through the host notify channel). Preserve the active tab when host mode still shows it (media always, text/transcript per capability and port), and keep resetting tabs host mode does not expose. Cover the applied and conflict outcomes through the real MediaSidebar path.
* feat(qa): add CodePress-parity QA gate set for staging PRs (#5319) - docs/qa: gate-set README, canonical QA report format, verification graph - scripts/qa-head-binding.mjs: emit/validate exact base/head + clean-tree binding - scripts/qa-browser-check.mjs: browser discovery (system Chrome, then Playwright chromium), renderFrame smoke, screenshot/log/manifest artifacts; exit 3 = BLOCKED when no browser session exists - scripts/qa-redaction-check.mjs: no secrets/tokens/local paths/media bytes in QA docs and artifacts - wire qa:binding, qa:browser, check:qa-redaction into package scripts/lint - regenerate provenance package.json SHA256 pins for the script changes - fallowrc: exclude scripts/qa-*.mjs from health complexity alongside the existing local-quality-tooling exclusions * fix(qa): pass changed-health on code, not config; correct gate docs Judge FAIL follow-up: revert the .fallowrc.json health-ignore exclusion for scripts/qa-*.mjs and instead decompose qa-head-binding/qa-browser-check/ qa-redaction-check main flows so every function sits under the fallow CRAP/cognitive thresholds (0 introduced findings, verified with fallow audit). Also correct docs/qa/README.md: verify aggregates gates 2 and 4-13 (lint is not part of it), and gate 3 documents that lint requires the editor-surface dist on a fresh tree (pre-existing TS2307 quirk, present at base). * fix(qa): close redaction gate false negatives (judge G1) - qa-redaction-check: replace the three known-root path patterns with a generic absolute-path pattern (any Unix root or Windows drive path, URL prefixes excluded) and match any image/video/audio data URI regardless of payload length; secrets keep their patterns. Add a per-line qa-redaction:allow marker for intentional doc examples (secrets never allowlisted). - qa-browser-check: sanitize logs with the same generic path pattern instead of replacing only ROOT and os.homedir(). - Add deterministic negative fixtures (scripts/fixtures/qa-redaction/) and a node:test self-test proving the gate fails closed on a tmp path, a generic absolute path, a short media data URI, and a Windows path, and passes on the clean set; wired into check:qa-redaction itself. - Regenerate provenance package.json SHA256 pins for the script changes. * fix(qa): detect Windows drive paths with forward slashes (review) The generic absolute-path pattern's Windows branch only matched backslash separators, so C:/Users/... passed both the checker and the browser-log sanitizer. Accept either separator in both scripts and add the negative-windows-forward-slash fixture to the fail-closed self-test (now 6/6).
…host-mounted) (#12) The decoder-prewarm worker's keyframes_extracted handler called updateMedia() unconditionally. updateMedia() goes through requireWorkspaceRoot(), which throws when the editor surface is host-mounted without <WorkspaceGate> (e.g. CodePress) — surfacing as an uncaught error on every decoded play/pause cycle. Gate the persistence call on getWorkspaceRoot(): host-mounted surfaces skip cross-session keyframe persistence while in-session registration (registerKeyframeIndex, keyframesSentForSrc) stays unconditional. Bump @quantfive/freecut-editor-surface to 0.3.1 (fail-soft fix; publish intentionally not done here).
…ortcuts (#13) * fix(editor): keep host bridge round-trip stable for minimal clip items stableSerialize treated a present-but-undefined key as different from a missing key, and frameItemToNativeComparable unconditionally emitted volume/speed/opacity/transform keys (plus an always-present text style object). Host snapshots that omit those optional fields failed metadataUnchanged after a simple drag or trim, landing in the unsupported-edit branch, and untouched clips leaked into changed[] on multi-clip timelines. - stableSerialize now skips undefined-valued object entries. - frameItemToNativeComparable emits volume/speed/opacity/transform only when set, and the plain-text style key only when non-empty; an opacity-only native transform round-trips as the top-level opacity field the host sent. - commandIdsForChanges compares clips with synthesized source bounds on both sides so host items without sourceStart/sourceEnd do not appear changed after the native bridge fills its defaults. - deriveSupportedHostEdit batches one remove_item command per removed item (bounded by MAX_COMMANDS_PER_OPERATION) instead of only handling exactly one removal. Round-trip tests install host snapshots through the real runtime, perform store move/trim/remove operations, and assert the derived host command batches. * feat(editor): mount host-safe timeline shortcuts in host mode TimelineShortcutsController was only rendered outside host mode, so Delete/Backspace/Space/J/K/L and friends did nothing in the host-embedded surface. Add useHostTimelineShortcuts (composed in use-timeline-shortcuts.ts) and mount it via a HostTimelineShortcutsController when hostRuntime is present. The composition mounts only bindings that never mutate the host-owned document outside the bridge: - Playback/navigation and tool switching mount as-is (local UI state; Shift+C split crosses the bridge as a supported split_item command). - Delete/Backspace are extracted into a shared useDeleteShortcuts hook; item removal flows through the bridge as remove_item commands, now batched for multi-select. - useUIShortcuts gains an enableHistory option (default true) so host mode mounts zoom/snap without undo/redo, which would mutate the temporal store without emitting host commands. Ripple delete, clipboard, markers, in/out points, nudges, join, freeze frame, and clear-keyframes stay unmounted in host mode. The editor -> timeline edge stays within budget by exporting the new hook from the existing use-timeline-shortcuts module. * refactor(editor): drop redundant null guards in isOpacityOnlyTransform nativeTransformToFrame fills every key, so the ?? 0 fallbacks were dead branches that tripped the fallow changed-health complexity gate. * fix(preview): keep host-provided cross-origin media audible MediaElementAudioSourceNode silences cross-origin media served without CORS approval (HTML spec), so host-mode playback through the Web Audio clip graph was muted. Route cross-origin sources around the graph: applyVideoElementAudioState and NativePitchCorrectedAudio now drive the media element's volume/muted directly when the source is not WebAudio-safe (isWebAudioSafeMediaSource: same-origin, blob:, data:), with reactive volume/mute propagation on the direct path. EQ remains graph-only and is skipped on the direct path. Host runtime hardening: reset the persisted playback mute/volume on mount (the monitor volume UI is hidden in host mode, so a persisted mute silently zeroed embedded audio), and keep resilient pointerdown/keydown listeners for the whole host session that resume the shared preview AudioContext when it starts suspended.
* chore(release): freecut-editor-surface 0.3.2 Version bump so the 0.3.2 npmjs publish is reproducible from a real repo commit (provenance gate). Content delta vs 0.3.1 is PR #13: host bridge round-trip stability, host-mode timeline shortcuts, and the host-mode cross-origin audio fix. * chore(release): document and script the manual npmjs release path The package manifest keeps publishConfig pointed at GitHub Packages (the CI tag/dispatch target, enforced by package-editor-surface.mjs), but CodePress consumes @quantfive/freecut-editor-surface from npmjs and the repo has no NPM_TOKEN secret, so npmjs releases are a manual maintainer step. Add scripts/publish-editor-surface-npmjs.mjs (root script publish:editor-surface:npmjs) that runs the provenance + deterministic pack + fresh-consumer smoke preflight and then publishes the exact tarball to https://registry.npmjs.org with --access=public; --dry-run validates without publishing. Document both release targets in the package README and refresh the provenance package.json checksums. * chore(release): make public npmjs the canonical release target Per maintainer direction the editor surface is a public package now. publishConfig targets https://registry.npmjs.org with public access and package-editor-surface.mjs enforces exactly that; the tag/dispatch workflow publishes to npmjs with NODE_AUTH_TOKEN from the NPM_TOKEN repo secret (a repo admin must add it before tag publishes work). The manual maintainer path (publish:editor-surface:npmjs) stays as the fallback and drops its registry/access overrides now that the manifest is canonical. README release docs updated; GitHub Packages is no longer a target. * chore(release): guard npmjs publish to the merged staging revision Reviewer finding: the manual publish script rebuilt and preflighted without checking the worktree or release ref, so uncommitted source could ship as the official public version. The script now requires an explicit --ref <sha> (or --ref HEAD) and refuses before any build step — in dry-run mode too — unless the tree is clean, HEAD equals --ref, and --ref is an ancestor of origin/staging. Guard behavior is covered by scripts/publish-editor-surface-npmjs.test.mjs (node --test, 7 cases: clean/match/ancestor acceptance, dirty tree, HEAD mismatch, non-staging ref, missing ref, unresolvable ref) wired as test:publish-editor-surface-guards. README documents the --ref flow; provenance checksums refreshed for the root manifest change.
Bring the host-backed editor work and QA tooling from `staging` onto `codepress-main`, which becomes the single release branch for @quantfive/freecut-editor-surface. Conflict resolution: - .github/workflows/publish-editor-surface.yml: keep `codepress-main`'s OIDC trusted-publishing version (id-token: write, --provenance, the already-published no-op guard, branch + tag triggers) as the base, fold in staging's explicit --registry flag, and drop every NPM_TOKEN / NODE_AUTH_TOKEN trace. The `if:` guard and its comment now name `codepress-main` instead of `staging`. - .github/workflows/sync-upstream.yml: no conflict — the file only ever existed on `codepress-main`, so the merge keeps it. - Everything else: take `staging`'s content (the newer fork work).
…s-main `codepress-main` is now the release branch, so every hard-coded `origin/staging` that meant "the release/base branch" moves with it. Semantics are unchanged — only the ref. - scripts/publish-editor-surface-npmjs.mjs: `STAGING_REF` -> `RELEASE_REF` (`origin/codepress-main`); the guard messages and the CI-fallback comment follow (CI now publishes via OIDC, not an NPM_TOKEN secret) - scripts/publish-editor-surface-npmjs.test.mjs: fixture ref and test names - scripts/qa-head-binding.mjs, scripts/check-fallow-changed-health.mjs: `DEFAULT_BASE_REF` - docs/qa/canonical-report.md, docs/qa/README.md, README.md, docs/pr2-provenance-and-packaging.md: the gate set now binds to `codepress-main` - packages/freecut-editor/README.md: describe the OIDC branch/tag release path and the repointed manual fallback; drop the NPM_TOKEN instructions - .github/workflows/reproducible-package.yml, .coderabbit.yaml: base branches - CLAUDE.md: record the fork's branch model (`codepress-main` releases, `main` mirrors upstream)
Canonical QA ReportVerifier revision bindingGate results
Additional gates for this change:
Inherited baseline failures
Environment blockers
Visual / browser artifactsCorrection (2026-08-22, post-judge): the original version of this section Generated by {
"schema": "freecut-qa-browser/v1",
"head": "e4daef3e1f3f4b01844c754528f6b10653f4156d",
"browser": {
"label": "system Chrome (channel: chrome)",
"version": "151.0.7922.172"
},
"generatedAt": "2026-08-22T18:50:38.355Z",
"artifacts": {
"frame.png": {
"bytes": 15682,
"sha256": "8206275c7f9794b6f22fb726923957fbde05fbb422c0cded71300306eb872cd9"
},
"harness.png": {
"bytes": 7535,
"sha256": "2360d2cc7439d7cb3ad958691f0d9ba7510d6c2ed3779538ff45d797aa15538b"
},
"console.log": {
"bytes": 1,
"sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b"
}
},
"result": "PASS"
}
git checkout e4daef3e1f3f4b01844c754528f6b10653f4156d
npm ci --ignore-scripts && npm run build
npm run qa:browser -- --skip-build
shasum -a 256 artifacts/qa/browser-e4daef3e1f3f/*.pngOverall❌ FAIL — NOT READY TO MERGE on gates 4, 10, 11 and No merge until trusted judge PASS. |
lightninglu10
commented
Aug 22, 2026
@codepress /judge-verification can you judge this verification? |
👋 Hey @lightninglu10! Spawning a verification judge session to help with your request. View session |
Dispatched by judge-verification. This judgment is READY TO MERGE at the current head. If the PR is still a draft, transition it to ready for review now with the guarded readiness transaction: Verification Judgment — PR #16 (local)Verdict: ✅ READY TO MERGE PR Head SHA: Summary: The canonical QA report is bound to the live PR head and provides clean-tree capture. The material release, host-editor, media, package, and QA surfaces in the diff have credible coverage. The four reported failures are reproduced inherited baselines, not regressions from this diff, so they are waived for this judgment. Revision binding
Diff trigger inventory
Per-item
Overall status check
What stood up
|
lightninglu10
commented
Aug 22, 2026
Report correction (form-only, per judge caveat on item 11). The judge's PASS carried one form caveat: the canonical QA report cited I have corrected the Visual / browser artifacts section of the canonical report comment above (edited in place, with the correction called out inline so the change is not silent). The correction:
No behavioral rerun was performed and none was required. Gate 18 result is unchanged: PASS 4/4 against real system Chrome 151.0.7922.172 at head Proceeding to the guarded readiness transaction. |
@codepress /judge-verification can you judge this verification? Local VerificationPR Head SHA:
Verifier revision binding
Captured from Diff Trigger Inventory
Verification Contract Results
Rows 17–20 are the four disclosed failures. They are recorded as failures in Overall: PASS |
Starting initial review of |
There was a problem hiding this comment.
The release-branch consolidation is coherent: the workflow remains OIDC-based, the package checks and consumer smoke test stay in front of publication, and the manual fallback verifies both a clean checkout and ancestry to codepress-main. The host bridge also remains the authority for supported document mutations, so this does not need an architectural split.
One nonblocking concern:
src/features/timeline/hooks/use-timeline-shortcuts.ts:64— Host mode mounts the full tool-shortcut hook, includingRfor rate stretch. That operation changes speed and may ripple adjacent clips, neither of which the host edit derivation supports. A host user can therefore enter the tool and make an edit that is immediately rolled back as unsupported. Limit host mode to tool bindings backed by the host command contract and coverRwith a host-mode shortcut test.
Approving because this is a bounded, recoverable host-mode interaction rather than a release or data-integrity failure.
Reviewed `7799d29f`...`e4daef3e`. Diff collection and GitHub posting were handled by the automation.Review head |
Uh oh!
There was an error while loading. Please reload this page.
Part of quantfive/codepress#5858 (Video editor v1) — item release.
codepress-mainbecomes the single release branch for@quantfive/freecut-editor-surface.stagingstops being a release target.Why
Two branches disagreed about how the package is released:
codepress-main@7799d29fhad the working publish path: npm trustedpublishing (OIDC —
id-token: write,npm publish --provenance), an"already published → no-op" guard, and triggers on push to
codepress-mainplus
freecut-editor-surface-v*tags. It shipped 0.3.1 on 2026-08-20.staging@47e2db3ccarried all the host-backed editor work (23 commitsahead, merge-base
4d62e808) and the QA tooling, but its copy of thepublish workflow still used
secrets.NPM_TOKEN— a secret that does notexist on this repo, so tag publishes from
stagingcould never work.What this does
1. Merges
stagingintocodepress-mainas a real merge commit(
e4ca524e) — history preserved, no squash, no rebase ofstaging.Conflict resolution (12 conflicted paths):
.github/workflows/publish-editor-surface.yml— keptcodepress-main'sOIDC version as the base and folded in the one thing
staging's copy hadthat it lacked (the explicit
--registry=https://registry.npmjs.orgflagand its manifest-drift comment). Every other step — provenance verify, tag
validation, deterministic pack, consumer smoke — was already present and
identical. All
NPM_TOKEN/NODE_AUTH_TOKENtraces are gone, along withthe now-pointless
packages: writepermission. The staleif: github.event_name == 'push' || github.ref_name == 'staging'guard andits "manual runs are limited to staging" comment now name
codepress-main..github/workflows/sync-upstream.yml— no conflict, and the brief'spremise here was wrong:
stagingnever deleted this file, it never hadit.
git log --oneline --all -- .github/workflows/sync-upstream.ymlshowsit was only ever added on the
codepress-mainside (efc6b07c,7799d29f), after the4d62e808merge-base. The merge keeps it, which iswhat we want — it is what holds
mainas a clean upstream mirror.package.json,packages/freecut-editor/{package,README},provenance/*,scripts/package-editor-surface.mjs,src/features/editor/{components/editor.tsx,host/*}) — tookstaging'scontent. Verified per file that
stagingis a strict superset, not adivergence:
codepress-main's copies are the same work squashed into7799d29f, andstagingcarries the later fixes (fix(editor): skip keyframe cache persistence when no workspace root (host-mounted) #12, fix(editor): host bridge round-trip stability + host-mode timeline shortcuts #13) on top.2. Repoints every hard-coded release/base ref from
origin/stagingtoorigin/codepress-main, semantics unchanged:scripts/publish-editor-surface-npmjs.mjsSTAGING_REF→RELEASE_REF=origin/codepress-main; guard messages and the CI-fallback comment followscripts/publish-editor-surface-npmjs.test.mjsupdate-ref refs/remotes/origin/…fixture + test namesscripts/qa-head-binding.mjs,scripts/check-fallow-changed-health.mjsDEFAULT_BASE_REFdocs/qa/canonical-report.md,docs/qa/README.md,README.md,docs/pr2-provenance-and-packaging.mdcodepress-mainpackages/freecut-editor/README.mdNPM_TOKENinstructions are replaced by the trusted-publisher description, and the manual fallback is repointed.github/workflows/reproducible-package.yml,.coderabbit.yamlCLAUDE.mdcodepress-mainreleases,mainmirrors upstream) instead of upstream'smain/staging/developLeft alone deliberately:
.claude/skills/changelog/SKILL.md:66, whichmentions
staging/developwhile describing upstream's branch model forchangelog drafting — not this fork's release ref.
No version bump, no tags.
The workflow triggers on push to
codepress-main, andstagingbringspackages/freecut-editor/package.jsonto 0.3.2 (currently published:0.3.1). So merging this publishes 0.3.2 via OIDC, and merging
#15 afterwards will publish 0.3.3. That is the intended
design — a merge that does not bump the version is a no-op, not a failure,
because of the already-published guard.
If you would rather publish only from tags, the one-line change is deleting
the
branches:block from theon: push:trigger. Offering it, not doing it.Follow-ups, not in this PR:
codepress-mainnorstaginghas any branch protection today. Nowthat
codepress-mainis the release branch and every push to it publishesto a public registry, it should get some.
.github/workflows/ci.ymlruns onpull_requestand on push tomainonly — it never listed
staging, so there was nothing to repoint. If youwant push-CI on the release branch too, that is a separate one-liner. (The
fallow ratchet in CI is unaffected either way: it passes
--base origin/${{ github.base_ref }}explicitly, so it already binds tocodepress-mainfor PRs like this one;DEFAULT_BASE_REFin the scriptsonly governs local runs.)
Verification
Run at head
e4daef3efrom a clean tree.npm ci --ignore-scriptsnpm run qa:binding -- --check7799d29f(origin/codepress-main)npm run check(typecheck)npm run lintnpm run buildnpm run verify:provenancenpm run package:editor-surfacefreecut-editor-surface-0.3.2.tgz, sha25672516a0f…3713f6npm run test:editor-surface:consumer -- --artifact <tgz>npm run test:publish-editor-surface-guardsorigin/codepress-mainnpm run check:changed-healthorigin/codepress-mainnpm run check:boundaries/:deps-contracts/:legacy-lib-imports/:deps-wrapper-health/:edge-budgetsnpm run check:qa-redactionnpm run headless:test:nodenpm run headless:test:chromenpm run qa:browser -- --skip-buildactionlint(all workflows)node --checkon the 4 changed.mjsInherited baseline failures (reproduced at base
7799d29f, not caused bythis PR — recorded per
docs/qa/README.md, not papered over):npm run check:unused-exports— ❌ FAIL: 129 findings, 10 new, 3 stale.Byte-identical counts at base
7799d29f. Not touching the allowlist(ratchet baseline).
npm run check:unused-class-members— ❌ FAIL: 83 findings, 2 stale.Byte-identical counts at base
7799d29f. Same rule.npm run format:check— ❌ FAIL: 169 files. Identical count at the mergecommit before my repoint; none of the files this PR touches are among them.
npm run test:run— ❌ FAIL: the known full-suite jsdom/localStoragebaseline (
Cannot read properties of undefined (reading 'setItem')fromzustand persist). Base
7799d29f: 469 failed / 4057 passed.origin/staging: 469 failed / 4080 passed. This head: 584 failed / 4278passed — the counts move run to run because the uncaught exceptions abort
test files at different points. Nothing in this diff can affect it:
git diff origin/staging HEADtouches 14 files, none undersrc/,headless/,packages/freecut-editor/src/,package.json,package-lock.json, orprovenance/. The merged tree is byte-identical toorigin/stagingeverywhere the test suite reads.Not done, on purpose
ve-*branches are untouched — the orchestrator retargetsthose after this merges.