---
title: "Changelog"
description: "Release notes aggregated directly from the package changelogs."
---

> Documentation Index
> Fetch the complete documentation index at: https://pupitre.rikology.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

These notes are imported from each package's CHANGELOG.md at build time. Unreleased entries describe work on the current source branch, not a published npm release.

## create-pupitre-plugin

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/create-pupitre-plugin/CHANGELOG.md)

### Changelog

### Unreleased

- Add opt-in `--with-ui` (`createPlugin({ withUi: true })`) scaffolding: shared catalog extension, typed server field factory, accessible React component, published `.`/`./catalog`/`./react` subpaths and generated tests; generated factory names avoid collisions.

### 0.1.0

- Added CLI and reusable createPlugin API with kebab-case/scoped names, safe directory handling and force overwrites.
- Ship a working navigation plugin template with TypeScript, tsdown and a public-manager Japa integration test.
- Security (WP42): reject parent traversal before path normalization and refuse symlink destinations/output files, including forced writes.
- Generated packages run npm test against published build exports without source-only conditions; regression coverage uses an extracted Pupitre tarball outside the workspace.


## pupitre

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre/CHANGELOG.md)

### Changelog

### Unreleased (1.1.0)

### Changed
- Localization (breaking for strict comparisons): server `t()` no longer returns its input unchanged. It returns the readable, interpolated English text followed by invisible signed translation metadata, so `t('Title') === 'Title'` is false. Resolve with `resolveTranslations()` / `stripTranslationMarkers()` before strict comparison, string transformation or delivery outside Pupitre responses, and use `captureTranslation()` / `restoreTranslation()` to persist translation intent. `Error.message`, logs and regex/`includes` checks keep reading the English text.
- Tables: toggle/checkbox inline-edit cells display "Enabled"/"Disabled" instead of "On"/"Off"; Shield permission checkboxes show human labels ("View any", "View") instead of ability names.
- Tenancy: the default column tenancy scope binds the tenant id as a string so TEXT ownership columns match on SQLite (better-sqlite3 binds JS numbers as REAL); integer ownership columns keep comparing and filling as integers on SQLite and PostgreSQL.

### Added
- Localization: request-local English/Indonesian localization. Server `t(key, params?)` markers are resolved at every Pupitre response boundary (structured Inertia props including full-page/SSR visits, JSON actions/options/search/notifications, auth mail, CSV/progress output); plural `{ one, other, zero? }` messages and `{name}` interpolation; `defineConfig({ i18n })` host dictionaries and `definePlugin({ messages })` plugin dictionaries; localized VineJS messages with field labels (host Vine providers keep precedence for untranslated rules); panel `.locale(string)` / `.locales([...])`, user/session/Accept-Language resolution, profile language selector and an optional `locale` users-column migration stub; `locale` is a reserved page/resource slug. Record data is never translated. ADR-0023.
- Localization: persisted notifications and queued CSV headers store readable text plus translation intent and are translated at read/render time (survives `APP_KEY` rotation); `tools/i18n-check` (`npm run i18n:check`) enforces Indonesian coverage of every literal `t()` key in CI.
- Catalog extensions: `defineCatalogExtension()` / `mergeCatalogExtensions()` in `@rikology/pupitre/catalog` and `defineField()`, `defineColumn()`, `defineEntry()` factories with typed custom methods, inherited field methods, Zod-validated props, VineJS value validation, server formatting/dehydration and live/repeater/action-modal support; `definePlugin({ catalog })` and `page.catalogExtensions` metadata. Element `type` in specs is now an open string validated by the client registry. ADR-0024.
- Tables: `table.filter.queryBuilder(name).constraints([...])` with `qb.text/number/date/boolean/select/relationship` constraints (`qb.number(...).integer()` for integer columns), negation, AND-within/OR-between groups, strict allowlist validation (400 on malformed, fractional-for-integer, out-of-range or year-zero values), size caps, scope-safe OR wrapping and timezone-aware calendar comparisons across table data, summaries, groups, relation tables, table widgets and exports. ADR-0025.
- Tables: opt-in `.multiSort(max = 3)`, ordered `.defaultSort([...])` and URL `sort=title,-price` with deterministic qualified primary-key tiebreakers; existing single-sort links keep working and `sort=` clears defaults.
- Validation: public `compileValidator()` / `FormValidator` so plugins, commands and jobs can validate data with a form's VineJS rules.
- Authorization: opt-in panel `.strictAuthorization()` validates after panel/plugin resolution that every resource, nested resource and relation-manager related model has an explicit or discovered policy (`E_PUPITRE_MISSING_POLICY`), denies policy-less checks at runtime, and is reported by `pupitre:inspect`.
- Panels: `.environmentIndicator({ visible?, label?, color? })` shares an `environment` prop (shown outside production by default); `.impersonation({ canImpersonate?, canBeImpersonated?, redirectTo? })` with `actions.impersonate()` (fail-closed `impersonate` policy ability, scoped tenant lookup, actor MFA required, rotated sessions, leave endpoint, profile security mutations blocked while impersonating, `pupitre:impersonation:started|stopped` events); `.sessions({ history?, retentionDays? })` tracks hashed browser-session tokens per login method, revokes unknown/revoked sessions on the next request, records authentication history (failed logins never store unknown emails), adds profile session management (password/passkey-confirmed "log out other sessions") and the policy-protected `loginHistoryWidget()`; `pupitre:prune` applies session/history retention. Migration stubs 1791700000045/46.
- Navigation: `defineCluster(slug)` with `.label/.icon/.navigationGroup/.sort/.subNavigationPosition`, `resource.cluster()` and `page.cluster()`; clusters are discovered from their children, collapse to one authorized navigation entry, prefix child routes (tenancy URL mode and nested resources included), render sibling sub-navigation and breadcrumbs, and reject slug collisions at boot. `node ace make:cluster`.
- Widgets: `.component(name, loader?)` delivers loader data to host components; `.actions([...])` runs authorized, Vine-validated widget actions through the JSON action transport; radar and scatter chart types with typed loaders.
- Tables: model-free custom-data tables via `t.records()`, `t.recordKey()` and `t.resolveRecord()` with URL-backed validated search/sort/filter/pagination, count-free pagination, server-resolved row/header/bulk actions and definition-time rejection of Lucid-only features (ADR-0026); `authorizeAbility()` passes the resolved row to host abilities.
- Tables: opt-in saved views (`t.savedViews()`, `t.presetViews([tableView(name).query(...).columns(...).default()])`) for resource and relation-manager tables, scoped per user/panel/tenant/table with validated stored queries, per-user defaults, explicit column visibility and fail-closed `managePublicViews` for public views. Migration stub 1791700000044.
- Exports: `actions.export().formats(['csv', 'xlsx'])` with a dependency-free streaming XLSX writer (inline strings, typed cells, bold header, CSV formula-injection policy), persisted format metadata, locale-resolved headers and private queued downloads; queued CSV output now streams too.
- Forms: `s.repeater(...).table([s.tableColumn(label).width().alignment().markAsRequired()])` table repeaters (positional or derived headers, `E_PUPITRE_INVALID_REPEATER_TABLE`); rich editor `.mergeTags()`, `.mentions([mentionProvider(char).options()/.search()])` through the protected options endpoint with save-time forgery stripping, and Vine-validated `.customBlocks([richBlock(id)...])`; `renderRichContent()` and `textEntry.html()/.richContent()` render escaped merge values and block HTML.
- Plugin support: public `parseInZone()`/`toIsoInZone()` timezone helpers; custom and resource pages accept structural widget builders.
- Plugin support: public `panelRouteGroups()` (authenticated, tenant-resolved panel route groups for plugin endpoints) and storage/upload helpers `createPupitreStorage`, `consumeFormUploads`, `sniffUploadMime`, `isUploadMimeAllowed`, `DEFAULT_UPLOAD_MAX_BYTES`.

