arkynChangelogGuides
docs / changelogs / breaking-changes

Breaking Changes

This document gathers only the Arkyn versions that require some code change from the library's consumers. Versions with no direct impact on your code do not appear here, see Latest for the current version, or the v3.x.x changelog and v3.0.1-beta changelog for the full version-by-version history.
If you're upgrading straight from v2.2.3 to v3.0.2, see the consolidated migration guide instead, it covers every breaking change across that whole jump, including ones made before this changelog started tracking versions individually at v3.0.1-beta.139.

v3.0.10

What changed

BadResponse and its subclasses (ServerError, BadGateway, BadRequest, Conflict, Forbidden, NotFound, NotImplemented, Unauthorized, exported from @arkyn/server) no longer include a cause field in the JSON response body when NODE_ENV === "production". Previously cause was always included, in every environment, which could expose stack traces, SQL fragments, or other internal error detail to clients in production. UnprocessableEntity is the one exception, it still includes cause in production, its cause is now redacted, structured field/validation data intended for the client (see the v3.x.x changelog), not internal diagnostic detail.

How to migrate

If your frontend or API client reads error.cause from a production error response body (any status other than 422), it will now be undefined there. It's still present in development, and in any NODE_ENV value other than "production". message and name are unaffected in every environment.
Before

ts

// In production, this response body no longer has a "cause" key
// (except for UnprocessableEntity's 422 responses)
throw new ServerError("Database operation failed", { table: "users" });
After

ts

// If you depend on cause reaching the client, either move that data
// into "message", or keep it in a 422 via UnprocessableEntity's
// redacted "data"/"fields", which are still sent in production.
throw new UnprocessableEntity({
message: "Database operation failed",
data: { table: "users" },
});

Impact

Low for most consumers, cause was intended for local debugging, not client-facing logic. Anyone parsing cause specifically in production error-handling code needs to stop, or move that dependency to a 422 (UnprocessableEntity) response, where it's preserved (redacted).

Notes

See the v3.x.x changelog for the full v3.0.10 release notes, including a related fix, UnprocessableEntity's own cause (fields/data) is now redacted with parseSensitiveData regardless of environment, independently of this production-hiding change.

v3.0.9

What changed

LogService.getConfig() (exported from @arkyn/server) now types apiUrl as string | null instead of string. This is a consequence of removing a hardcoded, plain-http:// fallback endpoint: when no valid logBaseApiUrl (an https:// URL, or http://localhost/http://127.0.0.1 for local dev) has been configured, apiUrl is null and no log requests are sent, rather than silently pointing at Arkyn's old default log server.

How to migrate

If your code reads LogService.getConfig()?.apiUrl and assumed it was always a non-null string, add a null check:
Before

ts

const config = LogService.getConfig();
console.log(config.apiUrl.toUpperCase());
After

ts

const config = LogService.getConfig();
if (config?.apiUrl) {
console.log(config.apiUrl.toUpperCase());
}
If you were relying on logs being sent without ever calling LogService.setConfig({ logBaseApiUrl: ... }), configure an explicit https:// endpoint, the old hardcoded fallback no longer exists:

ts

LogService.setConfig({
trafficSourceId: "your-traffic-source-id",
userToken: "your-user-token",
logBaseApiUrl: "https://your-log-server.example.com",
});

Impact

Low for typical usage, most consumers only call LogService.setConfig() and never read getConfig().apiUrl directly. TypeScript will flag any direct property access on apiUrl that isn't null-checked. Anyone who never configured logBaseApiUrl and depended (knowingly or not) on logs still reaching Arkyn's old default server needs to set an explicit, secure endpoint now, nothing is sent otherwise.

Notes

This change also fixes a real security gap: request/response headers and bodies sent to the log endpoint, and to ApiService's debug output, are now redacted with parseSensitiveData before being sent or logged. See the v3.x.x changelog for the full v3.0.9 release notes, including 17 other non-breaking security, bug, and accessibility fixes in the same release.

v3.0.5

What changed

RichText's video insertion modal now requires the URL to use https://, in addition to the ?v= parameter check it already performed. A YouTube URL passed as http://... is no longer accepted.

How to migrate

No code change is required. If you were relying on inserting video URLs written as http://..., use the https:// form instead, YouTube itself serves both, so this is typically just a matter of the URL string used.

Impact

Very low. Only affects RichText's video insertion modal, and only when the pasted URL explicitly uses the http:// scheme. Video embeds already saved from previous versions are unaffected, this only gates new insertions made through the modal.

Notes

The same https:// check now also gates the new link insertion feature introduced in v3.0.5. See the v3.x.x changelog for the full v3.0.5 release notes.

v3.0.4

What changed

PhoneInput's controlled-value masking (introduced in v3.0.3) no longer misplaces the country dial-code digits when a value string is in international format (starting with +), for example when onChange's value is round-tripped straight back as value, a common controlled-input pattern in React. The dial code is now stripped before masking; values without a leading + are unaffected.

How to migrate

No code change is required. If you rely on the round-trip pattern below, the display now shows the correct, unshifted value:

tsx

const [value, setValue] = useState("");
<PhoneInput name="phone" value={value} onChange={setValue} />
Before (v3.0.3), typing "34999998888" fed onChange's "5534999998888" back into value, and the dial-code digits were read as part of the local number, shifting the display. After (v3.0.4), the same code shows "(34) 99999-8888" correctly.

Impact

