Repository navigation
v0.1.4
Turn on noUncheckedIndexedAccess
It was not missing here — it was explicitly false, in sixteen of the
seventeen tsconfigs. eon alone had it on, which is why nobody had seen
what it finds.
It stays a named deviation from upstream: @adonisjs/tsconfig sets
strictNullChecks and noImplicitAny but not this one. We keep it because
turning it on is what caught an as asserting a possibly-absent regex
group was a known value — the exact shape the flag exists to find. Doing
better than upstream is kept and written down, not reverted to parity.
Every site is restated rather than silenced: no !, no cast, no ?? 0
standing in for a branch that cannot happen. A reversed copy read by
value where an index walked a callback list backwards, the winner of a
scan kept as the value it found rather than its position, destructuring
where a length check was doing the proving, and an explicit break where a
loop condition already bounds the read.
Report copied components the package has since changed
Copying is one-way by design: nebula:add hands over the source and steps
back. The bill for that is a fix released here never reaching a project, with
nothing anywhere reporting it — the files are local, no lockfile names them,
pnpm update does not touch them. shadcn has the same gap.
nebula:diff closes it, and it answers with the reason a file differs rather
than the fact that it does. Every copied component is expected to diverge
eventually — that is what owning the source means — so "differs from upstream"
would flag the whole tree and say nothing useful.
Separating the two needs a reference point, so add now records the hash of
what it copied, beside the components it describes. Four answers come out of
it: edited is yours and needs nothing, outdated means the package moved
while the copy stayed put and re-copying loses nothing, conflict means both
moved and a person has to read them, and unknown is a component copied
before anything was recorded — which is the honest answer, since there is no
way to tell which side moved.
Say what an app's own stylesheet is missing instead of skipping in silence
The adapters mark the stylesheet skipIfExists, which is right — it is the
app's once it exists. But nothing said so, and nothing said what had therefore
not been written. A project that already had an app.css got the components
copied in and no tokens for them to resolve against: every class name present
in the markup, every colour resolving to an undefined custom property, and a
page that renders grey with nothing raised anywhere.
configure now reads the file back and names the tokens it does not define.
Tokens rather than directives, because there is more than one correct way to
supply them: importing nebula's theme, or declaring --color-* directly in an
@theme block. Checking for the directives would have reported a complete,
working stylesheet as broken.
skipIfExists is also passed through to the codemod now, rather than the
contract being decorative and the default deciding.
Write the Tailwind variants out instead of building them at runtime
Slider and InputGroup composed their variants by joining a string:
`[&::-webkit-slider-thumb]:${THUMB.split(" ").join(" [&::-webkit-slider-thumb]:")}`
`[&_input]:${inputGroupControlClasses.split(" ").join(" [&_input]:")}`
Tailwind finds class names by scanning the source as text. It never evaluates
the expression, so [&_input]:border-0 was never a literal anywhere and no
rule was generated for it. The elements carried class names no stylesheet
defined. Nothing threw and the markup read as correct.
The shipped nebula.css proves it: no [&_input]: rule at all, and exactly one
slider-thumb rule — the focus-visible: pair on the line below, which is
written as a literal. The literal survives and every computed neighbour is
missing. Rebuilt, the thumb goes from 6 occurrences to 22.
The Slider was the worse of the two: its thumb got no styling whatsoever and
fell back to the browser's native control.
InputGroup now follows shadcn and ships an InputGroupInput. The stripping
belongs on the input, which is where shadcn puts it; moving it onto the parent
as a computed descendant variant is the deviation that could not survive a
static scanner. inputGroupControlClasses stays exported for a caller passing a
plain , documented as the path rather than as a workaround.
A test now walks the sources and fails on any variant built by interpolation.
This failure is silent by construction — nothing raises, only the pixels are
wrong — so it needs a gate rather than a review habit.
Reject three kinds of element the browser refuses to focus
isVisible() answered true for a node inside a collapsed
under a display:none shadow host, and for one detached from the document.
Chromium refuses focus on all three, so a focus trap could hand focus to a
node that never takes it, leaving it wherever it was and letting Tab walk
straight out of the overlay.
The collapsed
declines to answer: Chromium reports offsetParent , a 62x21 rect and
display: inline-block for a button inside one, while checkVisibility() says
false and focus() does nothing. The fast path therefore returned true before
the styles were ever consulted, so this has to be read from the markup. The
stays focusable in both states, and the walk continues past an open
because a nested one can still sit inside a collapsed parent.
The ancestor walk now leaves a shadow tree by its host, which parentElement
never does, and a detached node is rejected outright: it has no computed
styles at all, so nothing along the walk could ever be display: none.
Verified against Chromium: isVisible now agrees with whether focus() actually
lands, on all eight cases.
Release 0.1.4
Fix a visibility check that happy-dom had made unconditional
isVisible() decided whether a node is rendered from offsetParent !== null. happy-dom does not implement offsetParent, so it read undefined,
and undefined !== null is true: every element was visible, always. The
focus filtering behind focus traps, roving groups, menus and modals was
never exercised, and isVisible had no test of its own.
offsetParent stays the cheap fast path when it yields an element, which
is positive proof that a layout engine ran. Its absence proves nothing:
a browser reports null for position: fixed nodes and for too.
So the check falls through to the styles, walking ancestors for display
because it does not inherit, and reading the element's own visibility
because it does. Browser behaviour is unchanged; the check becomes
answerable without a layout engine.
The suite moves to jsdom, which declines to fake layout rather than
faking it wrongly. scrollIntoView is stubbed as a no-op: jsdom ships
none, and a headless DOM has no scrolling to assert. Seven tests now
cover the cases that were passing vacuously.
The Form unmount skip is lifted — it worked around an
HTMLFormElement.remove() bug in happy-dom that jsdom does not have, so
that component is demounted by the smoke test again.
Changes since v0.1.3.
The ancestor walk now leaves a shadow tree by its host, which parentElement
never does, and a detached node is rejected outright: it has no computed
styles at all, so nothing along the walk could ever be display: none.
Verified against Chromium: isVisible now agrees with whether focus() actually
lands, on all eight cases.
Release 0.1.4
Fix a visibility check that happy-dom had made unconditional
isVisible() decided whether a node is rendered from offsetParent !== null. happy-dom does not implement offsetParent, so it read undefined,
and undefined !== null is true: every element was visible, always. The
focus filtering behind focus traps, roving groups, menus and modals was
never exercised, and isVisible had no test of its own.
offsetParent stays the cheap fast path when it yields an element, which
is positive proof that a layout engine ran. Its absence proves nothing:
a browser reports null for position: fixed nodes and for too.
So the check falls through to the styles, walking ancestors for display
because it does not inherit, and reading the element's own visibility
because it does. Browser behaviour is unchanged; the check becomes
answerable without a layout engine.
The suite moves to jsdom, which declines to fake layout rather than
faking it wrongly. scrollIntoView is stubbed as a no-op: jsdom ships
none, and a headless DOM has no scrolling to assert. Seven tests now
cover the cases that were passing vacuously.
The Form unmount skip is lifted — it worked around an
HTMLFormElement.remove() bug in happy-dom that jsdom does not have, so
that component is demounted by the smoke test again.
Changes since v0.1.3.