### Fixed
- Action modals: uploads inside action forms use the server-issued `uploadOwner` (resource/record context) and action validation passes the resource slug to owned-upload validation, so session-owned uploads in header-action forms validate.
- Forms: `.hidden()` closures compile to a correct negated visibility condition — boolean literals flip (`.hidden(() => true)` → `visible: false`), `&&`/`||` conditions negate via De Morgan, and `!get(x)` drops its `not` — instead of spreading `not: true` onto non-condition values (which produced an invalid `{"not":true}` spec, e.g. the media resource's hidden `path` upload). Lifted `.visible()`/`.hidden()` results that are not valid json-render visibility conditions now fall back to server evaluation.

### Unreleased (1.0.0, milestone M4)

### Changed
- Catalog (M4): `Repeater` and `Builder` accept the common field props; the schema compiler now emits `name`, `label`, `helperText`, `hint`, `required` and `columnSpan` for repeaters, as it already did for builders.
- Actions (M4): confirmation descriptors default the submit label to the action label ("Delete", "Restore") instead of "Confirm", and destructive confirmations carry a default "This action cannot be undone." description. `confirmation.description` is now forwarded. Camel-case built-in labels are split into words ("Force Delete", not "ForceDelete").

### Added
- Shield (WP37): official `@rikology/pupitre-shield` plugin with Lucid roles/permissions, grouped role-management forms, request-cached Bouncer policies, super-admin bypass, explicit-policy preservation, custom action and nested resource grants, `shield:sync`, User-role assignment helper and custom-page access helper.
- Shield review (M4): hidden relationship fields skip preload authorization/options queries using the current request context; Shield inventories are panel-local and include related-model custom abilities only for enforced resources.
- Documentation (WP41): English docs site (Cloudflare Nimbus) at pupitre.rikology.com with M1–M3 guides, plugin authoring, Filament API migration, source-generated TypeScript references and package changelogs; static output publishes the generated shadcn registry index and payloads.
- Plugin generators (WP36): `make:plugin <name>` creates an idempotent local plugin; `--force` overwrites, while `--package [--dir]` executes the standalone scaffolder pinned to the core major without a shell.
- Registry (WP40): `pupitre:ui add` validates names and runs shadcn@4. Explicit root-relative file targets preserve the Inertia source tree; unsafe flattening `--path` passthrough is removed. Hook item names use kebab-case. Real CLI smoke type-checks copied Select and its dependency closure.
- Plugins (WP36): `definePlugin({ name, register, resources, pages, widgets, middleware, navigation, extendResource, boot })` and panel `.plugins([...])`. Plugins apply in declaration order at boot: `register(panel)` returns a new immutable panel builder, contributions are appended to the panel's own entries, `extendResource()` runs for every (nested) resource, and `boot({ app, manager, panel })` runs before routes are registered. Plugin navigation merges into explicit panel navigation by group label, or follows the derived navigation and claims the resources it references.
- Audit (WP38): optional `audit` adapter configuration and lazy `drivers/audit` reader over audit-trail, configurable model discriminators, normalized changes and pagination; timeline widgets format timestamps in the panel/user timezone and require record view authorization even on edit pages.
- Read-only activity resources omit mutation routes and implicit Delete actions. Text-column/text-entry URLs support authorized asynchronous subject links, with request context passed through table rendering. Audit timestamps handle the peer's real SQLite ISO storage and UTC-normalized filter bounds.
- Realtime (WP39): optional lazy `RealtimeAdapter`, boot initialization, URI-encoded user/resource channels, tenant-scoped table subscriptions, authenticated shared client metadata, and best-effort post-commit mutation/inbox pings (including queued imports and bulk actions). Existing polling remains available without a realtime client.
- Realtime review (M4): namespace wire pings as `pupitre:notification` / `pupitre:table`, notify every enabled panel's user channel, and share authorization patterns plus panel guard resolution with transport plugins.
- Audit review (M4): remove unused `recent`/`AuditFilters` contract; custom adapters serve record timelines while Activity queries the audit-trail model.
- Import/export lifecycle records persist canonical string tenant identifiers, keeping queued worker ownership checks consistent across SQLite and PostgreSQL.
- Security audit (WP42): enumerate all example mutating routes for missing/invalid XSRF rejection, enforce explicit 403 CSRF responses, disable browser Zod JIT probes under strict CSP, and publish the 1.x private disclosure policy.
- Restrict UI installer registry URLs and validate component names before network access; block sensitive paths, symlinks and recognizable embedded credentials from registry publication.
- Release performance audit (WP42): enforce measured gzip/Brotli budgets and lazy heavy-feature boundaries after the example CI build; load the example Transmit client only for an enabled realtime driver.
- Packaging audit (WP42): verify every built runtime import uses a declared dependency/peer and resolves under ESM conditions; validate all seven package tarballs and release assets, and exclude configure-test `tmp` declarations from the core build.
- Harden MIME validation and bounded tempfile buffering for resource/plugin uploads, avatars and CSV imports; apply default ceilings and serve CSV attachments with nosniff.
- Throttle sensitive panel and example-host authentication endpoints with bounded local fallback or installed shared limiter, propagating limiter backend failures instead of bypassing protection.
- Bind synthetic queued HttpContext into the Bouncer container resolver so injected authorization policies work in production jobs.
- Login and passkey-options quotas: the IP-wide auth budget is 60/minute (it counts successful sign-ins, so shared IPs are not locked out) while the per-email failure window stays at 5/minute and clears on success.
- Nested resources reused by a parent's relation manager (or reached outside nested routes) no longer fail with `.where expects value to be defined`: the nested scope constrains `:parentId` only when the route carries it.
- Release 1.0 (WP42): `@rikology/pupitre`, `-react` and `-ui` move lockstep to `1.0.0`; official plugins and `create-pupitre-plugin` start at `1.0.0`; inter-package peer ranges are `^1.0.0`; `catalogVersion` is `1.0` (clients on a different major reload once); npm dist-tag `latest` via the new `release.yml` trusted-publishing workflow.
- Final confirmation remediation: nested child scopes now fail closed outside an explicitly bound parent; owning relation managers bind that parent without leaking global-search or relationship-option rows.
- Preserve preloaded options for client-reactive visibility while still skipping server-hidden relationship authorization and queries.
- Align passkey login verification and social callbacks with the 60/minute shared-IP sign-in budget; evict the oldest local limiter key at the 10,000-key cap rather than locking out every new identity.
- Initialize browser CSP jitless configuration before every catalog schema module and probe the built catalog in a fresh process during `verify:exports`; retain repeated-success login and version-derived scaffolder regressions.
- Widgets: stats widgets span the full dashboard row by default (Filament `StatsOverviewWidget` parity); an explicit `.span()` before or after `.stats()` still wins.
- Passkeys: deleting your own passkey from the profile no longer fails with "`limit` has no effect on a `delete` statement". The ownership lookup and the delete now use separate query builders.
- Option labels: `table.text()`/`table.badge()` columns and `textEntry`/`badgeEntry` entries accept `.options(...)` (same shapes as select options) and display the label of the stored enum value instead of the raw value (`Published`, not `published`). Array values map item by item, badge colors stay keyed by the raw value, and table group headings use the options of the same-named column. `resource` scaffolding emits the options for enum columns. `BadgeEntryBuilder` is exported and types `colors()`.

### Fixed
- Schema cache: field actions (`prefixAction`/`suffixAction`/`hintAction`), page actions, select action links, option/upload URLs and other request-derived props are re-evaluated per request on cache hits; previously the first request's record, URL and authorization result could be served to later users and tenants in production.
- Actions: header actions (no record, not bulk) require `viewAny` before running, matching the action form endpoint; custom actions without `.can()` could otherwise run for users denied the resource. Form field actions on the create page (no record) authorize with `create`.
- Tenancy: host `.scope()`, `table.query()`, custom filter/tab and relationship scope callbacks are applied as one AND-grouped `WHERE` clause, so a top-level `orWhere`, `orHas`/`orDoesntHave` or an OR inside a model scope (`withScopes`/`apply`) can no longer widen results past the tenant predicate. Non-`WHERE` calls (`orderBy`, `preload`, `join`, `select`, `withCount`) still apply to the main query. Nested resources keep the parent constraint outside the host scope group.
- Tenancy: related models that are not panel resources but carry the ownership column are tenant-scoped in relationship options, relationship validation and inline option creation, matching relation managers.
- CSV: exported cells and headers starting with `=`, `+`, `-`, `@`, tab or carriage return are prefixed with `'` to prevent spreadsheet formula execution (sync, queued and failed-row CSVs). Numeric values are unchanged.
- Imports: CSV imports call `beforeSave` in the same lifecycle position as resource create/update.
- Exports: queued exports persist the export action name; progress and download recheck that action's current `.can()` grant and fail closed when it is missing. Exports queued before this change must be recreated.
- Return URLs: `X-Pupitre-Url` is validated by one helper for resource, relation-manager, page and export requests; relation managers previously used it unvalidated, and `/\host` forms are now rejected everywhere.
- Records: identifiers that PostgreSQL cannot parse for the primary-key type (e.g. `/activity/create` on a UUID resource) return 404 instead of 500 on record, relation-manager, nested-parent, upload and bulk lookups.
- Uploads: permanent deletes (destroy, force delete, bulk and relation-manager deletes) remove the record's file-upload objects after the transaction commits when no other row references them; soft deletes keep files and storage errors are logged without failing the delete. Reference checks are batched per field.
- Uploads: new optional `pupitre_uploads` table tracks uploaded keys until a resource, list-page modal, custom page or action-form submission consumes them (after validation, before the save handler). `node ace pupitre:prune-uploads --older-than=24h` removes abandoned uploads: it claims each row before deleting its object and skips, without consuming, file fields whose references it cannot check (relationship repeaters, custom pages, removed fields). Run `node ace pupitre:install` and `node ace migration:run` to enable it; without the table uploads keep working and the command exits with an error.
- Queued bulk actions validate their form in the request, so field errors reach the action modal before anything is enqueued.
- Tables: image URL resolution is memoized per page and resolved concurrently instead of one storage `exists` round-trip per cell.
- Tests: the PostgreSQL suite passes (relation fixtures no longer replay DDL); c8 enforces 85% line coverage on `src/compiler`, `src/query` and `src/authz`.

### Unreleased (0.3.0-beta, milestone M3)

### Added
- Widgets (WP27): added immutable widget definitions, dashboard/custom/resource-page widget grids, deferred props, an authorized widget endpoint, and `make:widget` generation.
- Testing (WP34): completed `@rikology/pupitre/testing` with controller-backed Japa resource/custom-page visits, VineJS form submission assertions, and action execution; added package integration and example functional coverage.
- Relations (WP28): added `.relations()`/`defineRelationManager()` with grouped hasMany/manyToMany table/form tabs, tenant/parent scoping, authorization fallbacks, attach/detach/associate/dissociate and related CRUD actions; nested resources, record sub-navigation, `pages.list().modals()`, and `make:relation-manager`.
- Social login (WP32b): panel `.social()` provider configuration, lazy Ally loading, guest redirect/callback, verified-email matching/registration, and MFA-aware completion.
- Import/export (WP30): added `actions.export()`/`actions.import()` CSV actions, immutable `exportColumn()` formatting, prefix-aware filtered exports, schema-derived header mapping (including Skip), per-row VineJS validation/upsert transactions and failed-row CSVs. Added `maxSyncRows` hand-off, chunked scoped export jobs, boot-registered serializable import/bulk/export handlers, nested parent authorization/ownership, `pupitre_exports` progress/download lifecycle, private storage, notifications and pruning.
- MfaPasskeys (WP32a): added the `completeLogin` MFA continuation seam, panel `.mfa()`/`.passkeys()` configuration, TOTP enrollment/challenge with single-use recovery codes, profile security UI, and passkey registration/management plus usernameless login; WebAuthn assertions consume one-time challenges and update credential counters/last-use timestamps.
- Theme (WP33): added validated `defineTheme()` definitions and panel theme compilation for font, density, colors, sidebar modes, content width, and safe stylesheet/font URLs; `indexAdmin()` now discovers theme files.
- Table interaction (WP29b): added immutable inline-edit columns with Vine validation and scoped PATCH transactions, scoped reorder endpoints, polling, persisted column toggles, responsive card layout, and deferred filter application.
- Timezone (WP35): date/time parsing now respects effective component/user/panel timezone, rejects DST gaps, selects the earlier overlap offset, and keeps native datetime inputs independent of the browser zone.
- Editors (WP26b): added MarkdownEditor and CodeEditor schema builders, image editor metadata, sanitized server-side Markdown rendering for TextEntry, and Vine string validation.
- TableData (WP29a): added filtered-query summaries, group results, relation counts, per-column search, safe row classes, manual soft-delete scoping/actions, and deferred list data; resolved deferred response validation and now serialize column summary/image metadata.
- Action extras (WP32c): action deep links, per-user/IP action rate limits, modal footer actions and submit/cancel visibility, and action-bearing flash/database notifications.
- Server evaluation (WP31): added server-backed `afterStateUpdated` form closures, guarded tenant-scoped evaluation endpoint, and complete `pupitre:inspect` closure/index diagnostics.
- WizardBuilder (WP26a): added immutable `s.builder()` typed block schemas, per-block Vine validation and form wizard steps with server-error step mapping.
- SchemaParity (M1–M3): added masked/tel/URL text validation, textarea autosize and field hint actions, bounded `s.slider()`, transactionally synchronized `hasMany` repeaters, and related-model policy checks for Select validation.
- SchemaParity/WizardBuilder (M3): apply builder field validation, metadata, defaults, and visibility; share schema traversal across wizard steps, builder blocks, and repeaters, and preserve explicit nested item bindings and form-field actions.

### Changed
- Wire protocol: `catalogVersion` is now `0.3` (new catalog components, descriptor fields, page props and endpoints listed above); packages released lockstep as `0.3.0-beta`. New optional peers: `@adonisjs/ally`, `@adonisjs/queue`, `@simplewebauthn/server`, `otplib`, `qrcode`.
- Authorization: Pupitre authenticates through `ctx.auth.authenticateUsing([panelGuard])`, so Bouncer policies see the panel user without relying on the host's silent-auth middleware; a policy that defines an ability method is final (fallbacks such as `replicate → create` apply only when the method is missing).
- Widgets/TableData (WP27/WP29a): preserve resource/custom-page context with pagination-safe widget refresh URLs, require explicit record-page context, and recheck nested parent/page access; normalize SQL group keys through Lucid attribute serialization, merge colliding groups with weighted averages, and correlate relation summary expressions so filtered-set totals survive pagination.
- MfaPasskeys (WP32a): bound MFA guesses atomically per actual user primary key across renewed password-login sessions, recovery-code regeneration and MFA disable; retain a fail-closed in-memory budget without an available optional limiter, and consume recovery codes under a transactional row lock.
- WizardBuilder (WP26a): retain owning-resource metadata and full nested upload paths during block validation, persist transformed block output and propagate validation backend errors; match uploads and nested image entries by full schema path and resolve typed block `.data` paths consistently for persisted files and cleanup.
- Import/export/Table interaction (WP30/WP29b): remove ordering from export counts, use primary-key batch tiebreakers and tolerate queued result-count drift; persist setup failures, keep completed exports downloadable after notification failures, and respect explicit reorder denials.
- Relations/Security (WP28/WP30): require record view permission and server-side visibility for custom relation actions, resolve bulk selections through parent/tenant scopes with per-record authorization, preserve explicit policy denials, skip ownership columns for tenant-less unregistered related models, and re-check nested parent access in queued bulk workers.
- Server evaluation (WP31): accept incomplete form state containing empty strings and nulls without rejecting unrelated fields.
- Auth (WP32): clear the login-attempt window after valid credentials while retaining failed-password and MFA throttles.
- MfaPasskeys/SocialLogin (WP32): cap MFA continuation at five failed attempts, preserve MFA enforcement for passkeys without verified user verification, expose enrollment routes without `.profile()`, and publish social identity migration with query-free callback redirects.
- Widgets (WP27): table widgets now run model-backed table queries through the compiler, prefix-aware query parser, and shared server formatter; custom pages accept `.widgets()` and `.filters()`.
- Widgets/TableData (WP27/WP29a): calculate summaries in SQL over scoped, filtered queries; support grouped summaries without loading all matching rows. Widget registries accept lazy barrel objects, widget descriptors expose their GET data endpoint, and generated table widgets bind a model.
- Testing (WP34): isolate fixture rows in integration tests, represent deferred props in helper contexts, and resolve notification inbox database access from the active request app instead of a cached app binding.
- Security (M3): enforce record and relation authorization, tenant-scope unregistered related models, preserve soft-delete hooks for relation deletes, and make panel-guard users visible to Bouncer policies.
- Action extras (WP32c): resolve record-page header deep links independently of record row actions while preserving scoped record authorization.
- Table interaction (WP29b): reorder requests accept a page-window offset so moving page-two rows preserves preceding global positions.

### Unreleased (0.2.0-beta, milestone M2)

### Added

- Panel builder: `.login()`, `.registration()`, `.passwordReset()`, `.emailVerification()`, `.profile({ avatar })` (`.login(false)` keeps host auth), `.navigation([...])` with the new `nav.group()/nav.item()` builders (sort, badge closures, visibility, collapsible groups, external links), `.userMenu([...])`, `.domain()`, `.theme({ primary, darkMode, radius, vars })`, `.tenancy({ mode: 'url' | 'implicit', ... })`, `.notifications({ interval })`, `.globalSearch({ keyBinding } | false)`, `.pages([...])`.
- Auth (WP17): spec-based login/logout, forgot/reset password (`pupitre_password_resets`, SHA-256 one-time tokens per guard with TTL, `pupitre:prune`), optional registration and signed email verification gate, profile (name/email/password with current-password check, avatar via the storage adapter), login/reset throttling through `@adonisjs/limiter` when configured. `MailAdapter` + `@rikology/pupitre/drivers/mail` over `@adonisjs/mail`. Migration stubs published by `configure`.
- Navigation & panels (WP18): permission-filtered nav (`viewAny`, page `canAccess`), boot-time validation of nav slugs, multi-panel support, `make:panel`.
- Authorization (WP19): full Bouncer mapping incl. `authorizeModel()` for related models, self-handled `E_PUPITRE_FORBIDDEN` (JSON 403 / Inertia redirect back + flash), `pupitre:inspect [--json]` reporting missing policies/abilities.
- Tenancy (WP20): `TenancyAdapter`, default `tenant_id` column driver, `@rikology/pupitre/drivers/tenant` over `@rikology/adonisjs-tenant`, tenant-prefixed routes (mode `url`) or user current tenant (mode `implicit`), switch endpoint, automatic ownership fill, resource `.scope(false)`; every resource query goes through `scopedQuery()`.
- Notifications (WP21): `NotificationsAdapter`, `@rikology/pupitre/drivers/notifications` over `@rikology/adonisjs-notifications`, `/_pupitre/notifications` (+ `/read`), `notify.*().sendToDatabase(user?)`.
- Global search (WP22): resource `.globallySearchable([...])` / `.globalSearchDetails()`, `GET /_pupitre/search` (parallel, 5 authorized results per resource, tenant/policy scoped).
- Custom pages (WP23): `definePage()` (path, form, fill/save, actions, `component()` escape hatch), page discovery in `indexAdmin()`, `make:page`.
- `make:resource` (WP24): Lucid model introspection (columns, types, enum meta, relations) or `--from-migration`.
- Dynamic options (WP25): select `options(closure)` (with `values` for validation/label resolution), `relationship(name, title, scope?)`, `searchable()`, `preload()`, `createOptionForm()`/`createOptionUsing()`, `prefixAction()`/`suffixAction()`; `GET/POST /_pupitre/options/:resource/:field`; server-side validation of submitted option values.

### Changed

- Wire protocol: `pupitre.panel` gains `profileUrl`, `userMenu`, `search`; `pupitre.tenant`/`tenants` (`TenantOption`), `pupitre.notifications` (`{ unread, url, readUrl, interval }`), `navGroup.collapsed`, `brand.logoDark`; new response schemas for search, notifications and options. `catalogVersion` is now `0.2`.
- `page.key` includes the tenant id; spec cache keys include the request-bound panel path.

### Unreleased (0.1.0-beta, milestone M1)

### Added

- Builders: `definePanel` (path, guard, middleware, resources, brand, theme, timezone, breadcrumbs), `defineResource` with model-typed form/infolist schema, table, actions, pages and lifecycle hooks (`fill`, `mutateBeforeCreate/Save`, `before/after Create/Save/Delete`, `create`/`update` overrides; hooks receive the Lucid transaction after `ctx`), `schema` (inputs, layouts, repeater, file upload, rich editor, infolist entries), `table` (text/badge/icon/image/color/custom/fragment columns; select/ternary/dateRange/custom filters), `action`/`actions` (create/edit/view/delete/bulk), `pages`.
- `@rikology/pupitre/catalog`: zod-only component catalog, wire-protocol schemas (`pageProps`, `actionDescriptor`, `actionResult`, `tableDescriptor`, `tableData`, …), `dynamic()`/`raw()` expression helpers, fixtures and `assertRegistryComplete`.
- Compilers: schema → json-render spec with closure lifting (expressions/`$computed`, static fallback, `E_PUPITRE_UNLIFTABLE_CLOSURE`), record/user-independent spec cache, VineJS validator (one `vine.compile` per definition, nested repeater paths, model-aware `unique`, upload key ownership, disabled/hidden dehydration), table query/descriptor/data with server-side formatting, action descriptors/forms/execution.
- HTTP runtime: per-panel routes (`<panel>.<slug>.<page>`), Inertia page props with top-level `data` for tables, JSON `ActionResult` protocol for actions, Bouncer mapping (fail-closed when a policy exists without Bouncer), uploads (`/_pupitre/upload` with MIME sniffing, size limits, session ownership, cleanup of replaced files), HTML sanitization.
- `node ace configure @rikology/pupitre` / `pupitre:install`, `pupitre:routes`, `indexAdmin()` discovery hook.


## pupitre-audit

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-audit/CHANGELOG.md)

