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
26 changes: 21 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@

### English

<!-- Add matching English release bullets here. -->

### 한국어

<!-- 위 영문과 대응하는 한국어 릴리스 항목을 여기에 추가합니다. -->
- First published 0.13 release. `v0.13.0` was tagged, but its release audit failed on a new advisory for a dev dependency (undici, see below), so nothing was published under that tag: npm stayed on 0.12.3, while the demo site, which deploys from `main`, already showed 0.13.0. Everything listed under 0.13.0 ships in this version.
- Reveal's `mask`, `wipe` and `clock` entrances without GSAP no longer skip their motion after one long frame. Their clock added the whole gap between frames, so a 300 ms frame during a busy page load finished a short entrance at once and the element simply appeared; one frame now advances at most four 60 Hz frames (`MAX_FRAME_STEP_MS` in `src/utils.js`, the same limit `frameEase()` already used), so the entrance slows for a moment and stays visible. A new case in `tests/reveal-variant-browser.mjs` puts two 300 ms frames at the start of a native mask and wipe; the old clock showed 0 frames in motion, the new one 7.
- Tests: the WebKit browser shard that included these checks took 60–140 s longer than the others on every CI run, because two tests failed their first attempt and ran again. `reveal-variant-browser` now samples until every entrance has completed (at least 450 ms, at most 2.5 s) instead of a fixed 450 ms, and `components-a11y` `tilt-rest` waits for the tilt to settle (at most 4 s) instead of a fixed 1.2 s — the default smoothing needs about a second of 60 Hz frames, so the old wait left no room on a slower runner. A failure now reports how long after the last move the tilt was still requesting frames.
- `Kineto.scan()` works in jsdom 30 and later with a large registry. Discovery joins every module's `[data-kt-*]` selector into one query; jsdom 30 rejects a selector longer than 2048 characters (browsers have no limit) and threw a `RangeError` from `autoInit()` in an app's unit tests once enough custom modules were registered. The list is now split into selectors under that limit, found elements are merged once each and kept in document order, and the built-in registry (about 1.1 KB) is still one traversal. `tests/performance-runtime.mjs` emulates the limit: 100 modules take two traversals, an element matched by two selectors is created once per module, and each module still creates top to bottom.
- Squircle created inside a hidden panel (a closed tab, accordion or dialog) now takes its shape when the panel opens in Chromium. The native `corner-shape` path measured the element once at create, found a 0×0 box, drew nothing and never looked again, because only the polyfill path watched for resizes; a percentage `cornerRadius` also stayed at its first pixel value when the element resized. Both paths now observe the box (an unchanged box is skipped). `tests/browser/squircle.mjs` creates both paths in `display:none` and resizes a `50%` radius; the old native path fails it.
- `npm run audit:hidden-panel` (`scripts/audit-hidden-panel.mjs`) creates every module that has demo markup three times — visible, visible again, and inside `display:none` shown 80 ms later — and lists the ones whose boxes, node count or inline style end up different only because they were created hidden. It found the Squircle case above; after the fix, 51 modules compare equal in Chromium, WebKit and Firefox (Counter and Page Transition vary between two plain runs and are skipped; Tilt's identity transform after resume is listed as known; in WebKit, Flip's gallery of lazy images needs `KT_SETTLE_MS=3500`, since a hidden copy only starts loading them when shown). It is a tool to run when a change touches how a module measures itself, not a CI gate.
- A native Reveal `slide-left`/`slide-right` created inside a hidden panel now plays when the panel opens. Without GSAP (blocked or offline, or a data-saver device on the low performance tier) Reveal decides "in view" with an IntersectionObserver on the element, and a slide waits one whole element width aside; on a page that clips horizontal overflow (`overflow-x: clip` on html/body) a wide card showed the observer only a sliver of that moved box, under the 10% threshold, so opening the panel produced no report and the card stayed invisible in Chromium, WebKit and Firefox. A ResizeObserver now also wakes the check when the element's size changes (from 0×0 when the panel opens); the decision is still made on the unmoved box. The same check now resolves a percentage `rootMargin` on top/bottom against the viewport height, as IntersectionObserver does (it used the width). New gate `tests/browser/reveal-hidden-panel.mjs` (both browser lanes, GSAP's CDN blocked); the old code leaves both cards at opacity 0. `npm run audit:hidden-panel -- --each` checks every distinct demo variant, not only the first (291 in Chromium; none differ after this fix).
- `release:ship` runs the all-lockfile audit after CI passes and before it creates the tag. The release workflow runs that audit only after the tag exists, so an advisory published between CI and the tag used the version up: `v0.13.0` was tagged on a green commit, then a new advisory for undici (a dev dependency, through jsdom) failed the release audit and nothing reached npm. A failed audit now stops before the tag with the version still available. Gate: `tests/release-automation.mjs`.
- Root lockfile: undici 7.29.0 → 7.30.0 (jsdom's `^7.25.0` range), clearing GHSA-rfgv-xxqx-mfg5, GHSA-w293-vg96-wgc3 and eight lower-severity undici advisories. It is only used by the Node tests; the package has no runtime dependencies.

### 한국어

- 처음 배포되는 0.13 릴리스입니다. `v0.13.0` 태그는 만들어졌지만, 개발 의존성(undici, 아래 참고)의 새 advisory 때문에 릴리스 감사가 실패해 그 태그로는 아무것도 배포되지 않았습니다. npm은 0.12.3 그대로였고, `main`에서 배포되는 데모 사이트는 이미 0.13.0을 보여 주고 있었습니다. 0.13.0에 적힌 내용은 모두 이 버전에 들어 있습니다.
- GSAP 없이 도는 Reveal `mask`·`wipe`·`clock` 등장이 긴 프레임 하나 뒤에 움직임을 건너뛰지 않습니다. 시계가 프레임 사이 간격을 통째로 더해서, 바쁜 페이지 로드 중 300ms 프레임 하나가 짧은 등장을 한 번에 끝냈고 요소는 그냥 나타나기만 했습니다. 이제 프레임 하나는 60Hz 4프레임까지만 나아가므로(`src/utils.js`의 `MAX_FRAME_STEP_MS`, `frameEase()`가 이미 쓰던 한도와 같음) 등장은 잠깐 느려질 뿐 계속 보입니다. `tests/reveal-variant-browser.mjs`에 native mask·wipe의 시작 직후 300ms 프레임 두 개를 넣는 케이스를 추가했습니다. 옛 시계는 움직이는 프레임이 0개, 새 시계는 7개였습니다.
- 테스트: 이 검사가 들어 있는 WebKit 브라우저 shard는 CI마다 다른 shard보다 60~140초 오래 걸렸습니다. 테스트 두 개가 첫 시도에서 실패해 다시 돌았기 때문입니다. `reveal-variant-browser`는 고정 450ms 대신 모든 등장이 끝날 때까지(최소 450ms, 최대 2.5초) 표본을 읽고, `components-a11y`의 `tilt-rest`는 고정 1.2초 대신 틸트가 멈출 때까지(최대 4초) 기다립니다. 기본 smoothing은 60Hz로 약 1초가 걸려서, 옛 대기는 느린 runner에서 여유가 없었습니다. 실패하면 마지막 움직임 뒤 몇 ms까지 프레임을 요청했는지 메시지에 남습니다.
- 모듈을 많이 등록해도 `Kineto.scan()`이 jsdom 30 이상에서 동작합니다. 탐색은 모든 모듈의 `[data-kt-*]` selector를 하나로 이어 한 번에 찾는데, jsdom 30은 2048자보다 긴 selector를 거부합니다(브라우저에는 한도가 없음). 그래서 사용자 모듈이 충분히 많으면 앱의 단위 테스트에서 `autoInit()`이 `RangeError`를 냈습니다. 이제 목록을 한도 안의 selector 여러 개로 나누고, 찾은 요소는 한 번씩만 합쳐 문서 순서를 유지합니다. 기본 레지스트리(약 1.1 KB)는 여전히 한 번에 찾습니다. `tests/performance-runtime.mjs`가 한도를 흉내 냅니다: 모듈 100개는 두 번 찾고, 두 selector에 걸리는 요소도 모듈마다 한 번만 만들며, 각 모듈은 여전히 위에서 아래 순서로 만듭니다.
- 숨긴 패널(닫힌 탭·아코디언·대화상자) 안에서 만든 Squircle이 Chromium에서도 패널이 열리면 모양을 갖습니다. 네이티브 `corner-shape` 경로는 만들 때 한 번만 쟀는데, 그때 0×0이라 아무것도 그리지 않고 다시 보지 않았습니다. 크기 변화를 지켜보는 건 polyfill 경로뿐이었고, 퍼센트 `cornerRadius`도 요소 크기가 바뀌어도 처음 픽셀 값에 머물렀습니다. 이제 두 경로 모두 상자를 지켜봅니다(크기가 그대로면 건너뜀). `tests/browser/squircle.mjs`가 두 경로를 `display:none` 안에서 만들고 `50%` 반지름을 리사이즈합니다. 옛 네이티브 경로는 이 검사에서 실패합니다.
- `npm run audit:hidden-panel`(`scripts/audit-hidden-panel.mjs`)은 데모 마크업이 있는 모든 모듈을 세 번 만듭니다: 보이게, 다시 보이게, `display:none` 안에서 만든 뒤 80ms 후 보이게. 그리고 숨겨서 만들었다는 이유만으로 상자·노드 수·인라인 style이 달라진 모듈을 알려 줍니다. 위 Squircle 문제를 이 스크립트로 찾았고, 수정 뒤 Chromium·WebKit·Firefox 모두 51개 모듈이 같게 나옵니다(Counter와 Page Transition은 두 번의 일반 실행끼리도 달라서 건너뛰고, Tilt의 재개 뒤 항등 transform은 알려진 차이로 표시. WebKit에서 Flip의 lazy 이미지 갤러리는 숨긴 복사본이 보일 때에야 이미지를 받기 시작해 `KT_SETTLE_MS=3500`이 필요). 모듈이 자기 크기를 재는 방식을 바꿀 때 돌리는 도구이고, CI 게이트는 아닙니다.
- 숨긴 패널 안에서 만든 네이티브 Reveal `slide-left`/`slide-right`가 패널이 열리면 재생됩니다. GSAP이 없으면(차단·오프라인, 또는 데이터 절약 기기의 low 성능 단계) Reveal은 요소의 IntersectionObserver로 "화면에 들어왔는지"를 판단합니다. 그런데 slide는 요소 너비만큼 옆에서 기다립니다. 가로 넘침을 자르는 페이지(html/body `overflow-x: clip`)에서는 넓은 카드일수록 옮겨진 상자가 관찰자에게 조금밖에 보이지 않아 10% 기준에 못 미쳤습니다. 그래서 패널을 열어도 알림이 오지 않았고, Chromium·WebKit·Firefox 모두에서 카드가 계속 보이지 않았습니다. 이제 요소 크기가 바뀌면(패널이 열리며 0×0에서 커질 때) ResizeObserver도 판단을 다시 깨웁니다. 판단은 여전히 옮기지 않은 상자로 합니다. 같은 판단에서 위/아래 퍼센트 `rootMargin`을 IntersectionObserver처럼 뷰포트 높이 기준으로 계산합니다(전에는 너비 기준). 새 게이트는 `tests/browser/reveal-hidden-panel.mjs`입니다(두 브라우저 레인, GSAP CDN 차단). 옛 코드는 두 카드를 opacity 0으로 남깁니다. `npm run audit:hidden-panel -- --each`는 첫 번째만이 아니라 데모의 모든 변형을 검사합니다(Chromium 291개, 이 수정 뒤 차이 없음).
- `release:ship`이 CI 통과 뒤, 태그를 만들기 전에 모든 lockfile 감사를 돌립니다. 릴리스 워크플로는 태그가 생긴 뒤에야 이 감사를 해서, CI와 태그 사이에 공개된 advisory 하나가 버전 번호를 써 버렸습니다. `v0.13.0`은 초록 CI 커밋에 태그됐는데, 그 뒤 undici(jsdom 경유 개발 의존성)의 새 advisory 때문에 릴리스 감사가 실패해 npm에 아무것도 올라가지 않았습니다. 이제 감사가 실패하면 태그 전에 멈추고 버전은 그대로 남습니다. 게이트: `tests/release-automation.mjs`.
- 루트 lockfile: undici를 7.29.0에서 7.30.0으로 올렸습니다(jsdom의 `^7.25.0` 범위 안). GHSA-rfgv-xxqx-mfg5, GHSA-w293-vg96-wgc3와 그보다 낮은 undici advisory 8개가 해소됩니다. Node 테스트에서만 쓰이고, 패키지 자체에는 런타임 의존성이 없습니다.

## [0.13.0] - 2026-09-27

Expand Down
15 changes: 14 additions & 1 deletion docs/AI-HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ or a tag; release approval remains separate.
tall as the window just by scrolling to it): a read under 10px is now
confirmed by a ResizeObserver report on the laid-out deck before any
fallback applies (`fullpage-hidden-panel` in `components-a11y.mjs`).
Squircle's native path measured once and had no ResizeObserver, so a squircle
in a hidden tab stayed a rectangle in Chromium (`tests/browser/squircle.mjs`).
A native Reveal slide-left/right in a closed panel never played on pages
that clip horizontal overflow: its IntersectionObserver saw only a sliver of
the box it had moved aside. An observer that watches a moved or 0×0 box
needs a ResizeObserver too (`tests/browser/reveal-hidden-panel.mjs`).
`npm run audit:hidden-panel` creates every demo module visible and inside
`display:none`, and lists the ones that end up different (`--each` for every
demo variant) — run it when a change touches how a module measures itself.
- **A geometry repair snaps; only a state change animates**: first placement,
a reveal, a resize and `refresh()` write the measured position with the
transition off, and nothing is written while the element has no box (a
Expand All @@ -166,7 +175,11 @@ or a tag; release approval remains separate.
`lerp(current, target, smoothing)` per rAF, which runs about 40% further in
the same time on 120Hz screens. A numeric factor is read with
`numberOption()`, so a curve string cannot turn it into NaN.
`tests/browser/motion-timing.mjs`.
`tests/browser/motion-timing.mjs`. A clock that adds elapsed time to a
timeline caps each frame at `MAX_FRAME_STEP_MS` (four 60Hz frames): one
300ms frame during a page load otherwise finishes a short entrance before it
is ever drawn (Reveal's native mask/wipe/clock did; the "300ms frame" case in
`tests/reveal-variant-browser.mjs`).
- **Verify the change, then push once**: run the tests that cover what you
touched (`node scripts/run-lane.mjs test:browser --only <name> --repeat 3`,
three engines for new browser gates), then `npm run verify:push -- --changed`
Expand Down
Loading
Loading