Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
### English

- CI: a green job no longer carries a failure annotation for `attw`. The type-resolution check (`scripts/check-package-lint.mjs`) judges attw's JSON report and ignores its exit code, because attw exits 1 for every finding, including the by-design ones it allows; `tests/ci-annotate.mjs` still published that exit as "exited with code 1" on every Node job since v0.13.0, and an agent reading the public annotations could not tell it from a real failure. A child whose exit code the caller checks itself now runs with `expectedExitEnv()` from `scripts/gh-actions.mjs`, and the hook stays silent for it. Gate: `tests/release-automation.mjs`.
- Counter, Blur Text and Text Split inside a panel that opens and closes follow the same rules as Reveal (GSAP path). A Counter in a closed tab counted while nobody could see it — 3454 of 5000 by the time the tab opened, or the final value with no count at all — and a `once:false` Text Split did not replay when its panel reopened. The rules now live in `src/utils.js` (`hasBox()`, `watchBox()`, and `panelGate()` for an entrance a ScrollTrigger starts), and Reveal imports them. `watchBox()` also watches the nearest non-inline ancestor: ResizeObserver reports an inline element — a `<span>` headline, the usual home of Blur Text and Text Split — as 0×0 whether it is shown or not, so a panel opening around one went unnoticed. New gate `tests/browser/panel-entrances.mjs` (both browser lanes): Blur Text, Text Split, Counter plain and slot, and an inline Reveal must play on the first open, replay on every reopen with `once:false`, stay put with `once:true`, and a closed counter must wait at 0; the previous code fails two of its checks. Size ceilings follow the budget rule: `kineto.js` 654 KB raw / 178 KB gzip, `kineto.min.js` 511 / 158, UMD 509 / 158, and the Vue consumer entry 183 KB.
- Reveal inside a panel that opens and closes (a tab, an accordion, a dialog) plays its entrance when the panel opens — the first time with `once:true`, every time with `once:false` — with GSAP and without it. An element in a closed panel has no layout box: ScrollTrigger measured it as a 0×0 box at the top of the viewport, so a default (`once:true`) entrance with GSAP ran while nobody could see it and the content just appeared when the panel opened; a `once:false` entrance showed only the tail of a replay (GSAP 80%, native 58–67% already in) or none at all. The Bootstrap example showed it: the second time a FAQ answer or the offer dialog opened, its content appeared with no entrance. Now nothing plays while the element has no box, losing the box puts a repeatable (or not yet seen) entrance back to its start at once, and getting it back re-measures the trigger and plays the entrance once the element is on screen; a panel closing fires no `onLeave`. All four paths follow the same rules (GSAP tween, the mask/wipe/clock clock, class-only, native). New gate `tests/browser/reveal-panel-reopen.mjs` (both browser lanes, with and without GSAP; the old code fails five of its checks). The rules cost about 0.5 KB gzip, so the product bundle ceilings move by the budget rule to 177 KB full/all, 181 KB React and 182 KB Vue.
- Slider no longer stops short of its slide when a frame timestamp repeats. The track loop measured each frame as `time - lastFrameTime`, which is 0 when requestAnimationFrame hands the same timestamp twice, so a carousel fed repeated timestamps stood still (a fade at 80%) while still requesting frames; the radial loop measured `time` against `performance.now()` and could not finish either. Both now use `frameClock()` + `frameEase()` like every other following loop (a repeated or backward timestamp counts as one frame, one callback advances at most `MAX_FRAME_STEP_MS`). This is the likely cause of two WebKit CI failures in which a `dissolve` stopped at 98.1% — exactly the same value both times, so not a render stall. Gate: `tests/browser/motion-timing.mjs` feeds a track and a radial slider 90 callbacks with the same timestamp; the old track stays at 80%.
- Tests: `slider-variant-browser` waits count rendered frames as well as time and report the frame count, the longest gap, how often the requestAnimationFrame timestamp repeated and the page's errors when they give up. `components-a11y` `sheet-close-button` read the sheet's `hidden` after a fixed 150 ms; it now polls until the 40 ms close animation has finished (at most 2 s), which is what failed a WebKit CI attempt. `stylize-patterns` checks each motion at 250, 450 and 650 ms instead of once at 650 ms: a periodic motion (`flow` repeats every cell of travel, about 390 ms at the tested speed) can land on its first frame at a single sample point, and `dither-noise:flow` failed two Firefox CI attempts that way before passing; `none` must now hold still at all three.
- Tests: `slider-variant-browser` waits count rendered frames as well as time and report the frame count, the longest gap, how often the requestAnimationFrame timestamp repeated and the page's errors when they give up. `components-a11y` `sheet-close-button` read the sheet's `hidden` after a fixed 150 ms; it now polls until the 40 ms close animation has finished (at most 2 s), which is what failed a WebKit CI attempt. `stylize-patterns` checks each motion at 250, 450 and 650 ms instead of once at 650 ms: a periodic motion (`flow` repeats every cell of travel, about 390 ms at the tested speed) can land on its first frame at a single sample point, and `dither-noise:flow` failed two Firefox CI attempts that way before passing; `none` must now hold still at all three. `flip-fold` read the number of pinned copies once, 1.2 s after a 0.6 s change; a WebKit CI attempt still had all 12 then (their animations had not finished) and passed on the retry. It now polls until they are gone (at most 5 s) and reports how long that took. The Bootstrap integration QA's accordion wait timed out about once in fifteen runs on a busy machine: Bootstrap's `scroll-behavior: smooth` animates the scroll Playwright makes for the click, which could settle with the opened answer just below the fold, where Reveal rightly waits. The test now scrolls the answer on screen first (0 failures in 30 loaded runs) and reports the answer's trigger and tween state if the wait gives up.