### Changelog

### Unreleased

- Localize Activity navigation, fields and timeline headings.
- Keep historical audit entries visible without subject links when PostgreSQL cannot parse their IDs as the subject model's primary-key type.

### 0.1.0

- Add record activity timelines backed by the configured Pupitre audit adapter.
- Add a read-only Activity resource with event, subject, and date-range filters, old/new detail, and authorized subject links.
- Require explicit authorization or AuditPolicy/ActivityPolicy for activity access.
- Security (WP42): add regression coverage for Activity tenant isolation and subject links requiring scoped records and list/detail authorization.
- M4 review: use the supported `activity` icon and clarify that custom audit adapters provide timelines, not Activity resource queries.


## pupitre-backup

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-backup/CHANGELOG.md)

### Changelog

### Unreleased

- Add server-only Backups resource with fail-closed authorization, tenant-scoped metadata, private streamed downloads, and file-removing deletion.
- Add consistent SQLite, PostgreSQL custom-format, and transactional MySQL snapshots, gzip-streamed to Drive with credential-safe errors and guaranteed ordinary-path temporary cleanup.
- Add durable cross-process concurrency exclusion, post-success count/age retention, optional jobs dispatch, and schedulable `backup:run` command.
- Document administrator-only access and manual restoration procedures; no web restoration endpoint is exposed.


