Skip to content

feat: you can now supply your own traversal - #24

Merged
DoodleBears merged 1 commit into
mainfrom
feat/custom-order-fn
Oct 1, 2026
Merged

DoodleBears merged 1 commit into
mainfrom
feat/custom-order-fn

Conversation

@DoodleBears

Copy link
Copy Markdown
Contributor

Why

The Traversal layer has always been one function of lattice position — the seam the whole
layered design leaves open. It was reachable only by writing your own renderer. Now it is an
option.

// a chevron that speeds up toward its tip
const chevron = (col, row, cols, rows) => {
  const mid = (rows - 1) / 2;
  const arm = Math.abs(row - mid) / (mid || 1);
  const v = arm * 0.6 + (col / (cols - 1 || 1)) * 0.4;
  return v * v;
};
createDotMatrix(el, { cols: 9, rows: 9, preset: "morph", order: chevron });

Change outline

It is free in both renderers — the CSS host derives each cell's animation-delay from
the same traversal value, so a custom traversal costs zero per-frame JS there too.

That asymmetry is the design: a custom envelope would not be free, because the CSS
animation is one shared @keyframes published per preset. So: traversal only, with a test
asserting the CSS host's --botui-o matches. A traversal the engine forgot to apply there
would produce two renderers that disagree — the one thing this library is arranged to make
impossible.

"Accelerating arrows" turned out not to need a new preset at all — only a traversal that
eases along its own axis. A centre point for the concentric orders is the same shape of
answer: geometry the traversal computes, so order covers it without a new option.

Special things to note

  • Reversibility: additive; nothing existing changes. Review focus is the type work below.
  • A type trap. order must be omitted from ResolvedDotMatrixOptions's Required
    alongside stagger — left in, the resolved type demands a traversal nobody supplied and
    DEFAULTS would need a default traversal, which is one more thing silently overriding the
    caller. OrderFn's fifth argument stays optional, so an existing four-argument traversal
    is still valid and one with no sense of direction is not forced to accept it.
  • The README example is executed by a test. A snippet that does not compile is worse
    than none — it is the first thing a reader runs. The test runs it verbatim, on 1×1 / 1×7 /
    7×1 grids where a pasted traversal divides by zero, and asserts it really accelerates:
    an easing that did nothing would still run and still look plausible.
  • It is a function, so it is not copyable, and the copy button omits it rather than
    emitting a prop that would not compile. Stated in the README and the option's docs, since
    it is a real constraint of this route.
  • 260 tests (was 249), oxlint 0, registry ok, both changelog ledgers.

🤖 Generated with Claude Code

`order` takes an `OrderFn` and replaces the preset's. This is the seam the layered design
always left open — which dot leads is one function of lattice position — finally reachable
from an option rather than only from someone writing their own renderer.

IT IS FREE IN BOTH RENDERERS, which is the asymmetry worth stating. The CSS host derives
each cell's `animation-delay` from the same traversal value, so a custom traversal costs
zero per-frame JS there too. A custom ENVELOPE would not be free: the CSS animation is one
shared `@keyframes` published per preset, so a per-caller brightness curve needs the
stylesheet's cache key and the animation name to change. Hence traversal only, and a test
that asserts the CSS host's `--botui-o` matches — a traversal the engine forgot to apply
there would produce two renderers that disagree, which is the one thing this library is
arranged to make impossible.

TWO THINGS THE TYPE HAS TO GET RIGHT, and the second is a trap:

- `order` must be omitted from `ResolvedDotMatrixOptions`'s `Required` alongside `stagger`.
  Left in, the whole resolved-options type demands a traversal nobody supplied, and
  `DEFAULTS` would need a default traversal — which is one more thing silently overriding
  the caller. `order` stays `undefined` until someone passes one, and `resolveField` is
  where the preset's traversal takes over.
- `OrderFn`'s fifth argument stays optional, so an existing four-argument traversal is still
  a valid `OrderFn` and a custom traversal with no sense of direction is not forced to
  accept one.

THE README EXAMPLE IS EXECUTED BY A TEST. A README snippet that does not compile is worse
than none — it is the first thing a reader runs and the thing they judge the API by. The
test runs the snippet verbatim, on 1×1 / 1×7 / 7×1 grids where a pasted traversal divides by
zero, and asserts that it really ACCELERATES: an easing that did nothing would still run and
still look plausible, so the prose's claim has to be checked against the numbers it makes.

The snippet is the accelerating chevron, which is one of the two motions asked for — so
"accelerating arrows" turned out not to need a new preset at all, only a traversal that
eases along its own axis. A centre point for the concentric orders is the same shape of
answer: it is geometry the traversal computes, so `order` covers it without a new option.

IT IS A FUNCTION, SO IT IS NOT COPYABLE, and the playground's copy button leaves it out
rather than emitting a prop that would not compile. A component carrying a custom traversal
is a component whose motion is code; that is a real constraint of this route and it is
stated in both the README and the option's own docs.

260 tests (was 249), oxlint 0, registry ok, both changelog ledgers.
@coderabbitai

coderabbitai Bot commented Oct 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 31 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f4ab0d66-2434-44fe-a9b2-8d8165889d37

📥 Commits

Reviewing files that changed from the base of the PR and between fbb747c and 8d6ed45.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • CHANGELOG.zh.md
  • packages/core/README.md
  • packages/core/src/dot-matrix/field.ts
  • packages/core/src/types.ts
  • packages/core/test/custom-order.test.ts
  • packages/core/test/readme.test.ts
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DoodleBears
DoodleBears merged commit 5b94bc9 into main Oct 1, 2026
1 of 2 checks passed
@DoodleBears
DoodleBears deleted the feat/custom-order-fn branch October 1, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant