AkiraAkira.dev
3 min read

Akira UI 3.0.0 breaks three imports and nothing else

An app that imported only Button could not bundle without recharts. The 3.0.0 tag moves charts, data table and form to their own subpaths.

also in FR PT

Cannot find package recharts imported from node_modules/@akira-io/ui/dist/index.js

The app that produced that line renders no chart. It imported Button.

Akira UI has declared recharts, @tanstack/react-table and react-hook-form optional peer dependencies since 1.1.0. The root entry point imported all three eagerly. Optional in the manifest, required by the bundler. The 3.0.0 tag fixes the contract, and the whole migration is three import lines.

What the word optional cost

The report came from a real app in early September. The Astro integration in payable-docs installed recharts, @tanstack/react-table and react-hook-form to consume navigation and overlay primitives. Nothing on those pages is a chart, a table or a form.

That app could have reached into dist/ and imported the chunk it wanted. It refused, because internal chunks are not public API and break on any release that reshuffles them. The published surface was the only supported route, and the published surface charged three packages at install time.

The migration

Every moved export kept its name, its props and its labels. The specifier is the only thing that changes:

// before, on 2.6.0
import { AreaChart, DataTable, Form } from '@akira-io/ui';

// after, on 3.0.0
import { AreaChart } from '@akira-io/ui/charts';
import { DataTable } from '@akira-io/ui/data-table';
import { Form } from '@akira-io/ui/form';

ChartContainer, CHART_PALETTE, chartColorVariable, FacetedFilter, RowActionsMenu, FormField and the Column, Row and ColumnDef types travel to the same subpath as the component they serve. An app that renders none of the three drops three dependencies and rewrites nothing else.

A test now walks the built import graph of the eight entry points on its list, declaration files included, and fails when any of them reaches one of the three peers. It also asserts each moved export left the root and arrived at its subpath. The regression that started this cannot come back quietly.

A major for three import lines

The objection is fair: a major version over specifiers inflates the number. Semantic versioning measures whether consumer code breaks, not how hard the break is to repair. Some code breaks here, so the major is honest, and the repair is a find and replace across three names.

The promise underneath the number is what changed. On 2.6.0, an app paid for components it never rendered. On 3.0.0 it pays for what it imports.

The rest of the tag

Passkeys ship as composable blocks: a list, an item, a register button, a sign-in button, and an Inertia entry point bound to @laravel/passkeys, so a Laravel app wires the credential flow without writing the WebAuthn dance again.

The calendar and the date range filter follow the locale provider’s date locale instead of pinning Portuguese. That is the kind of default that only looks fine to whoever wrote it.

Chart series colors no longer collide when two keys sanitize to the same custom property name. That fix was ready before the 2.6.0 tag and I dropped it there, because review found it broke the tooltip and the legend. Fixed properly, it shipped here.

The two-factor setup dialog is the reason 3.0.0 landed a day late. The QR code stretched to fill the panel and pushed the heading and the Continue button off screen. It surfaced on the docs site demo, not in a test, and not from a user. The QR now holds a fixed size inside a light frame, the setup key is a read-only field with reveal and copy, and the dialog body scrolls while the heading and the primary action sit outside the scroll area.

A peer is optional when the bundler agrees, not when the manifest says so.

share