Very low. Only affects controlled PhoneInput usage where value is a string starting with + (typically the onChange round-trip pattern). Consumers passing local digits without a leading +, the pattern already documented as of v3.0.3, see no change. This is best understood as a bug fix for a defect introduced by v3.0.3's masking change, not a deliberate behavior change.

Notes

See the v3.x.x changelog for the full v3.0.4 release notes, and the v3.0.3 entry above for the original controlled-value masking change this refines.

v3.0.3

What changed

PhoneInput's controlled value is now formatted with the selected country's phone mask before being displayed. Previously, a controlled value rendered exactly as passed, while uncontrolled typing already applied the mask, so the two modes were inconsistent.

How to migrate

No code change is required to keep compiling or running, the prop signature is unchanged. Review any place that:
  • Displays the raw PhoneInput value elsewhere and expected it unmasked.
  • Has snapshot/unit tests asserting the field's displayed text.
Before

tsx

<PhoneInput name="phone" value="34999998888" />
// Rendered: "34999998888"
After

tsx

<PhoneInput name="phone" value="34999998888" />
// Renders: "(34) 99999-8888"
If you had your own masking logic before passing value to compensate for the old behavior, it's now redundant, non-digits are stripped before re-masking, so an already-formatted string still works the same way, and can be removed.

Impact

Affects any project using PhoneInput in controlled mode (value prop). It doesn't break compilation or throw at runtime, it's a rendered-output change only, but it can break snapshot tests or surprise consumers relying on the old, unmasked display. There's no prop to opt back into the previous, unmasked behavior.

Notes

This also fixes a real inconsistency: the same component previously showed unmasked text when controlled and masked text when typed into directly. See the v3.x.x changelog for the full v3.0.3 release notes.

v3.0.1-beta.174

What changed

The schema prop, used by every Alert component (AlertContainer, AlertContent, AlertDescription, AlertIcon, and AlertTitle), was renamed to scheme. This change unifies the naming already adopted by Button, Badge, and IconButton across the library.

How to migrate

Rename schema to scheme in every use of Alert.
Before

tsx

<AlertContainer schema="danger">...</AlertContainer>
After

tsx

<AlertContainer scheme="danger">...</AlertContainer>

Impact

Affects any project that uses Alert with the schema prop. The old name is no longer recognized and is silently ignored, Alert still renders, but without applying the expected color scheme.

Notes

The scheme pattern was already used by the library's other components; this change simply aligns Alert with the existing convention.

v3.0.1-beta.170

What changed

UI dependencies used internally by @arkyn/components, framer-motion, mapbox-gl, slate, slate-history, slate-react, @react-google-maps/api, react-hot-toast, react-scroll, html-react-parser, and @react-input/mask, are no longer installed automatically and are now peerDependencies. In addition, the minimum required version of react and react-dom was corrected.

How to migrate

Explicitly install, in your project, the dependencies related to the components you use:

bash

bun add framer-motion mapbox-gl slate slate-history slate-react \
@react-google-maps/api react-hot-toast react-scroll html-react-parser \
@react-input/mask
You don't need to install all of them, only the ones used by the components present in your project (for example, framer-motion for Drawer/Modal, mapbox-gl for MapView, slate* for RichText).

Impact

Affects any project that relied on the transitive installation of these libraries. Without installing them manually, the components that depend on them stop working correctly.

Notes

The minimum react/react-dom requirement was corrected from >=19.2.6 to >=18.0.0, restoring compatibility with React 18 projects that had been incorrectly blocked.

v3.0.1-beta.148

What changed

The defaultValue prop on FullCalendar, used to set the initially focused date, was renamed to defaultViewValue.

How to migrate

Before

tsx

<FullCalendar defaultValue={new Date(2026, 5, 1)} />
After

tsx

<FullCalendar defaultViewValue={new Date(2026, 5, 1)} />

Impact

Affects only those who already used FullCalendar with the defaultValue prop to control the initially displayed date. The old prop is silently ignored in this version, without the rename, the calendar always opens on the current date.

Notes

This change standardizes the naming between FullCalendar and Calendar: value controls the selection, viewValue controls the displayed period, and defaultViewValue sets the initial period.

v3.0.1-beta.147

What changed

Three changes to FullCalendar's API:
  1. The date property on event objects (FullCalendarEvent) was renamed to initialDate.
  2. The defaultView prop, which set the initial view ("day" | "week" | "month"), was removed, the component now always initializes in month view.
  3. The onChange prop was removed.

How to migrate

Rename date to initialDate on every event:
Before

tsx

events={[{ title: "Meeting", date: new Date(2026, 5, 23, 10, 0) }]}
After

tsx

events={[{ title: "Meeting", initialDate: new Date(2026, 5, 23, 10, 0) }]}
If you used defaultView="day" or defaultView="week", remove the prop, there is no direct replacement; the calendar always opens in month view.
If you used onChange to react to date changes, replace it with onClickDate (user click on a cell) or onChangeView (navigation between periods), depending on the intended behavior:
Before

tsx

<FullCalendar onChange={(date) => setSelectedDate(date)} />
After

tsx

<FullCalendar onClickDate={(date) => setSelectedDate(date)} />

Impact

Affects only those who already integrated FullCalendar since its introduction in the previous version (v3.0.1-beta.146). Anyone who hadn't yet used the component is not impacted.

Notes

These changes accompany the introduction of blockedTimestamps and onClickDate, which expand control over calendar interactions. The same version also made "vertical" the default orientation for ImageUpload, MultiSelect, RadioGroup, RichText, and Select, that part does not require a code change and is therefore not listed as a breaking change here; see changelog for details.
On this page
    arkyn