## pupitre-calendar

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-calendar/CHANGELOG.md)

### Changelog

### Unreleased

- Add immutable calendar widgets and calendar page plugin, custom event loaders, tenant-scoped resource event sources, policy-aware navigation, and validated UTC rescheduling.
- Add lazy month/week/day/agenda UI in pupitre-ui with responsive agenda defaults, drag moves, accessible keyboard Move dialogs, and optimistic rollback.


## pupitre-health

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-health/CHANGELOG.md)

### @rikology/pupitre-health

### Unreleased

- Add a fail-closed AdonisJS health dashboard with process-local TTL caching, concurrent-run coalescing, forced refresh, summary statistics and custom-data check tables.
- Filter diagnostic metadata to known numeric measurements; publish a host-owned disk/memory/database check registry.


## pupitre-jobs

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-jobs/CHANGELOG.md)

### @rikology/pupitre-jobs

### Unreleased

- Monitor all AdonisJS Queue jobs through supported lifecycle tracing channels, preserving each execution attempt.
- Add tenant-scoped Jobs lists, status tabs, payload/error details and 24-hour stats.
- Encrypt original retry payloads, recursively redact credentials, and fail closed on authorization and retry eligibility.
- Add terminal-run retention pruning, an Ace command, migration publishing and a runnable host-job example.


