feat(quick-261016-nqk): admin heads form and preview with meta.title (G-18.1-4)

This commit is contained in:
Jakub Zych
2026-10-16 17:19:54 +02:00
parent bc5685eb93
commit 013890c3d6
4 changed files with 37 additions and 6 deletions

View File

@@ -78,6 +78,8 @@ const recordId = mode === 'update' ? Number(route.params.id) : null
const schema = ref<FormView | null>(null)
const values = ref<AdminRecord>({})
const labels = ref<RecordMeta['labels']>({})
/** The controller's record title (meta.title, pact.FormRecordTitle); '' when it names none. */
const recordTitle = ref('')
const errors = ref<Record<string, string[]>>({})
// The text of a save the server refused with 403 (UI-SPEC S6). It is a
// banner, not a toast: it stays readable until the next save attempt.
@@ -157,9 +159,11 @@ const panelFields = computed(() => {
return fields.value.filter((field) => tabOf(field) === key)
})
/** The record's display name: the first text-like field's value, in schema order. */
const recordName = computed(() =>
recordDisplayName(schema.value?.fields, values.value, schemaEnabledLocales(schema.value?.meta)),
/** The record's display name: the controller's title, else the first text-like field's value, in schema order. */
const recordName = computed(
() =>
recordTitle.value ||
recordDisplayName(schema.value?.fields, values.value, schemaEnabledLocales(schema.value?.meta)),
)
const title = computed(() => {
@@ -217,6 +221,7 @@ function adopt(record: RecordEnvelope | undefined): void {
}
values.value = next
labels.value = record.meta.labels ?? {}
recordTitle.value = record.meta.title ?? ''
}
saved.value = snapshot(editablePayload(fields.value, values.value, mode))
}

View File

@@ -54,6 +54,8 @@ const recordPath = `${listPath}/${recordId}`
const schema = ref<FormView | null>(null)
const values = ref<AdminRecord>({})
const labels = ref<RecordMeta['labels']>({})
/** The controller's record title (meta.title, pact.FormRecordTitle); '' when it names none. */
const recordTitle = ref('')
const actions = ref<RecordAction[]>([])
const loading = ref(true)
const failed = ref(false)
@@ -112,9 +114,11 @@ const panelFields = computed(() => {
return fields.value.filter((field) => tabOf(field) === key)
})
/** The record's display name: the first text-like field's value, in schema order. */
const recordName = computed(() =>
recordDisplayName(schema.value?.fields, values.value, schemaEnabledLocales(schema.value?.meta)),
/** The record's display name: the controller's title, else the first text-like field's value, in schema order. */
const recordName = computed(
() =>
recordTitle.value ||
recordDisplayName(schema.value?.fields, values.value, schemaEnabledLocales(schema.value?.meta)),
)
const subtitle = computed(() => message(schema.value?.messages.preview, undefined, { name: recordName.value }))
@@ -146,6 +150,7 @@ async function loadRecord(): Promise<boolean> {
}
values.value = { ...result.data.data }
labels.value = result.data.meta.labels ?? {}
recordTitle.value = result.data.meta.title ?? ''
actions.value = result.data.meta.actions ?? []
return true
} catch {

View File

@@ -623,6 +623,14 @@ describe('password and preset on the form (UI-SPEC S7, D-19, D-27 G7)', () => {
expect(editor(wrapper, 'slug').element.value).toBe('ada-lovelace')
})
it("titles a record by the controller's meta.title when it sends one, and publishes it as the crumb", async () => {
const record = clone(rosterRecordFixture)
;(record.meta as { title?: string }).title = 'Person #1 (Ada)'
const { wrapper } = await mountApp('/acme/roster/people/1', people({ [`GET ${PERSON}`]: { body: record } }))
expect(wrapper.find('h1').text()).toBe('Person #1 (Ada)')
expect(recordCrumb.value).toBe('Person #1 (Ada)')
})
it('titles a record by the default locale of an mltext first field, and publishes it as the crumb', async () => {
const record = clone(rosterRecordFixture)
record.data.name = { en: 'Ada Lovelace', pl: 'Ada Lovelace PL' }

View File

@@ -139,6 +139,19 @@ describe('preview view (UI-SPEC S3, D-11)', () => {
expect(recordCrumb.value).toBe('Ada Lovelace')
})
it("heads the record by the controller's meta.title when it sends one, before the first text field", async () => {
const { wrapper } = await mountApp(
PREVIEW,
routes({
[`GET ${RECORD}`]: recordWith((record) => {
;(record.meta as { title?: string }).title = 'Person #1 (Ada)'
}),
}),
)
expect(wrapper.find('h1').text()).toBe('Person #1 (Ada)')
expect(recordCrumb.value).toBe('Person #1 (Ada)')
})
it('shows the muted dash for an empty value and the label of a relation, never its id', async () => {
const { wrapper } = await mountApp(PREVIEW, routes({ [`GET ${RECORD}`]: recordWith((record) => (record.data.joined_ip = null)) }))
expect(wrapper.find('[data-preview-field="joined_ip"] [data-empty]').text()).toBe('—')