Record components
Import MoltaroRecordHeader, MoltaroRecordHistory and
MoltaroRecordLifecycleDialog from @moltaro/ui. Pass the identity of one
saved Entity Instance. Moltaro loads the record, evaluates server permissions,
renders its history and performs the requested lifecycle operation.
These exports require a host build that includes them. After installing that
build, update your page, run Check → Build → Activate, then fully reload
the WebApp. HostContractVersion stays 1; the declaration and host
fingerprint change. Existing saved projects are not rewritten automatically.
Choose a component
Section titled “Choose a component”| Component | Content and behavior |
|---|---|
MoltaroRecordHeader | Display name, record number, subtitle, archive state and server-authorized lifecycle menu; includes the reason dialog |
MoltaroRecordHistory | Paged events, selected event details, field and table changes, assignments, manual-order changes and business events |
MoltaroRecordLifecycleDialog | Standalone archive, restore or permanent-delete dialog for a page-owned action button |
The wrappers reuse native Entity UI. Do not import private WebApp components, stores, DTOs, registries or AG Grid to recreate these flows. The first version handles one saved record; it does not provide bulk operations, record editing, or new card/form extension points.
Identity and props
Section titled “Identity and props”import type { WorkspaceUiEntityRecordContext } from '@moltaro/ui'
const context: WorkspaceUiEntityRecordContext = { entityIdOrKey: 'Contact', instanceId: savedRecordId,}The context contains only entityIdOrKey and instanceId. Never supply your
own permission flags or a cached internal record model. Use a real saved ID;
these components do not accept a new-record draft.
All three components require context and attach. Pass
usePageContext().overlayTarget to attach; it is forwarded to menus,
reason dialogs, table-change dialogs and nested address viewers. A string or
number refreshKey explicitly reloads current data when changed.
Header and lifecycle dialog accept disabled and readOnly as additional
client restrictions. Neither grants server permission. The dialog also takes
v-model for opening and action: 'archive' | 'restore' | 'delete'.
Permanent delete is offered only when the server permits it, for an archived
record. The header hides unavailable actions using the native policy; a
standalone dialog explains a denied action when a safe server explanation exists.
Header slots are breadcrumbs, status-chips and actions. They contain
page-authored UI and do not expose an internal record object. Author-owned
action buttons must enforce their own applicable server contract.
Events and parent state
Section titled “Events and parent state”All components emit state-change with { status, error }. The possible
statuses are idle, loading, ready, submitting, checking, unknown,
error and unavailable; error is a display message, empty when absent. A
header reports the active lifecycle dialog’s state while that dialog is open.
Header and lifecycle dialog emit result only after a confirmed success:
interface WorkspaceUiRecordMutationResult { action: 'archive' | 'restore' | 'delete' entityDefinitionId: string instanceId: string operationKey: string rowVersion: string | null readableAfterMutation: boolean}The result identifies the captured record and returns no field data. A new
version may be unavailable. When readableAfterMutation is false, clear your
parent’s record display; do not treat a failed readback as a failed mutation
or repeat the write. The components never choose your page’s route.
The dialog emits result before update:modelValue(false). Closing without
submitting emits cancel once, followed by closing. Closing after an unknown
outcome does not report that the operation was cancelled. Changing context or
unmounting invalidates obsolete UI callbacks, not a write already sent to the
server.
Header and lifecycle dialog emit confirmed when a commit is confirmed without
the command’s own answer: by Check result after a lost answer, or when a
repeat answers that the same request was already saved. The dialog keeps the
confirmation visible until the person closes it.
interface WorkspaceUiRecordMutationConfirmation { action: 'archive' | 'restore' | 'delete' entityDefinitionId: string instanceId: string operationKey: string}It carries no version and no readability. Read the record again (or clear your
display after a delete) instead of treating it as a result.
The native mutation event refreshes other subscribed record components. Do
not also increment their refreshKey for the same event: that would request
a second refresh. Use result to update parent-owned state or navigation.
Use refreshKey for a separate external change that has no native notification.
Reason, conflicts and an unknown outcome
Section titled “Reason, conflicts and an unknown outcome”Every single-record archive, restore and delete asks the user to enter a reason, including on native screens. It must contain 1–1000 characters after trimming surrounding spaces. Validation errors appear at the reason field. The component loads the current record version and server decisions; the server remains authoritative when the command runs.
The current archive and restore commands also require post-operation visibility inside their transaction. If that check fails (for example, no permission to read the resulting archived record), the server rejects and rolls back the command. The components preserve this rule. A confirmed receipt, such as the result of permanent deletion, is success even without readable record data; a later loss of access must not be interpreted as permission to repeat a write.
The component freezes Reason, RowVersion, identity, action and OperationKey before sending. Double submission is blocked. The dialog uses the same host mechanism as Safe Entity writes. If the connection fails after send, the outcome is unknown: the Reason stays frozen and the dialog offers Check result (one read-only confirmation request), Retry the same request when the installation’s error catalog, the replay window and the send limit allow it, and Forget request with a warning that it does not undo anything. Do not create a new key or infer rollback from a network error.
If an exact retry is later refused, the first outcome remains unknown and the
retry is no longer offered. A 401, 403 or stale version is not evidence that
an earlier request with a lost response rolled back. A check that finds no
confirmation also leaves the outcome unknown.
Closing and reopening the same mounted dialog for the same record and action
continues that pending operation. The dialog also keeps a minimal handle in the
browser tab: after a full reload, opening the same dialog checks it once and
never sends the request again, because its content is gone; it can be confirmed
or forgotten, not repeated. MoltaroRecordHeader shows an alert for such an
operation of its record; Review opens the dialog. A refusal of a request
with no earlier unknown outcome reloads the record and requires a new explicit
confirmation. A read error has a separate retry that preserves the typed reason.
History boundaries
Section titled “History boundaries”History loads 25 events initially, with native choices of 10, 25 and 50 per page. It never downloads the full journal. Server permissions and field-safe projection determine the visible data. Identity, user and page changes abort old reads; a loss of access clears previously displayed history.
An empty page means no visible events were returned. It does not establish that
the record never changed. Every event shows its origin from the server’s
Source: a person’s save or merge, or the function, version, run and initiating
user of an automated change, and the before-save functions that derived saved
values. An event without Source is shown as Not recorded; it
does not identify a manual action or a particular C# function. The component
shows run identifiers but no run link, because reading history does not grant
access to run diagnostics. Archive, restore and delete events show their stored
reason. They show No reason was provided for operations that store none,
such as demo-data cleanup, and Reason not saved for events recorded before
reasons were stored. A record changed by a cascade shows the reason of the
originating operation, or states that it is hidden when the user cannot read
that record’s history or the record no longer exists. These components do not
backfill audit events or repair missing provenance from an import.
Page composition
Section titled “Page composition”Use the reference picker to choose an existing record, then pass its identity to the header and history. The Generic picker proves readability at selection time; subsequent reads and operations still enforce their own permissions.
<script setup lang="ts"> import type { WorkspaceUiEntityRecordContext, WorkspaceUiRecordMutationResult, WorkspaceUiReferenceSelection } from '@moltaro/ui' import { MoltaroRecordHeader, MoltaroRecordHistory, MoltaroReferencePickerDialog } from '@moltaro/ui' import { usePageContext } from '@moltaro/workspace-ui' import { ref } from 'vue' import { useI18n } from 'vue-i18n' import { VBtn } from 'vuetify/components'
const { overlayTarget } = usePageContext() const { t } = useI18n() const pickerOpen = ref(false) const context = ref<WorkspaceUiEntityRecordContext | null>(null) function select (value: WorkspaceUiReferenceSelection) { context.value = { entityIdOrKey: value.entityId, instanceId: value.id } } function completed (value: WorkspaceUiRecordMutationResult) { if (!value.readableAfterMutation || value.action === 'delete') context.value = null }</script>
<template> <section> <VBtn @click="pickerOpen = true">{{ t('records.choose') }}</VBtn> <MoltaroReferencePickerDialog v-model="pickerOpen" :attach="overlayTarget" :context="{ kind: 'Generic', targetEntityId: 'Contact', purpose: 'record-workbench' }" :selected-id="context?.instanceId" @select="select" /> <template v-if="context"> <MoltaroRecordHeader :attach="overlayTarget" :context="context" @result="completed" /> <MoltaroRecordHistory :attach="overlayTarget" :context="context" /> </template> </section></template>Replace Contact with a configured entity and add records.choose to each
of your project’s locale files. Use the header menu to archive or restore;
no second lifecycle dialog is needed for those menu actions.