Skip to content

v2.6.0

Choose a tag to compare

@58bits 58bits released this 28 May 13:04
· 149 commits to main since this release

Highlights

  • @byline/i18n — new package landing the admin-interface translation system. Ships the framework-agnostic primitives at the root entry (TranslationBundle, mergeTranslations(...), createFormatter, resolveInterfaceLocale) and the React surface at @byline/i18n/react (I18nProvider, useTranslation, LanguageMenu), with the built-in byline-admin namespace plus an adminTranslations({ locales }) factory shipping at @byline/i18n/admin. The package layers cleanly with the rest of the lockstep set — root is React-free and safe in server contexts (loaders, server fns, Workers), the React surface uses a single barrel to dodge the Vite optimizeDeps Context-identity trap that has bitten this codebase before. English and French bundle in the box; new locales drop in as a single JSON file alongside en.json / fr.json and are registered in the BUNDLES map — no new package, no new build, no new publish.

  • monorepo — the admin shell now renders end-to-end in English and French. Every editor-facing surface has been wired through useTranslation('byline-admin'): the sign-in page (with a pre-auth language menu), the admin chrome (app bar, menu drawer, breadcrumbs overflow, hamburger, preview toggle, language menu), the dashboard, the Account page (profile, password, preferences), the full Admin Users / Admin Roles / Admin Permissions modules (list views, detail containers, drawer forms, delete modals, inline permissions editor), the route-error shell, the collections list / create / edit / history / restore-version / api / view-menu / preview-link surfaces, and the entire document editor (forms, fields, presentation layout, status badge, diff modal). ICU MessageFormat handles plurals, dates, and numbers — no manual concatenation in translatable strings.

  • @byline/admin — per-user locale preference is now first-class. A new preferred_locale column on byline_admin_users stores each editor's choice; the language menu writes both a byline_admin_lng cookie (which carries the choice pre-auth, e.g. on the sign-in page) and the user row (post-auth). A reconcile flow at sign-in time resolves the two: the cookie picked on the sign-in page is persisted into preferred_locale if the user hasn't set one yet, otherwise the stored preference wins and the cookie is updated to match. The locale-resolution cascade is preferred → cookie → Accept-Language → defaultLocale, so even an anonymous visitor lands on a sensible default.

  • @byline/admin — substantial structural cleanup: the CMS-aware editor surface — forms/* (FormRenderer, form-context, document-actions, path-widget, navigation-guard, upload-executor), fields/* (FieldRenderer + every per-type field widget + the field-side services Context), presentation/* (AdminGroup, AdminRow, AdminTabs), widgets/* (StatusBadge, DiffModal) — moved from @byline/ui into @byline/admin. The motivation was layering: @byline/ui was carrying two unrelated jobs (framework-agnostic primitives like Button/Modal/Drawer/Table and the document-editor UI), and the latter embeds deep CMS concepts (CollectionDefinition, CollectionAdminConfig, DocumentPatch, blocks, locales, workflow status) that don't belong in a primitives package. The reshape opens a clean path for a future @byline/host-next adapter — the React components in @byline/admin are host-framework agnostic, the host adapter wires routing and server actions. A new @byline/admin/react barrel re-exports everything. breaking for external consumers — see the Breaking Changes section below.

  • @byline/core — the passwordSchema policy validator in @byline/core/validation now emits stable error codes (password.tooShort, password.tooLong, password.complexity) instead of free-form English messages, exposed as the PASSWORD_ERROR_CODES const. The matching code-to-translation map lives in the new translateValidationError(t, message) helper in @byline/admin/react, which client form components call on field.state.meta.errors. Keeps @byline/core portable to non-React / non-admin consumers (server-side request validation, future programmatic clients) while centralising the i18n surface in @byline/admin. This is the documented pattern for future shared schemas across the @byline/core/validation set.

  • @byline/webapp — the custom MediaListView is now the worked example for extending the i18n system from outside @byline/admin. New apps/webapp/byline/collections/media/i18n/ ships en.json, fr.json, and a mediaAdminTranslations({ locales }) factory exporting a webapp-media-admin namespace bundle. apps/webapp/byline/i18n.ts composes both bundles through mergeTranslations(adminTranslations({...}), mediaAdminTranslations({...})). The component itself calls useTranslation('webapp-media-admin') — toolbar labels, sort-option labels, the empty state, "no image" placeholder, pager aria-labels, and the upload aria all flow through t. The same pattern fits any third-party plugin, richtext extension, or custom field that wants to ship its own translations.

Migrations

  • @byline/db-postgres — the v2.6.0 admin-user table grows a preferred_locale varchar(16) column to back the per-user locale preference described above. Fresh installs (byline init or a brand-new pnpm drizzle:migrate) provision the column as part of the consolidated 0000_black_sabra.sql migration — the previously-split 0001_unknown_alice.sql has been collapsed into the initial migration so new projects always land on a single-step baseline.

    Existing installations — run the following one-liner against your database to bring the live schema up to v2.6.0:

    ALTER TABLE "byline_admin_users" ADD COLUMN "preferred_locale" varchar(16);

    The column is nullable; existing rows pick up null and fall back to the cookie / Accept-Language / default-locale cascade until each user picks a language. No data backfill is required.

Breaking Changes

  • @byline/admin / @byline/ui — the document-editor React surface moved from @byline/ui/react to @byline/admin/react. External consumers (anyone outside this monorepo importing these symbols directly) must update their imports.

    Symbols affected — all now live at @byline/admin/react:

    • Forms: FormRenderer, FormProvider, useFormContext, useFieldValue, useFieldError, useIsDirty, useIsFieldUploading, useSystemPath, useFormStore, useFormMeta, DocumentActions, PathWidget, NavigationGuardProvider, useNavigationGuardAdapter, useBeforeUnloadGuard, UseNavigationGuard, NavigationGuardResult.
    • Fields: FieldRenderer, ArrayField, BlocksField, CheckboxField, DateTimeField, FileField, FileUploadField, GroupField, ImageField, ImageUploadField, NumericalField, RelationField, RelationPicker, RelationSummary, SelectField, SortableItem, TextField, TextAreaField, DraggableContextMenu, LocalDateTime, LocaleBadge, ColumnFormatter, DateTimeFormatter, renderFormatted, useFieldChangeHandler, defaultScalarForField, placeholderForField.
    • Field-side services: BylineFieldServicesProvider, useBylineFieldServices, BylineFieldServices, GetCollectionDocumentsFn, UploadFieldFn, UploadedFileResult, CollectionListDoc, CollectionListParams, CollectionListResponse.
    • Presentational layout: AdminGroup, AdminRow, AdminTabs, AdminTabItem.
    • Editor-shared widgets: StatusBadge, DiffModal.

    Migration: most call sites can sed -i 's|@byline/ui/react|@byline/admin/react|g' the affected files, then split the import into two when the file legitimately uses both packages (the in-tree refactor split ~30 files this way during the move). @byline/admin is a workspace dep already if you depend on any admin module — otherwise add "@byline/admin": "^2.6.0" to package.json.

    Generic primitives (Button, Modal, Drawer, Table, Search, Datepicker, Section, Container, Card, Alert, every icon, every loader, the DraggableSortable / useSortable / moveItem dnd helpers) remain at @byline/ui/react — the boundary is "embeds CMS concepts in its API or types" vs "framework-agnostic React primitive". The new @byline/admin/react barrel is a single specifier on purpose, mirroring @byline/ui/react, to dodge the Vite optimizeDeps per-subpath Context-identity trap.

All other @byline/* packages bumped to 2.6.0 in lockstep with no behavioural changes this cycle.