## pupitre-kanban

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-kanban/CHANGELOG.md)

### Changelog

### Unreleased

- Add resource-scoped Kanban board pages, policy-aware card/create links, immutable business-rule gates, and transactional status/position moves.
- Add lazy sortable board UI with keyboard and pointer interaction, per-column loading, announcements, optimistic rollback, and bilingual copy.


## pupitre-media

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-media/CHANGELOG.md)

### Changelog

### Unreleased

- Add a fail-closed, tenant-aware Media resource with thumbnail grids, search/type/date filters, multi-file uploads, metadata editing, and original/conversion cleanup on delete.
- Add immutable mediaPicker/mediaEntry/mediaColumn catalog extension factories, ordered multi-selection, MIME/count/ownership validation, and private signed thumbnails.
- Reuse Pupitre's owned-upload pipeline and expose an authenticated media browse/adoption protocol for inline uploads.
- Add lazy optional Sharp conversions, a default 320px WebP thumbnail, and original-only fallback without Sharp.


## pupitre-react

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-react/CHANGELOG.md)

### Changelog

### Unreleased (1.1.0)

### Added
- Toasts: optional `<Pupitre toastDuration={5_000}>` auto-dismiss delay in milliseconds; default `0` retains manual dismissal, with per-toast deadlines preserved across navigation.
- Localization: client `t(key, params?)` resolves synchronously from the compact `pupitre.messages` shared prop and follows locale changes across Inertia visits.
- Catalog extensions: `defineRegistry(components, { extensions })` merges extension schemas, requires a component per extension, and reports unregistered or unknown spec types with an actionable developer error.
- Tables: `useTable` supports JSON QueryBuilder filter state and ordered multi-column sort, including explicit default clearing.
- Widgets: `useWidget(descriptor, initialData)` returns `{ data, loading, errors, reload(params?), runAction(name, data?) }` with stale-response suppression and `WidgetRequestError`; component widgets receive unwrapped loader data.
- Tables: `useTableViews(descriptor, onColumns)` lists, applies (prefix-preserving Inertia visits) and mutates saved views.
- Shell: optional `environment`, `impersonation` and cluster navigation props are forwarded through built-in and host-owned shells; export confirmations support XLSX downloads and format choice.

### Fixed
- Action modals: uploads use the server-issued `uploadOwner` instead of deriving resource context from the action name.

### Unreleased (1.0.0, milestone M4)

### Added
- Realtime (WP39): optional `Pupitre`/`PupitreShell` client context, SSR-safe `useRealtimeSubscription` with stable callbacks and cleanup, coalesced descriptor-scoped `useTableRealtime`, and notification push refresh with polling fallback.
- Security (WP42): preserve Request-input methods and headers in `jsonFetch`, ensuring XSRF headers on all mutating methods; add multipart/upload/deletion regressions.

### Changed
- Navigation guard (M4): `useNavigationGuard(shouldBlock)` no longer calls `window.confirm`. It returns `{ pending, proceed, cancel }` and holds the blocked Inertia visit, so the caller can render its own confirmation; `proceed()` re-issues the visit with its original options. Prefetch visits are no longer blocked. Tab close/reload still use the browser's native prompt.

