PhoneInput (@arkyn/components) no longer wipes its defaultValue in development under <React.StrictMode>. No public API changes, no breaking changes.PhoneInput (@arkyn/components): defaultValue is no longer wiped in development when the app renders under <React.StrictMode>, which is on by default in React Router's generated entry.client. Previously, after hydration the visible input showed the empty mask (e.g. (__) _____-____) instead of the formatted phone number, and the consumer's onChange fired on mount with only the dial code (e.g. "+55"), so forms looked "dirty" and save buttons became enabled with no user interaction. The server-rendered HTML was always correct, the problem only appeared after hydration. The cause: the internal PhoneInputMask resets the value whenever the selected country's mask changes, and skipped the effect's first run with an isFirstRender ref; StrictMode runs mount effects twice in development without resetting refs, so the second run was mistaken for a country change. The effect now compares the current mask with the previous one and only resets when the mask actually changes. Production was never affected (StrictMode doesn't double-invoke effects outside development), and selecting a country with a different mask still clears the field and calls onChange with the new dial code (e.g. "+1"). If you're on v3.0.12 or older and see an empty phone field in development, upgrade. See the PhoneInput docs.PhoneInput: one asserts that defaultValue and the hidden input value are preserved and onChange isn't called on mount, the other that changing the country still resets the value.PhoneInput page gains a defaultValue example.overrides for transitive dev/playground dependencies flagged by bun audit: shell-quote (^1.11.0), undici (^7.29.1), compression (^1.8.2), proxy-addr (^2.0.8), and source-map-js (^1.2.2). None of these are part of the published packages.@arkyn/cli, @arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.13.@arkyn/cli does nothing when run through its npm bin (also present in v3.0.12). npx @arkyn/cli init --agents, npx @arkyn/cli --version, and every other command run through the installed arkyn bin exit with code 0 and print nothing. The CLI entry point only runs when import.meta.url === `file://${process.argv[1]}`, but npm installs the bin as a symlink (node_modules/.bin/arkyn → dist/bundle.js), so process.argv[1] is the symlink path while import.meta.url is the real path, and the check fails. Paths that need URL-encoding (e.g. containing spaces) and Windows paths fail the same check. Workaround: run the bundle directly, node node_modules/@arkyn/cli/dist/bundle.js <command> (install @arkyn/cli as a devDependency first). A fix is planned for the next release. See the CLI introduction.@arkyn/ui is not part of this release, it remains at 0.1.0, unpublished, and excluded from the monorepo's all:* pipeline.Tooltip's text prop, a new opt-in withSecurityHeaders utility for @arkyn/server, and ARIA roles/states for the custom Checkbox, RadioBox, RadioGroup, and Switch controls. Renames LogService's userToken config to serviceToken and the date format "isoDate" to "usDate" (both misleading names, both kept as deprecated aliases). Also fixes BadResponse (and its 8 subclasses) so cause is no longer double-JSON-encoded, adds timeoutMs support to ApiService, and adds full keyboard/touch support to Slider. One breaking change, see Breaking Changes.Tooltip (@arkyn/components): the text prop is now sanitized with an internal (not exported) sanitizer built on the xss package before being rendered via dangerouslySetInnerHTML. Only a small inline-formatting allowlist is rendered as HTML, b, strong, i, em, u, br, span, small, and no attributes are permitted on any tag (so class, style, id, href, and on* handlers are always stripped, even on allowed tags: <span class="x" onclick="y">Hi</span> becomes <span>Hi</span>). <script>, <style>, <iframe>, <object>, <embed>, and <noscript> are removed together with their content (<script>alert(1)</script>Hello becomes Hello). Any other tag is HTML-escaped and shown as literal text, not removed: <div>Div</div> renders the visible text <div>Div</div>, and the same happens with <a>, <p>, <code>, <img>, <ul>, and so on. This closes a script-injection vector for any app that ever passed unsanitized or user-provided text into Tooltip, but it's a visible behavior change if you used tags or attributes outside the allowlist, see Breaking Changes. See the Tooltip docs.withSecurityHeaders (@arkyn/server, new): an opt-in utility that sets X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and Referrer-Policy: strict-origin-when-cross-origin on a Response, mutating and returning the same instance. Deliberately does not set Content-Security-Policy or Strict-Transport-Security by default (both are too app-specific to guess safely). Accepts an overrides object to replace, add, or (via null) remove individual headers. Nothing else in @arkyn/server calls it automatically, wrap your own responses to opt in: return withSecurityHeaders(new Success("OK", { data }).toResponse());. It only works on a Response whose headers are mutable (new Response(), Response.json(), and Arkyn's toResponse()), a fetch() response or Response.redirect() has immutable headers and makes it throw a TypeError. See the withSecurityHeaders docs.BadResponse and its 8 subclasses (BadGateway, BadRequest, Conflict, Forbidden, NotFound, NotImplemented, ServerError, Unauthorized, all from @arkyn/server): cause is no longer run through JSON.stringify() before being stored; it's now kept as-is, the same way UnprocessableEntity already worked. Previously, new BadRequest("msg", { field: "x" }).cause was the string '{"field":"x"}', now it's the original object, error.cause.field works directly. The JSON body changes accordingly, new BadRequest("Validation error", { field: "email" }) now produces "cause": { "field": "email" } instead of "cause": "{\"field\":\"email\"}". This also fixes silent data loss for falsy causes: new BadRequest("msg", 0).cause used to become undefined (the same as passing no cause at all) because of a truthy check before stringifying; 0, "", and false are now preserved as themselves. null is also now preserved as null instead of becoming undefined (so the body contains "cause": null). Production masking is unchanged, cause is still omitted when NODE_ENV === "production", except on UnprocessableEntity. BadResponse and all its subclasses, UnprocessableEntity included, additionally now extend the native Error class (instanceof Error is true, .message is the constructor message or "Unknown error", "Unprocessable entity" for UnprocessableEntity, .name is the class name, and .stack is available), which is what made keeping cause unstringified consistent across all 8 classes possible. This is a breaking change if you JSON.parse(error.cause) or exact-match a stringified cause, see Breaking Changes. errorHandler's fallback ServerError also now stores the raw thrown value as cause.Slider (@arkyn/components): no longer fires onChange with NaN when the track width is 0, the event is skipped instead.AudioUpload (@arkyn/components): fixes a Portuguese accent typo in its default upload-error text ("Erro ao enviar audio" → "Erro ao enviar áudio") and an English/Portuguese mismatch on the resend button, its tooltip already said "Reenviar áudio" but its aria-label said "resend image"; both now consistently use the same (now customizable, see below) text.@arkyn/cli: mergeAgentsBlock (used by arkyn init --agents) is now robust to a corrupted or hand-edited AGENTS.md. Start/end markers are paired left to right: the first valid pair is replaced with the fresh block, duplicate pairs are removed, orphaned or out-of-order marker lines are dropped, and if no valid pair is left the block is appended. Three or more consecutive newlines collapse to two, and user content outside the markers is never touched. Previously, an orphaned or duplicated marker could produce a malformed or duplicated block.formatToCep/formatToCnpj/formatToCpf (@arkyn/shared): fixes a grammar typo in the thrown error message, "CEP must be contain 8 numeric digits: ..." → "CEP must contain 8 numeric digits: ..." (and the equivalent for CNPJ/14 digits and CPF/11 digits). Anyone exact-string-matching the old message needs to update it, but this is a copy fix, not a functional change.formatToCurrency (@arkyn/shared): the "unsupported currency" error message now names the invalid input and lists every supported code, "Unsupported currency code: ABC. Supported codes: USD, EUR, JPY, ...", instead of the generic "Unsupported currency code". See the formatToCurrency docs.LogService.setConfig({ userToken }) (@arkyn/server) is renamed to serviceToken. userToken is kept as a deprecated alias, when used, a dev-mode warning is now logged via flushDebugLogs explaining that serviceToken must be a static, application-level credential (e.g. an env var set once at boot), not a per-request or per-session value, since the singleton only ever honors the value from its first call. The deprecation warning only prints when NODE_ENV === "development" or DEBUG_MODE === "true". setConfig() now also throws Error("LogService.setConfig() requires a serviceToken (or the deprecated userToken alias).") if the resolved token (serviceToken ?? userToken) is missing or empty (previously enforced only at the type level, not at runtime), and logs a dev warning instead of silently no-oping on any reconfiguration attempt after the first (the second call is still ignored). getConfig() now returns { trafficSourceId, serviceToken, apiUrl }, there's no userToken key anymore, so code that reads getConfig().userToken breaks, see Breaking Changes. The Authorization: Bearer <token> header sent with logs is unchanged. See the LogService docs."isoDate" (the inputFormat literal accepted by @arkyn/server's validateDate, and @arkyn/shared's parseToDate/formatDate/ValidateDateService) is renamed to "usDate". Both mean the same thing, month-first MM-DD-YYYY parsing, but "isoDate" was a misleading name since it isn't actually ISO 8601 (ISO 8601 date order is YYYY-MM-DD, which is what this library's "timestamp" format already represents). "isoDate" is kept as a permanent, doc-level-deprecated alias with identical behavior, no runtime warning is emitted for this one. See the validateDate, parseToDate, and formatDate docs.timeoutMs (@arkyn/server's ApiService): every request method (get/post/put/patch/delete) now accepts an optional per-request timeoutMs, and the constructor accepts an instance-level default (per-call value wins). Previously, requests had no timeout at all and could hang indefinitely on a stalled connection; now every request aborts after timeoutMs (default 10000, 10s) via AbortController. This default is a behavior change: any endpoint that takes longer than 10s to start responding now needs a larger timeoutMs, see Breaking Changes. The timer is cleared once the response headers arrive, so reading the response body isn't covered by the timeout. A timeout doesn't throw, it resolves to a distinguishable result, { success: false, status: 504, message: "Request timed out after {timeoutMs}ms", response: null, cause: <AbortError> } (cause is an Error object), instead of the generic network-failure result, which is unchanged: { success: false, status: 0, message: "Network error or request failed", response: null, cause: <string> }. See the ApiService docs.arkyn --version / arkyn -v (@arkyn/cli): prints the installed @arkyn/cli version, read from its own package.json. See the --version docs.Checkbox (@arkyn/components): the custom button control now exposes role="checkbox" and aria-checked, matching its visual state, instead of resolving to the generic role="button" it had before.RadioBox (@arkyn/components): now exposes role="radio" and aria-checked per option.RadioGroup (@arkyn/components): the options wrapper now exposes role="radiogroup".Switch (@arkyn/components): now exposes role="switch" and aria-checked, instead of the generic role="button" it had before.Slider (@arkyn/components): now exposes role="slider", aria-valuemin/aria-valuemax/aria-valuenow, aria-orientation="horizontal", and aria-disabled, and is now a tab stop (tabIndex={0}, or -1 when disabled); these attributes can be overridden through props. Its own onMouseDown, onTouchStart, onClick, and onKeyDown handlers are applied after the rest props, so a consumer-supplied version of any of those four is overridden. Adds full keyboard support, ArrowLeft/ArrowDown and ArrowRight/ArrowUp move the value by a new optional step prop (default 1), PageDown/PageUp move by step * 10, Home/End jump to 0/100, all disabled along with everything else when disabled is set. The value is clamped to 0–100. Also adds full touch support (drag via touch, mirroring the existing mouse behavior, onDragging fires for touch drags too). See the Slider docs.AudioPlayer's seek bar (@arkyn/components): built on Slider, so it now exposes role="slider" with aria-valuemin/aria-valuemax/aria-valuenow, is a tab stop, and responds to the same keyboard keys, unless disabled.ValidateDateService (@arkyn/shared) gains a new parseDateParts(dateParts, inputFormat) method, centralizing date-part reordering logic (brazilianDate/usDate/isoDate/timestamp) that was previously duplicated, copy-pasted, and separately maintained across parseToDate, formatDate (both @arkyn/shared), and validateDate (@arkyn/server). Behavior is unchanged from a consumer's perspective, this just guarantees the three call sites can no longer drift from each other. See the ValidateDateService docs.AudioUpload (@arkyn/components) gains 3 new optional text props, resendAudioText, invalidFileTypeText, and uploadErrorText, all defaulting to the same Portuguese text used before, so default behavior is unchanged. See the AudioUpload docs.qs (6.16.0), morgan (1.12.1), browserslist (4.29.0), and baseline-browser-mapping (2.11.24) are pinned via new root overrides, closing 6 vulnerabilities reported by bun audit in transitive dev/build tooling (used by @react-router/dev/@react-router/serve in the internal development app), none of these affect the runtime of the published packages. xss (1.0.15) is added as a new runtime dependency of @arkyn/components, used by the Tooltip sanitization above. @biomejs/biome, @types/bun, @types/node, and concurrently devDependencies are also bumped.@arkyn/cli, @arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.12.@arkyn/cli does nothing when run through its npm bin. The new entry point introduced in this release only runs when import.meta.url === `file://${process.argv[1]}`, which is false when the CLI is run through the symlink npm installs (node_modules/.bin/arkyn), so npx @arkyn/cli init --agents, npx @arkyn/cli --version, and every other command exit with code 0 and print nothing. Workaround: node node_modules/@arkyn/cli/dist/bundle.js <command>. Still present in v3.0.13, see that entry for details.@arkyn/ui is not part of this release, same reason as v3.0.11, it remains at 0.1.0, unpublished, and excluded from the monorepo's all:* pipeline pending npm publish permissions under the @arkyn/ scope.@arkyn/templates' countries export documents (no code change) that every country's flag is an external CDN URL (cdn.kcak11.com/CountryFlags), not bundled with the package. An offline, firewalled, or CSP-blocked environment will show a broken flag image, PhoneInput's typing/masking/submission is unaffected either way. See the countries docs.useAutomation (@arkyn/components) no longer silently stops re-firing its toast when two consecutive form submissions return an identical action response. No public API changes.useAutomation (@arkyn/components): the toast triggered by a server action response now fires again even when two consecutive submissions return exactly the same name/message (for example, a persistent server error like { name: "BadRequest", message: "Internal Server Error" } repeated). Previously, the hook memoized its toast effect (useCallback) based on derived primitive fields (message, name, firstErrorField) rather than the raw formResponseData reference; when those values were identical between submissions, the memoized callback kept the same identity, the useEffect dependency array saw no change, and the effect never re-ran, even though a new response had arrived. The effect now depends directly on the formResponseData reference instead of the separately memoized derived fields, so it re-runs on every new response, since a data router (for example useActionData() from React Router) hands back a new reference on every action response, even when its content is value-identical to the last one. Behavior for a first-time error, or for responses with a different name/message than the previous one, is unchanged. A regression test covering two identical responses in a row (asserting the toast fires both times) has been added.@arkyn/ui is not part of this release. Its first npm publish attempt failed with a 404, the CI automation token doesn't yet have permission to publish a new package under the @arkyn/ scope, so it has been temporarily excluded from the monorepo's all:* (build/test/typecheck/publish/release) pipeline. It remains at 0.1.0 and unpublished; it will be included once publishing permissions are resolved.@arkyn/server and @arkyn/components; the rest of the release is test coverage and build/CI consolidation across all five packages. One breaking change to production error responses, see Breaking Changes.UnprocessableEntity (@arkyn/server): fields and data are now redacted with parseSensitiveData before being stored on cause. Previously, a value submitted under a sensitive key, for example a password field resubmitted for form repopulation, was echoed back in clear text in the 422 response body. Matching is case-insensitive and covers nested objects. fieldErrors is unaffected, it only ever holds messages. See the UnprocessableEntity docs.BadResponse and its subclasses (@arkyn/server): cause is no longer included in the response body when NODE_ENV === "production". Previously it was always included, in every environment, which could leak stack traces, SQL fragments, or other internal error detail to clients in production. UnprocessableEntity is the one exception, its cause is redacted, structured data meant for the client (see above), not internal diagnostic detail, so it keeps being sent in production. See Breaking Changes.generateGAElements / generateGTMElements / appendToDataLayer (@arkyn/components): values such as measurementId, id, auth, preview, and dataLayerName are no longer interpolated directly into inline <script> string literals. A crafted value, for example one containing "); alert(1); //, could previously break out of the literal and inject arbitrary script. A new internal escapeForInlineScript utility now serializes these values safely (JSON-encoding plus neutralizing </script> sequences) and validates dataLayerName as a real JS identifier before using it bare; URLs built into src/iframe attributes are more strictly percent-encoded.validateEmail (@arkyn/server): the DNS lookup (MX/A/AAAA) no longer hangs indefinitely against a slow or unresponsive DNS server. Accepts a new optional second parameter, options.dnsTimeoutMs (default 5000), after which the lookup is treated the same as ENOTFOUND/ENODATA and the function resolves to false. See the validateEmail docs.decodeRequestBody (@arkyn/server): request bodies are no longer read and parsed without limit. Accepts a new optional second parameter, options.maxBodySizeBytes (default 5242880, 5 MB). Content-Length is checked upfront to fail fast, and the actual size of the body read is checked again afterward, so a missing or inaccurate Content-Length header doesn't bypass the limit. Exceeding it throws BadRequest before parsing runs. See the decodeRequestBody docs.RichText (@arkyn/components): the hidden <input> used for form submission no longer silently truncates its value at maxLimit when enforceCharacterLimit is false (the default). It now submits the full content past the limit, matching the prop's documented behavior. Typing is still blocked at maxLimit when enforceCharacterLimit is true, that path is unchanged. Found while writing real-browser tests for this component, see Testing below.CalendarProvider (@arkyn/components, used internally by DatePicker): the context value and its callbacks (changeDay, nextMonth, etc.) are now memoized with useMemo/useCallback instead of being recreated on every render, and the internal ViewService instance is now a module-level singleton instead of being re-instantiated per render. A memoized consumer of the calendar context no longer re-renders when an unrelated ancestor re-renders.node:dns in validateEmail's test suite instead of resolving real domains (gmail.com, outlook.com, etc.) over the network, making the suite deterministic and able to run offline.RichText (6 scenarios, run against real Chromium via a new @vitest/browser + playwright project scoped to that one file), replacing 6 tests that had been it.skip because jsdom doesn't implement the beforeinput/Selection APIs RichText's typing relies on. The rest of the @arkyn/components suite is unaffected and still runs under jsdom.@arkyn/components (6 new files, renderToString under a real Node environment, not jsdom) covering useHydrated, ClientOnly, MapView, GoogleAnalytics, GoogleTagManager, and FacebookPixel.@arkyn/cli's arkyn init test suite with end-to-end integration tests (missing package.json, first run, idempotent rerun, pre-existing AGENTS.md with unrelated content, multiple installed packages, and write confinement against a malicious package name).useFlipPosition, an internal hook consolidating the dropdown/calendar flip-positioning logic that was duplicated across Select, MultiSelect, and DatePicker. Behavior is unchanged, except it now also re-evaluates position on window resize while open, which none of the three did before. Not exported from the package's public entry point.apps/development (the internal, unpublished playground app) is now typechecked and built in CI on every pull request, it previously wasn't exercised by any workflow.generate-version.ts (byte-identical across all five packages) and generate-exports.ts (identical across @arkyn/server, @arkyn/shared, @arkyn/templates) into shared scripts at the repo root; @arkyn/components keeps its own generate-exports.ts variant, which also handles .css subpath exports. Adds a dedicated scripts/tsconfig.json (plus packages/components/tsconfig.scripts.json) so these scripts are typechecked in CI, which they weren't before.tsconfig.json into a new root tsconfig.base.json; per-package options that must stay relative to each package (outDir, rootDir, include, etc.) are unchanged.@vitest/browser, @vitest/browser-playwright, and playwright as devDependencies of @arkyn/components, and @types/bun/@types/node as devDependencies at the repo root, all test/build tooling, none are runtime dependencies of the published packages.@arkyn/cli, @arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.10.LogService/ApiService (from v3.0.9), keyboard/ARIA support in Select/MultiSelect/DatePicker and in Modal/Drawer/Popover (from v3.0.9), and the idempotent-publish script's error handling.axe-core) was added in this round. Manual keyboard/ARIA test coverage across Select, MultiSelect, DatePicker, Modal, Drawer, and Popover remains extensive, but doesn't replace an automated scan. Deferred as a deliberate scope decision, to avoid adding a second new devDependency category in the same release as the @vitest/browser/playwright addition above.import { X } from "@arkyn/components", as opposed to a subpath import) was not re-verified against a real bundler for tree-shaking of heavy optional peer dependencies (slate, mapbox-gl, etc.). Subpath imports, the documented and recommended approach, are confirmed working.RichText's browser tests was validated locally, not yet against a real GitHub Actions run.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates, and @arkyn/cli. No new components or hooks. One breaking type change, see Breaking Changes.LogService (@arkyn/server): removes a hardcoded, plain-http:// IP address that was silently used as the log ingestion endpoint whenever logBaseApiUrl wasn't configured. There is no fallback endpoint anymore, if logBaseApiUrl isn't set to a valid https:// URL (or http://localhost/http://127.0.0.1 for local development), no network call is made at all, and a warning is emitted through flushDebugLogs instead. requestHeaders, requestBody, responseHeaders, and responseBody sent to the log endpoint are now redacted with parseSensitiveData before submission (Authorization, Cookie/Set-Cookie, password, token/refreshToken/accessToken, apiKey, secret, in any casing). See Breaking Changes for the getConfig() type change this required. See the LogService docs.ApiService (@arkyn/server): debug output (enableDebug: true) no longer logs headers and request/response bodies in plain text. Both now pass through parseSensitiveData first, so Authorization and other sensitive fields are masked in debug logs instead of leaking secrets to the console. See the ApiService docs.parseSensitiveData (@arkyn/shared): key matching is now case-insensitive. Previously, sensitiveKeys (default ["password", "confirmPassword", "creditCard"]) only matched exact casing, so fields like Password, PASSWORD, or Authorization were left unmasked. The output key keeps its original casing, only the comparison is case-insensitive; only the value is replaced with "****". See the parseSensitiveData docs.@arkyn/cli: package names read from a project's package.json are now validated against the real npm naming rules (including scoped packages) before being used to build file paths, both when scanning for installed @arkyn/* packages and when resolving each package's AGENTS.md. A crafted name such as @arkyn/../../../etc/passwd is discarded with a warning instead of being used to construct a path outside node_modules.stripHtmlTags (@arkyn/shared): documentation-only fix, no behavior change. Its JSDoc now states explicitly that it's a best-effort, regex-based text transform, it doesn't decode HTML entities, can miss malformed markup, and must not be relied on as XSS protection ahead of dangerouslySetInnerHTML or similar. See the stripHtmlTags docs.FacebookPixel (@arkyn/components): options={{ autoConfig: false }} now actually disables Meta Pixel's automatic configuration. Previously the constructor used options?.autoConfig || true, so false || true evaluated to true and the option could never be turned off. undefined still defaults to true, unchanged.Select (@arkyn/components): a controlled value="" now correctly clears the field back to its placeholder. Previously value || selectedOption treated an empty string the same as "uncontrolled," so it fell back to whatever was last selected internally instead of clearing.SearchPlaces (@arkyn/components): no longer throws when Google returns a place with no address_components (for example, some point-of-interest or plus-code results). Missing address_components is now treated as an empty list instead of crashing handlePlacesChanged.calculateCardInstallment (@arkyn/shared): validation of numberInstallments (must be > 0) and fees (must be >= 0) now runs before the "no interest" short-circuit (fees === 0 || numberInstallments === 1), instead of after it. Previously, invalid input could skip validation entirely through that shortcut, for example calculateCardInstallment({ cashPrice: 100, numberInstallments: 0, fees: 0 }) returned { totalPrice: 100, installmentPrice: Infinity } instead of throwing. It now throws "Number of installments must be greater than 0" as expected. See the calculateCardInstallment docs.@arkyn/templates countries, used by findCountryMask/formatToPhone/PhoneInput): corrects digit counts and formats that didn't match real mobile numbers, verified against libphonenumber-js: China (11 digits, was 12), South Korea (mask widened to fit real mobile numbers), Vietnam (9 digits, was 10), Argentina (adds a two-mask array covering mobile and landline formats), and Indonesia (adjusts mask lengths/format). The public shape of mask (string | string[]) is unchanged; only the mask values for these 5 countries changed. No other country was touched. See Breaking Changes if you display or validate raw mask strings for these countries.@arkyn/cli: arkyn init no longer crashes with a raw SyntaxError when a project's package.json is malformed. It now reports the file path and parse failure, then exits with a non-zero code, matching the error-handling pattern already used elsewhere in the command.Select / MultiSelect / DatePicker (@arkyn/components): the field container is now focusable (tabIndex) and exposes role="combobox", aria-haspopup, aria-expanded, aria-controls, and aria-activedescendant. Enter/Space/ArrowDown opens the dropdown or calendar, ArrowUp/ArrowDown move the highlighted option (wrapping at the ends), Enter/Space selects the highlighted option, and Escape closes it and returns focus to the container. Options gain role="option" and aria-selected; MultiSelect also adds aria-multiselectable. MultiSelectOption changed from a non-interactive <div> to a real <button type="button"> so it's reachable by Tab and activatable by keyboard, visual appearance is unchanged (a CSS reset was added to compensate). DatePicker's calendar day cells are now focusable and selectable via Enter/Space; arrow-key navigation between days is not included in this release.Modal (ModalContainer) / Drawer (DrawerContainer) / Popover (@arkyn/components): all three now render with role="dialog", aria-modal="true", and focus management, powered by two new internal hooks, useEscapeKey and useFocusTrap. Focus moves into the overlay's content automatically when it opens, stays trapped inside it (Tab/Shift+Tab cycle within the content instead of escaping to the page), and returns to the element that had focus before it opened. Escape closes the overlay through the same exit path as clicking the overlay backdrop.@arkyn/templates (22 tests): structural invariants such as no duplicate ISO/UF/name across countries, mask/flag/code format checks, and cross-consistency between countryCurrencies and countryLanguage. The package's data files themselves were not changed.scripts/publish-idempotent.sh, used by all 5 packages' publish:beta/publish:latest scripts. It skips publishing (exit 0) if the exact version is already on the npm registry, publishes with npm publish --provenance otherwise, and treats a "cannot publish over previously published version" race from npm publish itself as success rather than failing the pipeline. Any other failure (auth, network, registry) still fails the pipeline as before.--provenance to every package's npm publish call (OIDC/id-token: write was already configured in CI but unused until now).>=3.0.2 to ^3.0.2 (caret) for @arkyn/shared/@arkyn/templates in @arkyn/components and @arkyn/server's package.json, and for @arkyn/templates in @arkyn/shared's package.json, preventing an unbounded future major version from being pulled in automatically..github/dependabot.yml now also monitors the github-actions ecosystem (actions/checkout, actions/setup-node, oven-sh/setup-bun, etc.), alongside the pre-existing npm ecosystem entry.@arkyn/cli, @arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.9.SENSITIVE_DATA_KEYS in @arkyn/server, useEscapeKey/useFocusTrap in @arkyn/components) are not part of the packages' public index.ts exports.stripHtmlTags's JSDoc, see Security above.@arkyn/cli, plus an AGENTS.md reference file bundled inside each of the four existing packages (@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates). Together they let AI coding assistants (Claude Code, Cursor, Copilot, etc.) discover and use the Arkyn API without reading source code. The Arkyn ecosystem now ships five published packages.@arkyn/cli, a new, zero-install command-line tool for the Arkyn ecosystem. Run it with npx/bunx, nothing is left installed afterward unless you add it as a devDependency yourself:bash
npx @arkyn/cli init --agents
arkyn init --agents reads your project's package.json, finds every installed @arkyn/* package (dependencies and devDependencies, deduplicated and sorted, @arkyn/cli itself always excluded), and writes or updates a marked block (<!-- arkyn:agents:start --> / <!-- arkyn:agents:end -->) in your project's AGENTS.md linking to each installed package's bundled AGENTS.md. The command is idempotent, rerunning it replaces its own block in place rather than duplicating it, and non-destructive, anything you've written outside that block is left untouched. If no @arkyn/* package is installed, or none of them ship an AGENTS.md yet, it prints why and doesn't modify any file. Requires Node.js >=18 and Bun >=1.0.0. See the @arkyn/cli documentation.AGENTS.md reference file, available at node_modules/@arkyn/<package>/AGENTS.md after install: @arkyn/components (906 lines), @arkyn/server (200 lines), @arkyn/shared (191 lines), and @arkyn/templates (62 lines). Each documents that package's exports with the exact signatures, props, defaults, and error behavior read directly from source, written to be self-sufficient for an AI assistant. @arkyn/cli is what connects these into your own project's AGENTS.md automatically. See "Using with AI coding assistants" on each package's introduction page (components, server, shared, templates).@biomejs/biome devDependency from 2.5.5 to 2.5.7 (and the root biome.json $schema reference to match).concurrently devDependency from ^10.0.3 to ^10.0.4.react-router and jest-dom in the internal lockfile resolution; no public API change for consumers.bun.lock: nanoid to ^3.3.18, postcss to ^8.5.26, and undici to ^7.29.0, alongside the pre-existing shell-quote override.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates, and the new @arkyn/cli) to 3.0.8; only the additions above are functional changes, the rest is packaging and dependency maintenance.useCopyToClipboard, a new hook exported from @arkyn/components, plus a round of tooling and test-dependency maintenance. No new components, and no public API changes to any existing component.useCopyToClipboard, a hook that copies text to the clipboard and reports whether it succeeded:tsx
import { useCopyToClipboard } from "@arkyn/components/useCopyToClipboard";const { copyToClipboard } = useCopyToClipboard();const success = await copyToClipboard("some text");
navigator.clipboard.writeText first. If it's unavailable or rejects, for example in a non-HTTPS context, an older browser, or a denied permission, it falls back to a temporary hidden <textarea> plus document.execCommand("copy"), removing that element afterward either way, including when execCommand itself throws. Never throws, copyToClipboard always resolves to true or false. See the useCopyToClipboard docs.@biomejs/biome devDependency from 2.5.5 to 2.5.7.concurrently devDependency from ^10.0.3 to ^10.0.4.react-router and the @react-router/* packages from 8.2.0 to 8.3.0 in the internal, unpublished apps/development playground.@testing-library/jest-dom devDependency from 6.10.0 to 7.0.0 (a major version bump, but it's only used in tests, not part of the published packages).overrides in the root package.json pinning shell-quote@^1.9.0, nanoid@^3.3.18, postcss@^8.5.26, and undici@^7.29.0, for consistency and security of transitive dependencies.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.7; only @arkyn/components has actual code changes in this release, the new hook above.RichText continuing to apply link formatting to text typed right after a link, plus an internal import fix. No new components, hooks, or public API changes.RichText: pressing Space or Enter right at the end of a link no longer carries the link formatting into the text typed next. Editing in the middle of a link is unaffected, formatting is preserved there as before. Nothing to configure, this is on by default and there's no prop to opt out.RichText: fixes the is-hotkey import (named import instead of default import) to match how the module exports it. No behavioral change, this only affects the module's internal import style.html-react-parser devDependency used to build/test @arkyn/components from 5.2.17 to 6.1.5. The public peerDependency range (>=5.0.0) is unchanged, no action needed if you already depend on html-react-parser 5.x or 6.x.react-router and the @react-router/* packages from 8.0.0 to 8.2.0 in the internal, unpublished apps/development playground.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.6; only @arkyn/components has actual code changes in this release.RichText, following the same toolbar-button-plus-modal pattern already used for images and videos, and tightens URL validation (https:// required) for both the new link button and the pre-existing video button.RichText: adds a link toolbar button that opens a modal to insert a URL. If text was selected when confirmed, that text becomes the link (its content is preserved, it only gains an href); if nothing was selected, the URL itself is inserted as the link's text. Enabled by default, no configuration required:tsx
<RichText name="content" />
linkConfig prop:tsx
<RichTextname="article"linkConfig={{modalTitle: "Insert link",modalInputUrlLabel: "Link URL:",modalCancelButton: "Cancel",modalConfirmButton: "Confirm",invalidUrlMessage: "Invalid URL",}}/>
hiddenButtons={["link"]}. Internally, this adds a new InsertLink subcomponent (mirroring InsertImage/InsertVideo), a link?: boolean / href?: string text mark in the editor's schema (RichTextCustomText), and serialize/deserialize support for <a href="...">, so toHtml/toRichTextValue already round-trip links with no extra setup. Linked text renders inside the editor with the arkynLeafLink class (underlined, colored). See the RichText docs.RichText: URL validation for link and video insertion now requires a well-formed https:// URL (new internal isValidHttpsUrl check). In the new link modal, the confirm button stays disabled and shows invalidUrlMessage until the URL is valid. In the pre-existing video modal, this is enforced in addition to the existing ?v= parameter check, a video URL using http:// is no longer accepted. See Breaking Changes.toRichTextValue (HTML → editor value): fixes a pre-existing bug, found while adding <a> support to deserialize but not otherwise related to links, where any HTML element that was the only non-text child of its parent (for example <p><strong>only this</strong></p> or <ol><li>single item</li></ol>) lost its formatting/type during conversion. This was blocking the most common link case, <p><a href="...">link</a></p>. No function signature changed, only the returned value for this specific edge case, from corrupted to correct.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.5.PhoneInput round-trip bug left over from v3.0.3, and cleans up comment/JSDoc prose across the codebase. No new components, hooks, or public API changes.PhoneInput: no longer duplicates or misplaces the country dial-code digits when the onChange value is round-tripped straight back as the controlled value, a common controlled-input pattern in React. A value in international format (starting with +) now has the selected country's dial code stripped before masking; values without a leading + behave exactly as in v3.0.3. See Breaking Changes.@arkyn/components, @arkyn/server, and @arkyn/shared (dashes replaced with commas for readability); no behavioral, type, or build changes.apps/development playground (components.slider.tsx, components.tooltip.tsx); cosmetic only.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.4; @arkyn/templates has no code changes at all in this release, and @arkyn/server/@arkyn/shared only have the prose cleanup above.DatePicker, a new form field for single-date and date-range selection built on top of the existing Calendar component, alongside a PhoneInput fix that changes what a controlled value renders as, a ModalProvider stale-closure fix, and an accessibility fix in FieldTemplate that benefits every form field in the library.DatePicker (@arkyn/components/datePicker, styles via @arkyn/components/datePicker.css), a form field wrapping Calendar in a popover, supporting type="single" (Date) and type="range" ([Date, Date]) selection, the same solid/outline/underline variants and md/lg sizes used across the library, prefix/leftIcon, errorMessage/useForm integration, disabled/readOnly/isLoading states, and a hidden input for native form submission (YYYY-MM-DD in single mode, a JSON-encoded [start, end] pair in range mode). Ships with 6 internal, non-exported subcomponents (DatePickerContainer, DatePickerContent, DatePickerCalendarContainer, DatePickerChevron, DatePickerOverlay, DatePickerSpinner) and a ~20-case test suite. Displayed dates and default text are fixed to the pt-BR locale in this version. See the DatePicker docs.PhoneInput: a controlled value is now formatted with the selected country's mask before being displayed, previously it rendered the raw digits while typing already applied the mask, an inconsistency between controlled and uncontrolled use. This changes the rendered output for existing controlled usages, see Breaking Changes.FieldTemplate: labels now receive htmlFor, correctly associating them with their field. This is a shared service used by nearly every form field (Checkbox, CurrencyInput, DatePicker, Input, MaskedInput, MultiSelect, PhoneInput, RadioGroup, Select, Switch, Textarea, and more), so the fix applies across all of them at once.ModalProvider: fixes a stale-closure bug where openModal/closeModal updated state from a closed-over variable instead of a functional setState updater, risking a lost update under fast/concurrent calls.ModalProvider: memoizes modalIsOpen, modalData, openModal, closeModal, closeAll, and the context value itself (useCallback/useMemo), reducing unnecessary re-renders in useModal consumers.placeholder parameter (default "9") to the internal applyMask utility, used by PhoneInput to support non-Brazilian country masks that use "_" as their placeholder character.datePicker/datePicker.css entries to exports and typesVersions in @arkyn/components's package.json.@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.3; only @arkyn/components has actual code changes in this release.apps/development playground: adds demo routes for DatePicker and for an automation pattern combining the existing useAutomation, useModal, and useDrawer; adds small "controlled" examples to about a dozen other existing demo pages. None of this affects published package behavior.DatePicker's popover decides to open above or below the field only once, when it opens, it does not recalculate on window resize or scroll.DatePicker exposes an onBlur prop, but it does not appear to be invoked internally in this version, treat it as unconfirmed rather than relying on it.v3.0.1-beta line (v3.0.1-beta.139 through v3.0.1-beta.207) to a stable, non-beta version, consolidating the full v2.2.3 → v3 rewrite described in the migration guide, together with a final round of stabilization corrections across all four packages (@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates).@arkyn/components, @arkyn/server, @arkyn/shared, @arkyn/templates) to 3.0.2, dropping the -beta suffix and moving from the beta npm dist-tag to latest.