### 한국어

- CI: 초록 job에 `attw` 실패 annotation이 더는 붙지 않습니다. 타입 해석 검사(`scripts/check-package-lint.mjs`)는 attw의 JSON 보고서로 판단하고 종료 코드는 무시합니다. attw는 허용하는 설계상 항목까지 포함해 찾은 것이 있으면 1로 끝나기 때문입니다. 그런데 `tests/ci-annotate.mjs`는 v0.13.0 이후 모든 Node job에서 그 종료를 "exited with code 1"로 공개했고, 공개 annotation을 읽는 에이전트는 진짜 실패와 구분할 수 없었습니다. 이제 호출한 쪽이 종료 코드를 직접 판단하는 자식 프로세스는 `scripts/gh-actions.mjs`의 `expectedExitEnv()`로 실행되고, 훅은 그 프로세스에 대해 조용합니다. 게이트: `tests/release-automation.mjs`.
- 여닫는 패널 안의 Counter·Blur Text·Text Split도 Reveal과 같은 규칙을 따릅니다(GSAP 경로). 닫힌 탭의 Counter는 아무도 못 보는 사이에 세어서, 탭을 열면 5000 중 3454부터 이어지거나 세는 모습 없이 끝 숫자만 보였습니다. `once:false` Text Split은 패널을 다시 열어도 재생되지 않았습니다. 규칙은 이제 `src/utils.js`에 있고(`hasBox()`, `watchBox()`, ScrollTrigger가 시작하는 등장을 위한 `panelGate()`), Reveal도 이를 가져다 씁니다. `watchBox()`는 인라인이 아닌 가장 가까운 조상도 함께 봅니다. ResizeObserver는 인라인 요소(Blur Text·Text Split이 흔히 붙는 `<span>` 제목)를 보이든 안 보이든 0×0으로 알려서, 그 주위에서 패널이 열려도 알아채지 못했기 때문입니다. 새 게이트는 `tests/browser/panel-entrances.mjs`입니다(두 브라우저 레인). Blur Text, Text Split, Counter plain·slot, 인라인 Reveal이 처음 열 때 재생되고, `once:false`는 다시 열 때마다 재생되며, `once:true`는 가만히 있고, 닫힌 Counter는 0에서 기다려야 합니다. 이전 코드는 이 중 두 검사에서 실패합니다. 크기 상한은 예산 규칙대로 옮깁니다: `kineto.js` raw 654 KB / gzip 178 KB, `kineto.min.js` 511 / 158, UMD 509 / 158, Vue 소비자 엔트리 183 KB.
- 열고 닫는 패널(탭·아코디언·대화상자) 안의 Reveal이 패널이 열릴 때 등장을 재생합니다. `once:true`는 처음 한 번, `once:false`는 열 때마다이고, GSAP이 있든 없든 같습니다. 닫힌 패널 안의 요소에는 레이아웃 상자가 없습니다. ScrollTrigger는 이를 뷰포트 맨 위의 0×0 상자로 쟀기 때문에, GSAP이 있으면 기본(`once:true`) 등장이 아무도 못 보는 사이에 끝나 패널을 열면 내용이 그냥 나타났습니다. `once:false` 등장은 다시 열 때 끝부분만 보이거나(GSAP 80%, native 58~67% 진행된 상태) 아예 재생되지 않았습니다. Bootstrap 예제에서도 FAQ 답변이나 혜택 대화상자를 두 번째로 열면 등장 없이 내용만 나타났습니다. 이제 상자가 없는 동안에는 아무것도 재생하지 않고, 상자를 잃으면 반복 등장(또는 아직 보이지 않은 등장)을 즉시 시작 상태로 되돌리며, 상자를 다시 얻으면 trigger를 다시 재고 요소가 화면에 들어왔을 때 등장을 재생합니다. 패널이 닫혀도 `onLeave`는 부르지 않습니다. 네 경로(GSAP tween, mask·wipe·clock 시계, class 전용, native)가 같은 규칙을 따릅니다. 새 게이트는 `tests/browser/reveal-panel-reopen.mjs`입니다(두 브라우저 레인, GSAP 유무). 옛 코드는 검사 다섯 개에서 실패합니다. 이 규칙이 약 0.5 KB gzip을 더해, 예산 규칙대로 제품 번들 상한을 full·all 177 KB, React 181 KB, Vue 182 KB로 옮깁니다.
- 프레임 타임스탬프가 반복되어도 Slider가 슬라이드 앞에서 멈추지 않습니다. 트랙 루프는 한 프레임을 `time - lastFrameTime`으로 쟀는데, requestAnimationFrame이 같은 타임스탬프를 두 번 주면 이 값이 0이라, 반복된 타임스탬프를 받은 캐러셀은 프레임을 계속 요청하면서도 제자리에 멈춰 있었습니다(fade 80%). 방사형 루프는 `time`을 `performance.now()`와 비교해 역시 끝나지 못했습니다. 이제 둘 다 다른 추종 루프처럼 `frameClock()` + `frameEase()`를 씁니다(반복되거나 거꾸로 간 타임스탬프는 한 프레임으로 치고, 한 번에 `MAX_FRAME_STEP_MS`까지만 진행). WebKit CI에서 `dissolve`가 98.1%에 멈춘 실패 두 번의 원인으로 보입니다. 두 번 모두 값이 완전히 같았으므로 렌더링 정지가 아닙니다. 게이트: `tests/browser/motion-timing.mjs`가 트랙·방사형 슬라이더에 같은 타임스탬프로 콜백 90번을 줍니다. 옛 트랙은 80%에 머뭅니다.
- 테스트: `slider-variant-browser`의 대기는 시간과 함께 그린 프레임 수도 세고, 포기할 때 프레임 수·가장 긴 간격·requestAnimationFrame 타임스탬프가 반복된 횟수·페이지 오류를 알려 줍니다. `components-a11y` `sheet-close-button`은 고정 150ms 뒤 시트의 `hidden`을 읽다가 WebKit CI 시도 하나에서 실패했고, 이제 40ms 닫힘 애니메이션이 끝날 때까지(최대 2초) 확인합니다. `stylize-patterns`는 각 motion을 650ms 한 번 대신 250·450·650ms에 봅니다. 주기적인 motion(`flow`는 셀 하나만큼 이동할 때마다 반복, 이 속도에서 약 390ms)은 표본 한 점에서 첫 프레임으로 돌아올 수 있고, Firefox CI에서 `dither-noise:flow`가 그렇게 두 번 실패한 뒤 통과했습니다. `none`은 이제 세 점 모두에서 멈춰 있어야 합니다.
- 테스트: `slider-variant-browser`의 대기는 시간과 함께 그린 프레임 수도 세고, 포기할 때 프레임 수·가장 긴 간격·requestAnimationFrame 타임스탬프가 반복된 횟수·페이지 오류를 알려 줍니다. `components-a11y` `sheet-close-button`은 고정 150ms 뒤 시트의 `hidden`을 읽다가 WebKit CI 시도 하나에서 실패했고, 이제 40ms 닫힘 애니메이션이 끝날 때까지(최대 2초) 확인합니다. `stylize-patterns`는 각 motion을 650ms 한 번 대신 250·450·650ms에 봅니다. 주기적인 motion(`flow`는 셀 하나만큼 이동할 때마다 반복, 이 속도에서 약 390ms)은 표본 한 점에서 첫 프레임으로 돌아올 수 있고, Firefox CI에서 `dither-noise:flow`가 그렇게 두 번 실패한 뒤 통과했습니다. `none`은 이제 세 점 모두에서 멈춰 있어야 합니다. `flip-fold`는 0.6초 변경 뒤 1.2초에 고정된 복사본 수를 한 번 읽었는데, WebKit CI 시도 하나에서 그때까지 12개가 모두 남아 있었고(애니메이션이 아직 끝나지 않음) 재시도로 통과했습니다. 이제 모두 없어질 때까지(최대 5초) 확인하고 걸린 시간을 알려 줍니다. Bootstrap 통합 QA의 아코디언 대기는 바쁜 기계에서 15번에 한 번쯤 시간 초과였습니다. Bootstrap의 `scroll-behavior: smooth`가 Playwright의 클릭 스크롤을 애니메이션해, 열린 답변이 화면 바로 아래에 멈출 수 있었고 Reveal은 그 자리에서 올바르게 기다렸습니다. 이제 테스트가 답변을 먼저 화면에 가져오고(부하 30회 실패 0), 대기가 실패하면 답변의 trigger·tween 상태를 알려 줍니다.

## [0.13.1] - 2026-09-29

Expand Down
Loading
Loading