### Fixed
- Options: abort superseded searches during debounce and discard late responses without changing options, paging, loading, or errors.

### Unreleased (0.3.0-beta, milestone M3)

### Added
- Widgets (WP27): added polling-driven partial reloads for widget props and widget grid rendering integration.
- Relations (WP28): `useTable` reads descriptor-selected top-level data props and namespaces table query keys while preserving list-page `data` behavior.
- Action extras (WP32c): modal deep-link URL synchronization and initial query-param opening, modal footer action execution with form data, and global shortcuts for non-row actions.
- Server evaluation (WP31): wired debounced `evaluate` actions to `/_pupitre/evaluate`, ignored stale responses, and applied only safe `/data/**` state updates.
- WizardBuilder (WP26a): action modals navigate wizard steps, submit on the final step, select the first errored step after a 422, and retain Next/Back navigation alongside footer actions when modal submit is disabled.
- Import/export (WP30): direct CSV downloads and jobs-backed export/import polling now flow through the action bridge, with progress/completion toasts and file delivery.

### Changed
- Confirm fixes (M3): share Cmd/Ctrl shortcut matching across page and UI actions, include field affix/hint shortcuts, and suppress export cancellation errors on navigation.
- Widgets (WP27): resolve and submit resource forms nested beneath widget layouts, without selecting disconnected forms.
- Widgets (WP27): widget tables can render outside the spec runtime while retaining `useTable` URL-prefix sorting, pagination, and prop-only reloads.
- Action extras (WP32c): import actions now stop before form submission and page-level shortcuts dispatch once; footer confirmations gate requests.
- WizardBuilder (WP26a): 422 errors select the first invalid step once per submit, without pinning navigation to stale errors.

### Unreleased (0.2.0-beta, milestone M2)

### Added

- `kind: 'auth'` pages render inside the registry `AuthShell`; `kind: 'custom'` pages and host pages can keep the panel shell through `PupitreShell`.
- `ShellProps` gains `tenant`, `tenants` and `notifications`; `ShellRegistry` gains `AuthShell`.
- Hooks: `useOptions` (debounced, abortable, paged dynamic select options), `useNotificationsPolling` (pauses while the tab is hidden); shared `jsonFetch`/`xsrfToken` bridge helpers.

### Unreleased (0.1.0-beta, milestone M1)

### Added

- `Pupitre` Inertia page, `PupitreRenderer` (store reset only on `page.key` change; reloads refresh `/errors`, `/meta/record`, `/options`), `SpecRenderer` for nested specs, `defineRegistry` (catalog + shell components, built-in `$computed` functions, `$raw` directive).
- Inertia bridge: form submission with 422 → `/errors/**`, action protocol (link/direct/confirm/form) over JSON `fetch` with `X-Pupitre-Action`, `X-Pupitre-Url`, XSRF header and descriptor HTTP method, `ActionResult` handling (flash, redirect, reload, patch, deselect), uploads, toasts surviving redirects, catalog-version mismatch reload guard.
- Hooks: `useBoundValue`, `useFieldError`, `useStateValue`, `useSetState`, `usePanel`, `usePage`, `useRunAction`, `useTable` (URL-driven table state with partial visits), `useSubmitForm`, `useEmbedded`, `useUpload`, `useToast`, `useVisit`, `useNavigationGuard`; test seams `setRouter`, `setFetch`.


## pupitre-settings

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-settings/CHANGELOG.md)

### @rikology/pupitre-settings

### Unreleased

- Add immutable typed settings groups, generated custom pages and optional page actions.
- Every writable form field must have a `.defaults()` entry; missing defaults fail at plugin registration.
- Persist JSON values with merged defaults, shared VineJS validation and atomic transactions.
- Add fail-closed group authorization, tenant isolation and request-scoped caching.
- Publish an idempotent SQLite/PostgreSQL migration through the configure hook.


## pupitre-shield

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-shield/CHANGELOG.md)

### Changelog

### Unreleased

- Localize role and permission management labels and user-facing errors.

### 0.1.0 — Milestone M4

- Add Lucid Role and Permission models, global role-management resource with resource-grouped permission checkboxes, and configurable host user relation/table integration.
- Add request/user-cached Bouncer policies, explicit-policy preservation, independent standard ability checks, custom action abilities, nested-resource discovery, and super-admin bypass.
- Add `shieldCan` page authorization and `shieldRolesField` User-resource relationship helper.
- Add configure migration publishing and lazy Ace command registration; `shield:sync` idempotently synchronizes resource permissions and can bootstrap a super administrator by ID/email.
- Add SQLite HTTP integration coverage and seeded example editor/super-admin integration.
- Security (WP42): forbid non-super-admin escalation through role names or permission delegation, protect the reserved role, and invalidate request-cached grants after edits; expose `invalidateShieldPermissions()` for host-managed pivot changes.
- Persist `shieldRolesField()` assignments and revocations transactionally with authenticated delegation checks, preserving host hooks and invalidating grants after pivot synchronization.
- Cover Shield-protected production queued workers, permission revocation, changed principals, cross-tenant records and revoked memberships.
- Thread active transactions through permission checks, isolate request caches from transaction snapshots, and authorize both permission additions and removals while preserving unchanged grants.
- Restrict Roles form inventories to enforced resources, discover relation-manager abilities on related resources, and isolate inventories when a plugin instance is reused across panels.
- Hide unauthorized role-assignment fields without blocking User forms or preloading forbidden options; preload existing role selections on edit.
- Reject deletion of roles containing any resource or host permission the actor lacks, while retaining super-admin bypass and deletion of fully delegated roles.
- Validate role-assignment IDs as positive PostgreSQL-range integers before querying Roles; normalize equivalent numeric IDs and cover create/update rejection with query-spy regressions.


## pupitre-transmit

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-transmit/CHANGELOG.md)

### Changelog

### Unreleased (milestone M4)

- Add `transmitRealtime()` with panel-guard, user identity, tenant membership, and resource policy channel authorization.
- Add SSR-safe `transmitRealtimeClient()` at `/client`, shared subscriptions, catalog payload validation, and Shield XSRF request headers.
- Security (WP42): add malformed-channel and guard-principal/tenant isolation regressions alongside subscribe/unsubscribe XSRF coverage.
- M4 review: reuse core channel patterns and panel guard helper; accept only namespaced `pupitre:notification` and `pupitre:table` wire pings.


## pupitre-ui

