Skip to content

chore: fold the v0.2.0 release-audit follow-ups into the release#33

Merged
yultyyev merged 4 commits into
feat/timeline-engine-phase0from
chore/v0.2.0-release-polish
Jun 18, 2026
Merged

chore: fold the v0.2.0 release-audit follow-ups into the release#33
yultyyev merged 4 commits into
feat/timeline-engine-phase0from
chore/v0.2.0-release-polish

Conversation

@yultyyev

Copy link
Copy Markdown
Collaborator

Folds all 7 follow-ups from the pre-release audit into v0.2.0 so the release ships complete, rather than tracking them for v0.2.1. Three are correctness fixes with regression tests; the rest are docs/cleanup. Stacked on the integration branch; merge before phase0 → main.

Code fixes (with regression tests)

  • fix(timeline): follow symlinks when confining agent/UI paths. resolveInWorkspace checked containment on the lexical path, so an in-workspace symlink pointing out of the tree was followed at FFmpeg-open time. Now canonicalizes both sides via an lstat walk that follows symlinks explicitly (incl. dangling links → their real out-of-tree target) and fails closed on EACCES/symlink-loops; only a genuine ENOENT is a not-yet-created tail. A symlinked workspace root (macOS /var/private/var) is collapsed so legit paths aren't rejected. Residual (documented): open-time TOCTOU needs O_NOFOLLOW, out of scope.
  • fix(timeline): widen the applyVerbs retry to MAX_COMMIT_ATTEMPTS. The re-lower loop was capped at a hardcoded 4 while the inner commit loop allows 8, so a burst of >4 concurrent edits dropped its tail. Latent today (no concurrent caller of the verbs path ships), closed before the API gains one. expectedBaseRev stays load-bearing. Deterministic barrier-based race test added.
  • fix(timeline): drop per-clip fades only on real transition boundaries. Suppressing a per-clip fade on a transition cut (to avoid double-darkening) over-fired on a transition keyed to the last clip — which the fold never xfades — silently dropping a fade-to-black to a hard cut (reachable by removeClip on the final clip). Now gated on a following clip existing.

Docs / cleanup

  • Document the clip timeline workflow in README, SKILL.md, and the top-level clip --help (it was reachable but unlisted).
  • Reframe the source-of-truth narrative: the Composition document is canonical; session.json is the op log (was "the session is the source of truth", contradicting the code + AGENTS.md feat: walking skeleton — clip CLI + Claude Code skill (trim tool) #2).
  • Refresh the stale "356 tests" → 539 (README badge + FAQ, llms.txt).
  • Remove competitor brand names from the TransitionKind JSDoc (it ships in dist/index.d.ts); the README/llms comparison prose keeps its intentional naming.
  • Drop the drifting SKILL.md frontmatter version field.

Verification

These fixes were found by an adversarial review of the first cut of this branch (each finding double-verified by reproduce + refute lenses); all three confirmed findings are fixed here and covered by new tests.

Gate green: pnpm type-check, pnpm test (539 pass), pnpm lint (7 pre-existing warnings only), pnpm build; npm pack surface re-checked brand-free.

🤖 Generated with Claude Code

yultyyev added 4 commits June 18, 2026 10:40
…orkspace

resolveInWorkspace checked containment on the lexically-resolved path, so an
in-workspace symlink pointing out of the tree was followed at FFmpeg-open time —
the boundary it exists to enforce (AGENTS.md non-negotiable #3) leaked.

Canonicalize both the workspace and the resolved path before comparing: walk the
existing prefix with lstat, follow symlinks explicitly (so a DANGLING in-workspace
link resolves toward its real, possibly out-of-tree, target instead of looking
like an in-workspace leaf), and treat only a genuine ENOENT as a not-yet-created
tail — EACCES / symlink loops fail closed. A symlinked workspace root (macOS
/var -> /private/var) is collapsed too, so legitimate paths aren't rejected. The
lexical resolved path is still returned, so an in-workspace symlink to an
in-workspace target keeps working.

Residual (documented): an open-time TOCTOU race needs O_NOFOLLOW at the open
site, out of scope here.
applyVerbs re-lowers and retries on a CompositionConflictError (the optimistic
expectedBaseRev guard) but capped the loop at a hardcoded 4, while the inner
commit loop allows MAX_COMMIT_ATTEMPTS (8). A burst of >4 concurrent edits against
the same base rev therefore dropped its tail: the 5th+ writer exhausted its
retries and lost the edit. Align the cap to MAX_COMMIT_ATTEMPTS so each concurrent
edit re-lowers against fresh state and lands.

Latent today (the shipping UI has no concurrent caller of the verbs path), but
closed before the API gains one. expectedBaseRev stays load-bearing — the guard
is what forces the re-lower. Adds a deterministic regression test that races a
burst through a barrier so each writer needs an increasing attempt count.
A per-clip fadeIn/fadeOut on the same cut as a transition double-darkened (the
xfade already blends that window). Suppress the fade on a transition boundary so
the xfade owns the blend — but only when the fold actually emits an xfade there.
A transition keyed to the LAST clip (e.g. left dangling after the next clip was
removed) blends nothing, so gate fadeOut suppression on a following clip existing;
otherwise a fade-to-black at the timeline end was silently dropped to a hard cut.
Outer-boundary fades and fades on plain cuts are untouched.

Adds regression tests for the matched-boundary case and the dangling-transition
(last-clip) case.
- Document the `clip timeline` composition workflow (new → add-media →
  transition → show → export, with undo/redo and read-only show/at/frame) in
  README, SKILL.md, and the top-level `clip --help` (it was reachable but unlisted).
- Reframe the source-of-truth narrative: the Composition document is canonical
  for assembled edits; session.json is the append-only op log of tool calls
  (was "the session is the source of truth", which contradicted the code and
  AGENTS.md non-negotiable #2).
- Refresh the stale "356 tests" claim to 539 (README badge + FAQ, llms.txt).
- Remove competitor brand names from the TransitionKind JSDoc (it ships in
  dist/index.d.ts); the README/llms comparison prose keeps its intentional naming.
- Drop the hand-maintained, drifting SKILL.md frontmatter version field.
@yultyyev
yultyyev merged commit 8745b9e into feat/timeline-engine-phase0 Jun 18, 2026
1 check passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant