Conversation
Types & correctness: - pass an initial value to every useRef (fixes 4 React 19 type errors) - give useIntersectionObserver a real lazy conditional return type, a stable callback ref, and number[] threshold support - fix useLazyState previousValue (was always undefined) - cancel pending timers on unmount in useDebouncedCallback and useResizeObserver Packaging: - collapse packages/react into src/ (removes the duplicate package name) - dual ESM + CJS build via tsdown unbundle with per-file "use client" so useObjectFit stays usable in Server Components - inline the resize emitter and drop nanoevents -> zero runtime dependencies - drop the unused react-dom peer; set peer react >=18; add engines.node >=18 Tooling & tests: - Biome-only (remove the dead ESLint config); add typecheck/lint/test scripts - add a bun:test suite: render smoke, SSR safety, debounce leak regression - rewrite publish CI for bun; add a CI workflow with a React 18/19 matrix
…e object
Folds three setState calls in the resize effect into a single atomic update,
and returns a referentially stable object (it only changes on resize instead of
on every render). Public return shape { width, height, dpr } is unchanged.
Pins GITHUB_TOKEN to `contents: read` for both jobs, resolving the CodeQL "workflow does not contain permissions" findings (publish.yml already does this).
…lback The private helper shadowed React's reserved useEffectEvent API name, which misleads readers and tooling into assuming that hook's call-site restrictions apply. It is just a ref-backed stable-identity callback with none of them. Behavior-preserving: private function, internal call sites only.
…RCE) happy-dom <20 has a critical advisory (GHSA-37j7-fg3j-429f) plus two highs. Test-only dependency; all 46 tests pass on 20.10.2.
Upgrade the (non-published) playground stack to patched majors and override a transitive: - astro ^4.16.1 -> ^6.4.4 (high reflected-XSS GHSA-wrwg-2hg8-v723 + several moderate/low advisories; pulls vite/esbuild to patched) - @astrojs/react ^3.6.2 -> ^5.0.7, @astrojs/check ^0.9.3 -> ^0.9.9 - override yaml ^2.9.0 (transitive <2.8.3 stack-overflow GHSA-48c2-rrv3-qjmp) bun audit now reports no vulnerabilities. Shipped library unaffected: 46 tests pass, typecheck + build clean.
The playground build (astro check && astro build, not in CI) had been broken independently of any published code: - hamo was pulled from the registry (typeless 0.3.2) instead of the local build, because the repo root isn't a member of its own workspaces. Link it with file:.. so it resolves to the local dist (1.0.0, with types). - Align playground on React 19 to match hamo's build types (fixes ReactNode version-skew errors from @astrojs/react 5 pulling @types/react 19). - Remove www/pages/core.astro: dead page copied from another project that imported a non-existent ~/core/ dir and a different library (tempus). astro check: 0 errors; astro build: 2 pages. Shipped library untouched.
Completes the green-build fix (prior commit carried only the core.astro removal). Link hamo via file:.. so it resolves to the local dist instead of the typeless published 0.3.2, and bump react/react-dom/@types to ^19 to match hamo's build types (clears the @astrojs/react 5 ReactNode version skew).
Nuclear-review of playground deps: - Remove lorem-ipsum: unused since core.astro was deleted (no remaining references anywhere). - typescript ^5.5.4 -> ^6.0.3: was a major behind; @astrojs/check 0.9.9 declares typescript ^5 || ^6, so astro check is compatible. - @types/react refreshed to latest 19.x patch via lockfile. astro/@astrojs/react/@astrojs/check/react/react-dom already at latest. astro check: 0 errors; build: 2 pages. Shipped library manifest unchanged.
Move the publishable hamo library off the repo root into packages/hamo so it is a real workspace member. The root is now a private workspace root whose build/typecheck/lint/test/dev scripts delegate via `bun --filter hamo`. - playground links the library with workspace:* (the file:.. hack is gone; it had been silently resolving the published hamo@0.3.2 from npm, without types). - CI: test runs via `bun run test` so the package's bunfig.toml preload loads; React-18 matrix pin targets the package with --cwd; publish runs from packages/hamo (cd packages/hamo && npm publish). - biome vcs.useIgnoreFile off (gitignore stays at root); tsconfig/biome excludes trimmed to package scope. Verified: shipped tarball byte-identical to pre-move (npm pack --dry-run vs baseline), 46 tests pass, typecheck/lint/build clean, playground builds.
Legacy sandbox whose App.jsx imported ../src/hooks/* (a path that never existed); already excluded from lint + typecheck, no live references.
This was referenced Jun 8, 2026
#13) Co-authored-by: Clément Roche <rchclement@gmail.com>
Behavior-preserving maintainability pass; typecheck/lint/test/build all green.
- useScrollTrigger: delete the always-true isNumber guard and collapse the four
repeated position-parsing blocks into one resolveAnchor helper; drop the stale
redundant debugRef; fix debug-effect deps (biome hooks-lint 9 warnings -> 0);
un-export the internal modulo.
- useTransform: remove structuredClone from getTransform (ran every scroll tick)
in favour of direct accumulation; createTransform factory for inits; stabilise
the parent-inherit subscription.
- debounce-config: extract the triplicated module-level debounce default + setter
(useRect/useResizeObserver/useWindowSize) into one factory; per-hook semantics
preserved.
- useEffectEvent: canonical useInsertionEffect + useCallback ponyfill; route the
hand-rolled callbackRef pattern in four hooks through it; tighten the generic
off any.
- useResizeObserver: null the shared observer after disconnect so a later observe
re-creates it.
- types: deps: any[] -> DependencyList; drop {} as Rect casts; useDebouncedCallback
preserves arg tuples; export DebouncedFunction from the barrel.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Ships
hamoas a real1.0.0. Four things landed on this branch:packages/hamo/, and brought in three new hooks ported from feat: useScrollTrigger, useTransform, useEffectEvent #11 (credit @clementroche).No existing public API changed shape; current imports keep working.
Summary
Types & correctness
useRef— fixes 4@types/react19 type errors (tsc --noEmitclean).useIntersectionObserver: reallazyconditional return type (removed theascast), stable callback ref,number[]threshold support.useLazyState:previousValuenow reports the real prior value (was alwaysundefined).useDebouncedCallback/useResizeObservercancel pending timers on unmount (no callbacks after teardown).Maintainability pass (whole-codebase review)
useScrollTrigger: removed a brokenisNumberguard that was always true — every keyword slipped through and only the follow-upifs rescued the result, leaving the ternary fallbacks as dead code. The four repeated position-parsing blocks collapse into oneresolveAnchorhelper. Also dropped a stale, redundantdebugRef, corrected the debug-effect dependency arrays (Biome hooks-lint now reports 0 warnings, down from 9), and un-exported the internalmodulo.useTransform: droppedstructuredClonefromgetTransform()— it ran on every scroll tick — in favour of building the accumulated transform directly; acreateTransform()factory replaces the cloned inits, and the parent-inherit subscription is now a stable identity so it isn't torn down and recreated every render.useRect,useResizeObserver, anduseWindowSizeeach carried a verbatim copy of the module-level debounce default +setDebounce. Extracted to a singledebounce-config.tsfactory; each hook keeps its own independent config instance, souseX.setDebouncesemantics are unchanged.useEffectEventrewritten to the canonicaluseInsertionEffect+useCallbackponyfill (no render-phase ref mutation); the hand-rolledcallbackRefpattern that four hooks reimplemented now routes through it. Generic constraint tightened offany.useResizeObserver: the shared observer is reset tonullafterdisconnect(), so a laterobserve()re-creates it instead of reusing a disconnected instance.deps: any[]→DependencyListacross hooks, removed the{} as Rectcasts,useDebouncedCallbacknow preserves argument tuples, andDebouncedFunctionis exported from the barrel.useRect'sscrollTop/scrollLefthelpers accumulate scroll past the intended wrapper boundary up to the document root. Left as-is pending a coordinate-space decision — it's tested behavior and changing it blindly is riskier than the cleanup is worth.New hooks — ported from #11 (@clementroche)
useScrollTrigger— scroll-progress tracking with GSAP-style position syntax, optional Lenis integration with graceful native-scroll fallback. Debug overlay shipped behind thehamo/scroll-trigger/debuggersubpath.useTransform/TransformProvider— context-based transform accumulation for parallax compensation.useEffectEvent— promoted to a public hook;use-debouncenow consumes it (removed the privateuseStableCallbackduplicate). v1's debounce implementation was kept (it had the unmount-cleanup the feat: useScrollTrigger, useTransform, useEffectEvent #11 branch lacked).Security
happy-dom→^20— fixes a critical VM context-escape RCE (GHSA-37j7-fg3j-429f) plus 2 highs in the test environment.lorem-ipsum) —bun auditreports 0 vulnerabilities. The playground is not published, so none of this reaches consumers.Packaging / structure
packages/hamo/;playground/is a sibling workspace that links it viaworkspace:*(a real monorepo — the oldfile:..hack that silently pulled the published0.3.2is gone). Root is a private workspace root whose scripts delegate viabun --filter hamo.unbundle, per-file"use client"souseObjectFitstays usable in Server Components.nanoevents). Peers:react >=18, pluslenisas an optional peer (only needed foruseScrollTrigger; the hook falls back to native scroll without it).docs/directory;engines.node >=18.Tooling & tests
useHookAtTopLevel+useExhaustiveDependencies), TypeScript 6.bun:testsuite (46 tests): render smoke tests, SSRrenderToStringsafety, and a debounce-cancel-on-unmount leak regression.ci.yml(React 18 + 19 matrix) andpublish.ymlretargeted topackages/hamo(publish runs from the package; test routes through the package'sbunfig.tomlpreload); least-privilege permissions; lockfile committed.Test Plan
bun run typecheck— 0 errorsbun run test— 46 pass / 0 failbun run lint(biome) — clean, 0 warnings (down from 9)bun run build— emits the dual ESM/CJS bundle (+ the new hooks, thedebounce-configmodule & debugger subpath)bun audit— 0 vulnerabilitiesastro check0 errors, 3 pages incl./scroll-triggerpublint— cleanattw— main entry + all hooks resolve 🟢 across node16 + bundler. Known limitation: the dev-onlyhamo/scroll-trigger/debuggersubpath does not resolve under legacy node10 module resolution (it readsmain, notexports). Modern tooling is unaffected; acceptable for a debug-only export.bun run dev), including the/scroll-triggerdemoSupersedes #11 (its three hooks are ported here, credited via Co-Authored-By). Stale Dependabot PRs #6/#7/#8 closed (the bun migration replaces that lockfile).