Repository navigation
Releases: Particle-Academy/fancy-walkthrough
Release list
v0.4.1
Changed
- The
@particle-academy/react-fancypeer range now also accepts 6.x (>=5.27 <6to>=5.27 <7). react-fancy 6.0.0 makesButtondefault totype="button"instead of submitting an enclosing form (Change Council CC-fancy-0003). Audited for that: its threeButtons (skip, back, next) are actions and none sits inside a form. NoButtonneededtype="submit"and noPromptInputis used, so the package behaves the same on 5 and 6. On 6 itsButtons also stop submitting a host form you happen to render it inside. The devDependency moved to^6.0.0, so this package's own suite, typecheck and build now run against 6. Widening only adds candidates, so the react-fancy you already have still resolves. Consumer action: none on react-fancy 5; on react-fancy 6 it works as-is.
Security
- Dev-only lockfile update:
vitest4.1.11 to 4.1.12 (brings@vitest/mocker4.1.12, GHSA-qxg6-89mg-rqv8 / GHSA-xp26-3454-mjgp) andundici7.30.0 to 7.30.1 via jsdom (GHSA-2m5v-pch6-74hx, GHSA-4w7w-5xrc-9h26, GHSA-qrxm-fqhp-jwvm, GHSA-xjj6-f33q-929c, GHSA-9fm3-97px-rjf6). Neither ships in the package. Consumer action: none.
v0.4.0
Additive, so this becomes 0.4.0 (a MINOR). It has one visible behaviour
change (Back, see Changed) and two type additions that can matter to strict
TypeScript. Nothing was removed or renamed.
Added
- Action-required steps. Steps with
advanceOn: "target-click"or
"event"ask the person to do something in the APP, not in the card. The
card shows no Next and no Back, only Skip, the instruction and the hint.
Enter and ArrowRight do not advance them. Use"event"with atargetwhen
the click only starts the action (server confirmation, data returning, a
redirect): the target is spotlighted, and the step moves on when the host
callssignal(event). fail(event, message?)onuseWalkthrough(), for when the action failed
(the server rejected it). The step stays put, the default card shows
message(or the newlabels.actionFailed) asrole="alert"with
data-walkthrough-error, andwalkthrough:action-failed { walkthrough, step, event, message? }is emitted. The next successfulsignal, or any
step change, clears it.useWalkthrough().actionErrorexposes it.createWalkthroughController()+<Walkthrough controller>, for
signalling from outside the React tree: a query client's mutation defaults, a
module, another React root.controller.signal(event)/
controller.fail(event, message?). A message sent while no walkthrough is
mounted (a per-page<Walkthrough>mid-visit) is held for the next one, which
applies it once, only to the step it resumes on and only if that step awaits
it. Safe to create at module scope and on the server.- Handles on the card wrapper, present with a custom
renderSteptoo:
data-walkthrough-requires-action="event" | "target-click",
data-walkthrough-awaiting="<event>", anddata-walkthrough-action-error
while a failure shows. WalkthroughStepContextgainsrequiresAction,canGoBackand
actionError. New types:WalkthroughController,WalkthroughActionMessage.
Changed
- No Back on an action-required step, and none onto one from the step after
it.back()refuses that move too. The tour cannot undo what the person
did in the app (created a project), so going back would ask them to do it
twice. A host or agent can still move a controlledstep. Nothing to do
unless your copy told people to press Back on such a step. - Strict TypeScript only:
WalkthroughEventhas a new member
(walkthrough:action-failed) andWalkthroughLabelsa new key
(actionFailed). Aswitchthat asserts exhaustiveness withnever, or code
that builds a completeWalkthroughLabels(not thePartialthelabels
prop takes), needs the new case or key. Everything else compiles unchanged.
Fixed
- A
signalsent from the page an action lands on was lost. The page's
mount effect runs before<Walkthrough>has read its storage, when no step is
current yet. Such a signal is now held and applied to the resumed step. A
page that happened to re-run its effect whensignalchanged identity did
advance, but reportedwalkthrough:startedinstead ofresumedand dropped
first-value. signalacted on the render it came from. A callback captured earlier,
such as a mutation'sonSuccess, was ignored if the step had changed since.
Worse, one captured before the person pressed Skip brought the dismissed
tour back on the following step.signalnow always reads the latest state.
Security
- Dev toolchain only — no runtime change, nothing for consumers to do. The
lockfile now resolvesvite8.3.5 (was 8.2.2), whose bundled
launch-editorfixes GHSA-587h-7wmj-h5pm (HIGH,fs.existsSync()check
bypassable on Windows), GHSA-698v-4r3x-3vcv (escapeCmdArgs()omits%
escaping on Windows) and GHSA-r825-w8mm-5pxw (editor CLI flag
misinterpretation).viteis only reached through the test/dev toolchain
and the published package does not ship our lockfile, so the exposure was
this repo's own dev server.
v0.3.0
Pre-1.0 reminder: this is a MINOR release, and in this package a minor may
carry breaking changes. This one carries one behaviour change (see Changed);
no API was removed or renamed.
Added
- Chained walkthroughs (#2).
WalkthroughDocument.nextnames the
walkthrough to start when this one completes (skipping never chains).
Give one<Walkthrough>a library withwalkthroughs={[...docs]}and the
next one starts in place; ifnextis not in the library it is handed over
through the storage adapter to whichever<Walkthrough>has that id, already
mounted on the page or mounted on a later one. - The active walkthrough is controlled state:
walkthrough+
onWalkthroughChange, or uncontrolled withdefaultWalkthrough.
useWalkthrough()gainswalkthrough(the active id) and
start(at?, walkthrough?). - Steps on another page.
step.route+<Walkthrough onNavigate>: when
a step starts and the host is elsewhere,onNavigate(step, { walkthrough, index })is called once and the HOST navigates (Inertia'srouter.visit, its
own page transition). The package never navigates and depends on no router.
The card waits for the target to register on the new page, and the step's
whenMissingpolicy is held off until the host has arrived. Pass
currentRoute(compared by string equality) so the package knows where
the host is; without it,window.location.pathnameis read in an effect. - Resume across navigation and reloads through a storage adapter, per the
design record's 2026-09-22 decision:storage={{ load, save }}, where
load()returnsWalkthroughProgress | null(or a Promise of it) and
save(progress | null)stores it.WalkthroughProgressis
{ walkthrough, step?, index }; resume finds the step by id first, so
reordering steps does not resume on the wrong one. An optionalsubscribe
lets a hand-over reach a<Walkthrough>already on the page. The default is
createLocalStorageAdapter(key = "fancy-walkthrough"), which is SSR-safe
(nothing toucheswindowat import or render) and degrades to remembering
nothing when storage is disabled or full. - Events:
walkthrough:chained { walkthrough, from, to }when one hands
over to the next;walkthrough:resumed { walkthrough, step, index }instead
ofstartedwhen progress is read back (sostartedstays an honest
denominator);walkthrough:completednow carriesnextwhen there is one. - Handles: the card carries
data-walkthrough-id(the active walkthrough)
and, while the host is navigating to its page,data-walkthrough-navigating
(the route). - Types:
WalkthroughStorage,WalkthroughProgress,
WalkthroughNavigateContext.
Changed
- In-progress walkthroughs now persist to localStorage by default. A tour
that was running when the page reloaded resumes on the step it was on.
Finishing or skipping clears it. To keep 0.2's behaviour, pass
storage={null}. Nothing else to do:idandstepsare now optional
(they may come fromwalkthroughsinstead), which accepts everything that
compiled before. - Nothing runs or reports until the store has been read, so a resumed
walkthrough does not first reportstartedatdefaultStep. With a sync
store this happens in the mount effect, before anything is painted.
v0.2.0
Added
- Targets in another React root (#1). A Laravel layout mounts its chrome as
separate roots (Blade islands) beside the Inertia page, and context cannot
cross roots, so a target there never registered and its step waited forever.
Create one registry withcreateWalkthroughRegistry(), pass it to
<Walkthrough registry>, and register targets with it from any root:
<Walkthrough.Target registry>,useWalkthroughTarget(id, { registry })
(oruseWalkthroughTarget(id, registry)), or
<WalkthroughTargetsProvider registry>around a whole root with no
<Walkthrough>above it. An explicit registry wins over context. Nothing
queries the DOM. - Markdown step bodies.
bodyFormat: "markdown"on a step, or
<Walkthrough bodyFormat="markdown">for every step, rendersbodythrough
react-fancy'sContentRenderer, sanitised. A step's ownbodyFormatwins. - The body element carries
data-walkthrough-body("text"or"markdown"). - Types:
WalkthroughTargetOptions,WalkthroughTargetsProviderProps.
Nothing to do: plain string bodies stay plain text by default, and a
walkthrough with no registry keeps its own, exactly as before. The
react-fancy peer range is unchanged.
Fixed
CHANGELOG.mdis now in the published tarball.filesdid not whitelist it, so npm never shipped it — and this package puts breaking changes in MINOR releases and tells you in the README to read the entry before taking one. The instruction existed for the author, who has the file, and not for the consumer, who is the only one being instructed. Nothing for you to do; the file simply arrives from this release on.
Security
source-map-jsis pinned forward to^1.2.2viaoverrides. Versions up to
1.2.1 allow an event-loop denial of service through indexed source-map section
offsets, and it arrives here transitively through the build toolchain.
Nothing for a consumer to do, and no runtime change: an npm package does
not ship a lockfile, so this governs builds OF this repo, not anything
installed FROM it. Recorded rather than left silent because the override it
sits beside —shell-quote^1.9.0, added for an earlier advisory — was
carried with no note of why, and had drifted back inside the vulnerable range
before anyone looked.
v0.1.3
Fixed
-
Corrects the claim made in 0.1.2. That entry said it fixed "the spotlight
did not move between targets". It did not, because there was no such
product defect — the symptom was an artefact of how I verified it.What actually happened: the browser tab driving the check was a BACKGROUND
tab (document.hidden === true). Chrome throttlesrequestAnimationFrameto
zero there, so the glide's callback never ran — measured, not guessed: one
rAF scheduled, zero fired in 900ms. The hole therefore stayed on the first
target.animate={false}appeared to "fix" it only because that path draws
synchronously inside the layout effect and needs no rAF at all. A real user,
in a visible tab, would not have seen any of it.The defect 0.1.2 genuinely fixed is real and unrelated to that symptom, and
stands: the glide measured elapsed time across two clocks (startfrom
performance.now(),nowfrom the rAF timestamp) and clamped with
Math.min(1, …), which caps only the top. A first callback stamped before the
call drivestnegative andlerpextrapolates away from the target.
Reproduced and covered bysrc/Spotlight.glide.test.tsx.No code changed in this release. The correction is the point: 0.1.2's entry is
the version an upgrading consumer reads, and it described a fix to a bug that
did not exist — which would send anyone debugging a stationary spotlight down
the wrong path. What to check first is whether the tab is visible.