[Package source](https://github.com/rikoriswandha/pupitre/blob/main/packages/pupitre-ui/CHANGELOG.md)

### Changelog

### Unreleased (1.1.0)

### Added
- Localization: accessible, tenant-aware language switcher in the user menu and profile; built-in dialogs, forms, editors, uploads, widgets and accessibility text are translated.
- Tables: QueryBuilder editor (typed values, negation, AND/OR groups, active summaries) in every filter layout, and keyboard-accessible multi-column sort controls with priority announcements.
- Catalog extensions: DataTable and nested infolist entries render registered custom columns/entries with formatted values and row/record context.
- Shell: environment badge and top strip across sidebar/collapsed/top-nav layouts, impersonation banner with CSRF-protected leave, responsive cluster sub-navigation.
- Tables: Views toolbar (save/update/rename/default/public/delete, saved column visibility), simple pagination and header actions for custom-data tables, CSV/Excel format picker for exports.
- Forms: accessible table repeater (numbered row controls, drag and keyboard reorder, per-cell errors, horizontal scroll at narrow widths); rich editor merge-tag and mention suggestions and custom-block preview cards with isolated edit modals.
- Widgets: radar and scatter charts in the lazy chart chunk with screen-reader data tables.
- Profile: Browser sessions and Recent logins sections.
- Plugin surfaces (lazy): `Calendar` (month/week/day/agenda, drag and keyboard rescheduling with rollback), `KanbanBoard` (dnd-kit sortable cards, keyboard moves, per-column loading, rollback), `MediaPicker`/`MediaEntry`/`MediaColumn`.

### Fixed
- Dark theme primary (`oklch(0.545 0.1 163)`) raises primary-button text contrast above WCAG AA (4.53:1).
- Table grid cards: the selection checkbox sits beside the card title instead of being pushed to the opposite edge, and long field values wrap inside the card instead of overflowing it.

### Unreleased (1.0.0, milestone M4)

### Changed
- Redesign (M4): new default "writing desk" skin. Warm OKLCH neutrals (canvas, sidebar, card layers), blotter-green default primary, semantic `success`/`warning`/`danger`/`info` tokens with derived `-soft`/`-ink` variants (mixed in oklab so a `theme.primary` override re-skins every state), `--input-strong` for checkbox/radio/switch boundaries, chart palette, elevation and density (`--control-h`, `--row-py`) tokens. See the root `DESIGN.md`.
- Primitives (M4): `Button` now takes `variant` (`solid`/`outline`/`ghost`/`link`) × `color` (`primary`/`gray`/`success`/`warning`/`danger`/`info`) × `size` (`xs`–`lg`) plus `square` and `loading`; the old `default`/`secondary`/`outline`/`destructive` variants are removed. New exports: `buttonClasses`, `controlClasses`, `Textarea`, `InputGroup`/`InputAddon`/`InputSlot`, `FieldDescription`, `RequiredMark`, `CheckboxControl`, `SwitchControl`, `Avatar`, `Kbd`, `Spinner`, `Skeleton`, `Tooltip`, `Panel`/`PanelHeader`, `EmptyState`, `toTone`, and popup/dialog/tab class helpers.
- Shell (M4): sticky topbar (search field, notifications, avatar menu), breadcrumbs above the page title, raised active navigation items, icon-rail tooltips, tenant switcher card, mobile drawer, and header actions laid out by the new `ActionList` (leading action promoted to primary, overflow in a "More actions" menu).
- Components (M4): every catalog component restyled: tables in a single panel with toolbar search, filter/column popovers, bulk-action menu and row actions collapsed after two; form sections, tabs and wizards as panels (nested panels flatten through `PanelNestingContext`); selects and pickers open popups; infolists, widgets, modals, toasts, auth and profile screens follow the same system in light and dark.
- Testing (M4): `AdminPage.openAction(name, row)` opens a row's "More actions" menu when the action is not inline.
- Motion (M4): shared easing tokens (`ease-ui`, `ease-move`, `ease-drawer`) replace ad hoc curves. The desktop sidebar rail toggle morphs via the View Transitions API (sidebar crossfades and resizes, content slides; instant under reduced motion or without browser support). Section, Repeater item, Builder block and sidebar nav group bodies open and close with a height and fade transition through the new `Collapse` primitive, with rotating chevrons. Toasts grow in from the bottom edge and close their slot on dismissal instead of disappearing. Top-navigation group dropdowns and the bulk-selection toolbar fade in, and buttons give subtle press feedback.

### Added
- Registry (WP40): deterministic shadcn registry generated from UI sources, versioned npm/import-graph dependencies, preserved relative targets, public `/r/*.json` payloads and index, CI drift check, and copy-ability tests.
- Audit (WP38): `Widget.Timeline` catalog renderer and exported `WidgetTimeline` display server-formatted activity times, actors, icons/colors, structured field changes, and empty states; `WidgetGrid` supports timeline spans and deferred data.
- Realtime (WP39): DataTable and NotificationTray replace polling with configured channel subscriptions, retain polling fallback, and clean up subscriptions and pending reloads on unmount.
- Realtime inbox (WP39): open notification trays refresh items on user-channel pings through the existing single subscription; superseded requests are aborted and late responses cannot overwrite the current inbox.
- Accessibility (WP42): strict WCAG 2.1 A/AA axe coverage across registry fixtures, shell/account components, dialogs and interactive popup/editor/table states, plus an example auth/CRUD/settings/security audit.

### Fixed
- DataTable: preserve versioned pending and settled inline edits across older in-flight refreshes (including rollback to a previously saved edit), release saved overrides when the server confirms them, a refresh issued after settlement lands, or host-supplied props change; ignore visits cancelled before they start, revert failures to the latest authoritative value, and memoize column definitions and descriptor lookup.
- Accessibility: table selection and inline toggle targets are at least 24px without enlarging checkbox visuals, switch tracks, or text-only rows in compact and comfortable density.
- Class merging (M4): `cn()` now resolves conflicting utilities with `tailwind-merge`, so a component's `className` reliably overrides primitive defaults instead of depending on CSS order. Template-literal class concatenations with conflicts now go through `cn()`.
- Selects (M4): table filters, grouping, rows-per-page, editable cells, widget filters and import mapping use the new `SelectMenu` primitive, the same Base UI trigger and listbox as form `Select`, instead of native `<select>` elements. Their heights follow density (`--control-h`, `--control-h-sm`), and auto-width triggers size to their widest option like a native select, so a column of editable cells lines up. `.pupitre-native-select` remains for `Select native`, drawing the `chevrons-up-down` icon instead of the UA arrow.
- Color (M4): the swatch fills its slot instead of collapsing to a sliver; presets are 28px square buttons.
- Switch (M4): the thumb keeps an even 2px inset in both states instead of overrunning the track when checked, and stays light in dark mode (new `--switch-thumb` token) instead of reading as a hole in the track.
- Tags and multi-select (M4): chips and the draft input are centered and compact, so a single row matches the control height.
- Checkbox layout: keep compact 16px checkbox visuals within 24px form hit targets, align option labels, and separate CheckboxList legends from options. Label clicks, target-edge clicks, and keyboard toggling remain supported.
- ConfirmDialog: narrower (`sm`) dialog with the heading centred on the tone icon and full-size description text.
- Confirmations (M4): the Form `dirtyGuard` prompt ("Discard unsaved changes?", Stay / Leave page) now renders the registry `ConfirmDialog`, the same dialog as action confirmations such as Delete, instead of `window.confirm`; passkey deletion in `ProfileSecurity` uses `ConfirmDialog` too. Closing or reloading the tab still shows the browser's native prompt.
- DataTable (M4): an absent data prop no longer re-renders forever (stable empty rows, manual pagination).
- Wizard (M4): advancing to the last step no longer submits the form when Next becomes the submit button.
- Repeater/Builder (M4): render their field label, hint, helper text and required marker.
- Host styling (M4): namespace the example's card/focus tokens so public-site defaults cannot override Pupitre light/dark surfaces and keyboard focus.
- Host styling (M4): `styles.css` points every border at `--border` in the base layer; bare `border` utilities no longer paint Tailwind v4's currentColor (near-black) on cards, tables, chart panels, selects and buttons. Stat cards size to their widget column instead of the viewport, and the table page indicator no longer wraps.
- Navigation (M4): sidebar, brand, breadcrumb, record sub-navigation and table record links navigate through the Inertia router (new `NavLink` primitive) instead of reloading the document; modified clicks, new-tab targets, downloads, fragments and cross-origin URLs keep native behavior.
- Accessibility (M4): move the panel toolbar banner outside the main landmark and cover shell landmark best practices.
- Realtime inbox (M4): retain existing notification items and keyboard focus during background ping refreshes.
- Accessibility (WP42): use an opaque auth canvas and strengthen light muted-text contrast so authentication links and muted-surface text meet AA in the built host application.
- Accessibility (WP42): isolate CheckboxList option field scopes and labels; hide duplicate native inputs correctly, preserve required/error descriptions, and provide 24px checkbox targets with visible keyboard focus.
- Accessibility (WP42): fall back to field names for unlabelled Text controls, repair standalone/nested infolist description-list semantics, and remove invalid ARIA from decorative hint/color content.
- Accessibility (WP42): trap and restore mobile navigation/modal/search focus, dismiss menus with Escape, expose skip/landmark structure, uniquely label repeated profile controls and toast dismissal actions, and announce empty search states without invalid empty listbox roles.
- Accessibility (WP42): add roving ToggleGroup focus, separately named Radio options, sortable/mixed-selection table semantics, labelled upload/editor controls, editor loading status, chart data alternatives, valid icon/toggle table cell roles, and AA destructive-text contrast on hover.

### Unreleased (0.3.0-beta, milestone M3)

### Added
- Widgets (WP27): added stats with sparklines, lazy chart rendering, table widgets, custom registry dispatch, deferred placeholders, and polling refresh.
- Testing (WP34): added the optional `@rikology/pupitre-ui/testing` Playwright fixture and reusable `AdminPage`; example E2E specs now import the package fixture.
- Relations (WP28): added the catalog-backed RelationManager component with relation label/badge, readOnly action suppression, and shared DataTable rendering.
- AuthShell (WP32b): shows configured social provider sign-in links on the panel login page.
- Theme (WP33): added top-navigation dropdown groups, persisted collapsed icon rail, responsive content-width shells, density scaling, and per-panel font/stylesheet links.
- Table interaction (WP29b): added optimistic editable cells with rollback/toasts, keyboard and drag reorder controls, deferred filter Apply, polling-aware reloads, SSR-safe persisted column visibility, and responsive grid cards.
- Editors (WP26b): added lazy Markdown writing/sanitized preview and attachment upload, lazy CodeMirror with on-demand language extensions, and client-side crop/rotate/flip for image uploads.
- Action extras (WP32c): notification tray and flash toast action controls, including click-to-mark-read actions.
- Timezone (WP35): date/time controls display field-zone wall time without browser-local parsing, submit local datetimes for server validation, and show a zone hint when it differs from the browser.
- WizardBuilder (WP26a): added typed block editing controls and mounted wizard steps with accessible navigation and 422 error-step recovery.
- SchemaParity (M1–M3): added raw-value text masks, textarea autosizing, accessible scalar/range sliders, hint tooltips/actions, and prefix/suffix action controls.
- Import/export (WP30): added a CSV import dialog with preview, header-to-form mapping, async-job progress, summary counts, and downloadable failed-row CSV.

### Changed
- Testing (WP34): `pupitreTest` takes a `pupitre: { panelPath, basePath?, landingPath?, email?, password? }` fixture option (set it in `playwright.config.ts` `use`); `AdminPage` no longer assumes the example app's tenant, credentials or seed data.
- Confirm fixes (M3): inline blur saves no longer steal focus, refreshed rows clear optimistic cells, and page wizards jump once per validation response without blocking subsequent navigation.
- Table interaction (WP29b): distinguish filter labels from row-specific inline controls for accessible, unambiguous navigation.
- Table data (WP29a): expose the overall table summary as a named region, distinct from group summaries.
- Testing (WP34): scope page field helpers to forms so inline table edit buttons cannot be mistaken for loading form fields.
- Widgets (WP27): `Widget.Table` now delegates to the shared `DataTable` instead of a separate compact renderer.
- Widgets (WP27): render stat colors/icons, support widget spans through all four grid columns, and use the documented widget GET endpoint for polling and filter refresh.
- Widgets (WP27): preserve polled values across parent wrapper rerenders, reconcile only changed widget props, and resolve semantic stat colors through theme classes while retaining raw CSS colors.
- Editors (WP26b): text masks consume literal mask positions correctly, Markdown fields link labels and validation descriptions, and CodeMirror syncs external values with readOnly/disabled semantics.
- Editors (WP26b): reconfigure CodeMirror access state in place so disabled, loading, and read-only toggles retain the view, selection, focus, and undo history.
- Theme (WP33): density uses scoped minimum sizes that preserve checkbox/radio and icon controls; mobile sidebars regain an accessible navigation toggle.
- Table interaction (WP29b): editable cells reset drafts and restore focus, preserve component identity through optimistic updates, and page-window reordering preserves global offsets.
- WizardBuilder (WP26a): render step icons/descriptions, keep Next mounted, and use the valid `aria-current=step` variant.
- Import/export (WP30): import dialogs use Base UI modal focus handling, maintain popup/backdrop stacking, and announce progress/results; image crop dialogs use theme tokens.
- Import/export (WP30): queued imports can be dismissed with Cancel or Escape, abort client polling, and toast that the import continues in the background.
- Widgets (WP27): charts include series legends and distinct default colors.

### Unreleased (0.2.0-beta, milestone M2)

### Added

- `AuthShell`, `TenantSwitcher`, `NotificationTray`, `GlobalSearch` (cmdk, configurable key binding), `UserMenu` (custom items, profile, logout) and `ShellSidebar` (collapsible groups persisted per panel, badges, dark logo); `Shell` composes them.
- `Select` async mode: remote search with paging, selected-label hydration, create-option modal, prefix/suffix actions.
- Panel theme variables (`--primary`, `--radius`, custom vars) are applied to the document root so portaled overlays inherit them.

### Unreleased (0.1.0-beta, milestone M1)

### Added

- Default implementations of every M1 catalog component on Base UI + Tailwind v4: inputs (text, textarea, number, select, checkbox(list), radio, toggle(s), date/time, color, tags, key-value, file upload, lazily loaded Tiptap rich editor), layouts (form, grid, section, fieldset, tabs, group, split, placeholder, html, actions), domain components (`DataTable` with URL-driven sort/search/filters/tabs/pagination, row/bulk actions; `Repeater`), infolist entries and the shell (`Shell`, `Toaster`, `Modal`, `ConfirmDialog`, `NotFound`).
- `live({ onBlur, debounce })` support in text-like inputs, theme CSS variables and dark mode on the document root, favicon, action key bindings for header and bulk actions.
- `styles.css` for hosts (import after Tailwind), `components` registry map, Playwright component tests for every catalog fixture.

Source: https://pupitre.rikology.com/changelog/index.mdx
