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.