Skip to content

Commit d748ae8

Browse files
fix(spec): a page label does not fill the untitled header of a record page (#22598)
Fixes #22536 Clause-②: no On a record page, a `page:header` with no `title` is the sanctioned spelling for "the renderer derives the heading from the record" (`PageHeaderProps.title`, the maintainer ruling recorded in `component.zod.ts`). Studio's record-page seed writes exactly that header. `translatePage` filled the absent title with the pack's `pages.NAME.label`, so the record's name was replaced by the page's static label. This happened in every locale whose pack carries the label, including the `en` skeleton that `os i18n extract` writes. Nothing warned. ## The rule (triage ruling A, comment 6092658777) `resolvePageHeaderTitle` (`packages/spec/src/system/i18n-resolver.ts`) no longer lets the bundle label fill an untitled root `page:header` when all three of these hold (`headerDerivesFromRecord`): - the page's `type === 'record'`, read as served: a document with no `type` is not a record page; - the page's `object` is a non-empty string; - the header's `properties.recordChrome !== false`. Everywhere else the label still fills an absent title, as before. That includes the existing `#22508` control fixture, a page with no `type` and no `object`, whose pin name now says "non-record page". An authored title, including one that restates the page `label`, follows the existing order unchanged. The docblock's order list carries the new condition in its step 3. **Where the predicate comes from.** objectui is read at the `.objectui-sha` pin `47b1f0bb71`: - `PageHeaderRenderer` draws the record-derived heading only when a record context is mounted and the header keeps its record chrome (`containers.tsx:2331`-`:2332`, `:1573`-`:1574`). - Two hosts mount that context: the record route, for the page `usePageAssignment` picks (`usePageAssignment.ts:139`-`:140`: `type === 'record'` and `object` naming the route's object), and the Studio preview (`PagePreview.tsx:186`-`:187`, `:311`). - The one host this rule cannot see is a `type: 'modal'` action that renders its target page inside the invoking record's context (`useActionModal.tsx`, `RecordDetailView.tsx:3064`). Such a page keeps today's fill. Triage accepted this gap, and its in-repo population is 0. Docs: `content/docs/ui/translations.mdx` states the exception beside the label-fallback sentence. ## Measured at the door These are one-shot runs and were never committed. `GET /api/v1/meta/:type/:name` goes through `RestServer`, using the `meta-plural-i18n.test.ts` harness shape, on both the cached and uncached branches. The page is the Studio-seed shape: `account_record`, `type: 'record'`, `object: 'account'`, and a root `page:header` with properties `{recordChrome: true}`. | pack, locale | before (`main` d6c3791) | after (cdfc9ef) | | --- | --- | --- | | zh-CN pack with only `pages.account_record.label` (客户记录页), zh-CN | header gains `title: 客户记录页` | header `{recordChrome: true}`, no title | | `en` extract skeleton (`label: Account Record`), en | header gains `title: Account Record` | header `{recordChrome: true}`, no title | | no `en` pack, en | untouched | untouched | | control: `type: 'app'` page, untitled header with `recordChrome: false`, zh-CN | gains `title: 落地页` | gains `title: 落地页` (unchanged) | The resolver, its tests, `component.zod.ts` and `page.zod.ts` are byte-unchanged between d6c3791 and this branch's merge base. ## Pins (`packages/spec/src/system/i18n-resolver.test.ts`) Each pin runs at a region root and at a slot root. - The Studio-seed record header stays untitled with the zh-CN label-only pack and with the `en` extract skeleton, and the page's own `label` still translates. - A bundle `subtitle` still applies beside the untitled header. - The pin also goes through `translateMetadataDocument`, the function the REST page doors call. - One control per condition, where the label DOES fill: `type` absent, `object` absent or `''`, and `recordChrome: false`. A further control covers an object-bound page of another `type` (`list`, `app`, `home`, `utility`). - An authored title on a record page follows the existing order: a template is kept, a title that restates the label takes the label translation, and a pack `title` replaces it. - The existing `#22508` control is kept as written: a header with no title on a non-record page shows the bundle `label`. **Ablation.** This ran at cdfc9ef with the fix committed, using `node scripts/ablation-replace.mjs`. It turned `if (own === undefined) return derivesFromRecord ? undefined : bundleLabel;` into `if (own === undefined) return bundleLabel;`. The anchor went from 1 to 0 hits, and the blob moved from c0682db to 69817db5e283. Result: `Tests 9 failed | 349 passed (358)`. The 9 failures are exactly the record-page pins (4 per root plus the `translateMetadataDocument` pin), and every control stayed green. The file was restored with blob c0682db equal to HEAD, and `git diff HEAD` is empty. ## Verification All runs went through `scripts/pm/os-verify-lock.sh` with slot `issue-22536`. Each result is the `VERDICT command-exit` line of its run. - `@objectstack/spec`: - `i18n-resolver.test.ts` plus `translation.test.ts` at d841d26 (after the `main` merge): 505 passed. - `pnpm test` at cdfc9ef: 638 files, 19037 passed, 1 todo. - `pnpm typecheck` at cdfc9ef: green. The test-typecheck ledger held at 52 files, 246 errors and 135 signatures. - `build && check:generated` at d841d26: all 15 generated artifacts up to date. - The only runtime caller of the translators is `@objectstack/rest` (`meta-item-read-gate.ts`), via `translateMetadataDocument`. Its consumer runs, all at cdfc9ef: - `pnpm test`: 269 files, 4979 passed, 326 skipped. - `pnpm typecheck`: green, with 0 test-layer errors. - `pnpm test:repo`: 191 passed, 1 skipped. - `@objectstack/cli` unit tier, `test/platform-page-i18n-parity.test.ts` and `test/i18n-extract.test.ts`: 51 passed. These call `translatePage` on the shipped platform pages. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at d841d26 derived 107 commands. 106 were run and all exited 0. `check:skill-examples` first exited 3 (PREREQUISITE NOT MET: `client-react` was unbuilt) and exited 0 after `turbo run build --filter='@objectstack/client-react...'`. `pnpm check:dual-build-cjs-loads`: NOT MEASURED, because the dispatch rules out a whole-workspace build. `--ran` reconciliation: 107 derived, 106 run, 1 unrun (that one). Artifact rosters block: 48 of 51 run, all exit 0. The 3 PR-context guards (`check-closing-target-claim`, `check-partof-closing-keyword`, `check-single-claim-paths`) are run against this PR after it opens, and their results are reported on the card. ## Acceptance notes - **Seam: `PageSchema`'s parse default against `type` read as served.** This is reported, not fixed here. `PageSchema` defaults an absent `type` to `'record'` (`page.zod.ts:1177`). - Stored path: closed. A page saved without `type` is stored with `type: 'record'`, and a stored row without it is served with it (`withDeclaredPageTypeDefault`). Its 9 existing pins pass here. - Registry path: open. Measured through the door, a page registered in the `SchemaRegistry` without `type` and without a parse is served with no `type`. In zh-CN its untitled header gains the label (`title: 打包无类型页`). The same page as a stored row is served `type: "record"`, and its header stays untitled. - No real producer was found that registers a typeless page without parsing. `defineStack` applies the default, and the plugin pages in this tree declare `type`. Reach is therefore not established. - **`recordChrome` spelling.** Only `properties.recordChrome` is read. Measured: `PageSchema` refuses a bare top-level `recordChrome` on a component (`unrecognized_keys` at `regions.0.components.0`), so a valid served document never carries it. Reading that spelling would be consumer tolerance for a key the contract refuses. - **Unchanged on purpose.** A pack's `pages.NAME.title` and `subtitle` still apply to an untitled record-page header (step 1). Triage ruled on the label alone. - **Population.** `showcase_task_detail` is the one in-repo record page with an untitled root header. No in-repo pack carries its label today, so nothing in-repo renders differently. The plugin-approvals record page that landed on `main` during this work is `kind: 'slotted'` with no header slot, so the server sees no root header on it. --- _Generated by [Claude Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 76bc1e0 commit d748ae8

4 files changed

Lines changed: 214 additions & 5 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
A locale pack's page `label` no longer replaces the record-derived heading on a record page whose header authors no title
6+
7+
Clause-②: no
8+
9+
On a record page, a `page:header` with no `title` is the sanctioned spelling for "the renderer derives the heading from the record" (`PageHeaderProps.title`). Studio's record-page seed writes exactly that header. `translatePage` used to fill the absent title with the pack's `pages.<name>.label`, so the record's name was replaced by the page's static label. This happened in every locale whose pack carries the label, including the `en` skeleton that `os i18n extract` writes. Measured at `GET /api/v1/meta/page/:name` before this change: the header gained `title: "客户记录页"` in zh-CN, and `title: "Account Record"` in en with the extracted skeleton.
10+
11+
The label now does not fill an untitled root `page:header` when all three of these hold:
12+
13+
- the page's `type` is `'record'` as served (a document with no `type` is not a record page);
14+
- the page's `object` is a non-empty string;
15+
- the header keeps its record chrome (`properties.recordChrome` is not `false`).
16+
17+
Everywhere else the label still fills an absent header title, as before. An authored header title follows the existing order, including one that restates the page's `label`. A pack's `pages.<name>.title` and `subtitle` apply as before.

‎content/docs/ui/translations.mdx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ When `title` is omitted, the header keeps the title its author wrote, and
131131
title restates the page's `label`. So a page whose header title matches its
132132
nav label needs only `label`, while a header whose title says something else,
133133
such as a dynamic `'{name}'`, keeps it until the bundle supplies a `title`.
134+
One exception: on a page of `type: 'record'` bound to an `object`, a header
135+
that authors no title and keeps its record chrome (`recordChrome` not
136+
`false`) derives its heading from the record, so `label` does not fill it and
137+
the record's own name shows in every locale.
134138
Every root-level `page:header` (a region's entry or a `slots.<slot>` entry)
135139
is resolved by this one rule.
136140
</Callout>

‎packages/spec/src/system/i18n-resolver.test.ts‎

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2784,7 +2784,10 @@ describe('translatePage — a bundle label never replaces a header title that sa
27842784
expect(headerOf(root, out).title).toBe('客户 {name}');
27852785
});
27862786

2787-
it('CONTROL: a header with no authored title still shows the bundle `label`', () => {
2787+
// The fixture page carries no `type` and no `object`, so as served it is
2788+
// NOT a record page and its untitled header derives nothing from a
2789+
// record. On a record page the label does not fill (#22536, below).
2790+
it('CONTROL: a header with no authored title on a non-record page still shows the bundle `label`', () => {
27882791
for (const header of [{}, { title: '' }, { subtitle: 'Overview' }]) {
27892792
const out = translatePage(pageWith(root, header), labelOnly, { locale: 'zh-CN' });
27902793
expect({ header, title: headerOf(root, out).title }).toEqual({ header, title: '客户详情' });
@@ -2813,6 +2816,142 @@ describe('translatePage — a bundle label never replaces a header title that sa
28132816
});
28142817
});
28152818

2819+
// #22536: on a record page, a header with no title is the sanctioned spelling
2820+
// for "the renderer derives the heading from the record"
2821+
// (`PageHeaderProps.title`). Filling it with the bundle's page `label` put the
2822+
// page's static name in place of the record's, in every locale whose pack
2823+
// carries the label, the en skeleton `os i18n extract` writes included. The
2824+
// label now does not fill an untitled header when the page's `type` is
2825+
// `'record'` as served, its `object` is a non-empty string, and the header
2826+
// keeps its record chrome. Everywhere else it still fills, as before.
2827+
describe('translatePage — a bundle label does not fill the untitled header of a record page', () => {
2828+
const PAGE = 'account_record';
2829+
type Root = 'region' | 'slot';
2830+
const ROOTS: readonly Root[] = ['region', 'slot'];
2831+
2832+
/**
2833+
* The Studio seed's shape: objectui's `createSeed` writes `type: 'record'`,
2834+
* the chosen `object`, and the synthesized `page:header`, which carries
2835+
* `recordChrome: true` and no `title`.
2836+
*/
2837+
const SEED_PAGE: Record<string, unknown> = { label: 'Account Record', type: 'record', object: 'account' };
2838+
const SEED_HEADER = { recordChrome: true };
2839+
2840+
const pageWith = (
2841+
root: Root,
2842+
header: Record<string, unknown>,
2843+
page: Record<string, unknown> = SEED_PAGE,
2844+
): any => {
2845+
const component = { type: 'page:header', properties: header };
2846+
return root === 'region'
2847+
? { name: PAGE, ...page, regions: [{ name: 'main', components: [component] }] }
2848+
: { name: PAGE, ...page, kind: 'slotted', regions: [], slots: { header: component } };
2849+
};
2850+
const headerOf = (root: Root, page: any): Record<string, unknown> =>
2851+
root === 'region' ? page.regions[0].components[0].properties : page.slots.header.properties;
2852+
2853+
/** A zh-CN pack carrying the page label and nothing else. */
2854+
const zhLabelOnly: TranslationBundle = {
2855+
'zh-CN': { pages: { [PAGE]: { label: '客户记录页' } } },
2856+
};
2857+
/** The en skeleton `os i18n extract` writes: the authored label, restated. */
2858+
const enSkeleton: TranslationBundle = {
2859+
en: { pages: { [PAGE]: { label: 'Account Record' } } },
2860+
};
2861+
const CASES = [
2862+
{ pack: 'zh-CN label only', bundle: zhLabelOnly, locale: 'zh-CN', label: '客户记录页' },
2863+
{ pack: 'en extract skeleton', bundle: enSkeleton, locale: 'en', label: 'Account Record' },
2864+
] as const;
2865+
2866+
for (const root of ROOTS) {
2867+
describe(`at a ${root} root`, () => {
2868+
for (const { pack, bundle, locale, label } of CASES) {
2869+
it(`keeps the Studio-seeded record header untitled with the ${pack} pack`, () => {
2870+
const out = translatePage(pageWith(root, SEED_HEADER), bundle, { locale });
2871+
expect(headerOf(root, out)).toEqual({ recordChrome: true });
2872+
expect('title' in headerOf(root, out)).toBe(false);
2873+
// The page's own label still translates; only the header stays as authored.
2874+
expect(out.label).toBe(label);
2875+
});
2876+
}
2877+
2878+
it('keeps an untitled header untitled when `recordChrome` is left at its default', () => {
2879+
const headers: Array<Record<string, unknown>> = [{}, { title: '' }];
2880+
for (const header of headers) {
2881+
const out = translatePage(pageWith(root, header), zhLabelOnly, { locale: 'zh-CN' });
2882+
expect(headerOf(root, out)).toEqual(header);
2883+
}
2884+
});
2885+
2886+
it('still applies the bundle subtitle beside the untitled header', () => {
2887+
const subtitled: TranslationBundle = {
2888+
'zh-CN': { pages: { [PAGE]: { label: '客户记录页', subtitle: '客户概览' } } },
2889+
};
2890+
const out = translatePage(pageWith(root, SEED_HEADER), subtitled, { locale: 'zh-CN' });
2891+
expect(headerOf(root, out)).toEqual({ recordChrome: true, subtitle: '客户概览' });
2892+
});
2893+
2894+
// One control per condition: drop any one of the three and the header
2895+
// derives nothing from a record, so the label fills it as before.
2896+
it('CONTROL: a page with no `type` is not a record page as served, and the label fills', () => {
2897+
const untyped = { label: SEED_PAGE.label, object: SEED_PAGE.object };
2898+
const out = translatePage(pageWith(root, SEED_HEADER, untyped), zhLabelOnly, { locale: 'zh-CN' });
2899+
expect(headerOf(root, out).title).toBe('客户记录页');
2900+
});
2901+
2902+
it('CONTROL: a record page with no `object`, or an empty one, has no record to derive from, and the label fills', () => {
2903+
const unbound = { label: SEED_PAGE.label, type: SEED_PAGE.type };
2904+
const pages: Array<Record<string, unknown>> = [unbound, { ...SEED_PAGE, object: '' }];
2905+
for (const page of pages) {
2906+
const out = translatePage(pageWith(root, SEED_HEADER, page), zhLabelOnly, { locale: 'zh-CN' });
2907+
expect({ object: page.object, title: headerOf(root, out).title })
2908+
.toEqual({ object: page.object, title: '客户记录页' });
2909+
}
2910+
});
2911+
2912+
it('CONTROL: a record-page header with `recordChrome: false` draws the bare heading, and the label fills', () => {
2913+
const out = translatePage(pageWith(root, { recordChrome: false }), zhLabelOnly, { locale: 'zh-CN' });
2914+
expect(headerOf(root, out)).toEqual({ recordChrome: false, title: '客户记录页' });
2915+
});
2916+
2917+
it('CONTROL: an object-bound page of another `type` derives nothing from a record, and the label fills', () => {
2918+
for (const type of ['list', 'app', 'home', 'utility']) {
2919+
const out = translatePage(pageWith(root, SEED_HEADER, { ...SEED_PAGE, type }), zhLabelOnly, { locale: 'zh-CN' });
2920+
expect({ type, title: headerOf(root, out).title }).toEqual({ type, title: '客户记录页' });
2921+
}
2922+
});
2923+
2924+
// An authored title on a record page follows #22508 unchanged.
2925+
it('an authored template title on a record page is kept against a label-only pack', () => {
2926+
const out = translatePage(pageWith(root, { ...SEED_HEADER, title: '{name}' }), zhLabelOnly, { locale: 'zh-CN' });
2927+
expect(headerOf(root, out).title).toBe('{name}');
2928+
});
2929+
2930+
it('an authored title on a record page that restates the page label takes the label translation', () => {
2931+
const out = translatePage(pageWith(root, { ...SEED_HEADER, title: 'Account Record' }), zhLabelOnly, { locale: 'zh-CN' });
2932+
expect(headerOf(root, out).title).toBe('客户记录页');
2933+
});
2934+
2935+
it('a pack with `title` replaces an authored title on a record page', () => {
2936+
const withTitle: TranslationBundle = {
2937+
'zh-CN': { pages: { [PAGE]: { label: '客户记录页', title: '客户 {name}' } } },
2938+
};
2939+
const out = translatePage(pageWith(root, { ...SEED_HEADER, title: '{name}' }), withTitle, { locale: 'zh-CN' });
2940+
expect(headerOf(root, out).title).toBe('客户 {name}');
2941+
});
2942+
});
2943+
}
2944+
2945+
it('serves the same answer through `translateMetadataDocument`, the function the REST page doors call', () => {
2946+
for (const root of ROOTS) {
2947+
for (const { bundle, locale } of CASES) {
2948+
const out = translateMetadataDocument('page', pageWith(root, SEED_HEADER), bundle, { locale });
2949+
expect({ root, locale, header: headerOf(root, out) }).toEqual({ root, locale, header: { recordChrome: true } });
2950+
}
2951+
}
2952+
});
2953+
});
2954+
28162955
describe('translatePage — filter-preset tab bar', () => {
28172956
const bundle: TranslationBundle = {
28182957
'zh-CN': {

‎packages/spec/src/system/i18n-resolver.ts‎

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,8 +1884,14 @@ function sameAuthoredText(a: string | Record<string, string>, b: unknown): boole
18841884
* 2. the header's AUTHORED title, left as written: a template such as
18851885
* `'{name}'`, a plain string, or an inline locale map;
18861886
* 3. the bundle's `pages.<name>.label`, in two cases only: the header authors
1887-
* no title, or its title restates the page's own authored `label`. In the
1888-
* second case the label's translation IS a translation of that text, and
1887+
* no title AND does not derive its heading from a record
1888+
* ({@link headerDerivesFromRecord}), or its title restates the page's own
1889+
* authored `label`. In the first case's exception, an absent title IS the
1890+
* authored value: `PageHeaderProps.title` makes "omitted" the sanctioned
1891+
* spelling for "the renderer derives the heading from the record", so the
1892+
* label filling it would put the page's static name in place of the
1893+
* record's, and the title stays absent (#22536). In the second case the
1894+
* label's translation IS a translation of that text, and
18891895
* `os i18n extract` offers no `title` key for such a header (it offers the
18901896
* label alone), so reading the label here is what translates it.
18911897
*
@@ -1896,14 +1902,53 @@ function resolvePageHeaderTitle(
18961902
bundleLabel: string | undefined,
18971903
authoredTitle: unknown,
18981904
authoredPageLabel: unknown,
1905+
derivesFromRecord: boolean,
18991906
): string | undefined {
19001907
if (bundleTitle !== undefined) return bundleTitle;
19011908
if (bundleLabel === undefined) return undefined;
19021909
const own = authoredLabelText(authoredTitle);
1903-
if (own === undefined || sameAuthoredText(own, authoredPageLabel)) return bundleLabel;
1910+
if (own === undefined) return derivesFromRecord ? undefined : bundleLabel;
1911+
if (sameAuthoredText(own, authoredPageLabel)) return bundleLabel;
19041912
return undefined;
19051913
}
19061914

1915+
/**
1916+
* Does this root-level `page:header` derive its heading from a record? The
1917+
* test step 3 of {@link resolvePageHeaderTitle} reads (#22536), and all three
1918+
* conditions must hold:
1919+
*
1920+
* - the page's `type` is `'record'`, read as SERVED. A document with no
1921+
* `type` is not a record page to the hosts below, so it is not one here
1922+
* either, although `PageSchema` defaults an absent `type` to `'record'`
1923+
* at parse;
1924+
* - the page's `object` is a non-empty string;
1925+
* - the header does not opt out of the record chrome
1926+
* (`properties.recordChrome !== false`).
1927+
*
1928+
* The renderer's own test is runtime, not a property of this document.
1929+
* objectui's `PageHeaderRenderer` draws the record-derived heading when a
1930+
* record context is mounted (`ctx.data` and `ctx.objectSchema`) and the
1931+
* header keeps its record chrome. The first two conditions are when the
1932+
* record hosts mount that context: the record route mounts it for the page
1933+
* `usePageAssignment` picks (`type === 'record'` and `object` naming the
1934+
* route's object), and the Studio preview mounts it for a `type: 'record'`
1935+
* page with an `object`. The third is the renderer's own switch, which it
1936+
* reads off the header node. Only `properties.recordChrome` is read:
1937+
* `PageComponentSchema` is strict and refuses a bare top-level
1938+
* `recordChrome` on a component, so a valid document never carries that
1939+
* spelling.
1940+
*
1941+
* One host is not visible from here, and that gap is accepted. A
1942+
* `type: 'modal'` action renders the page it targets inside the record
1943+
* context of the page it was invoked from, whatever that page's `type` is.
1944+
* Such a page keeps the label fill.
1945+
*/
1946+
function headerDerivesFromRecord(page: PageLike, header: PageComponentLike): boolean {
1947+
return page.type === 'record'
1948+
&& typeof page.object === 'string' && page.object.length > 0
1949+
&& header.properties?.recordChrome !== false;
1950+
}
1951+
19071952
/**
19081953
* The copy keys `pages.<name>.components.<id>` carries (#6080). Measured
19091954
* against `ComponentPropsMap` — see the schema's own note for which component
@@ -2265,7 +2310,10 @@ export function walkAddressedPageComponents(
22652310
* authors no title or its title restates the page's own `label`, so a
22662311
* translator need not repeat a string identical to the page's nav label
22672312
* ({@link resolvePageHeaderTitle}). A label never replaces a header title
2268-
* that says something else, a dynamic `'{name}'` above all.
2313+
* that says something else, a dynamic `'{name}'` above all. Nor does it fill
2314+
* the absent title of a header that derives its heading from a record
2315+
* ({@link headerDerivesFromRecord}): on a `type: 'record'` page bound to an
2316+
* object, "no title" means "the record's own name".
22692317
*
22702318
* **One component, one address.** A ROOT-LEVEL `page:header` — an entry of a
22712319
* region's `components[]`, or of a `slots.<slot>` on a `kind: 'slotted'` page
@@ -2421,6 +2469,7 @@ export function translatePage<T extends PageLike>(
24212469
// `nested: false`, so the fallback order cannot differ between them.
24222470
const headerTitle = resolvePageHeaderTitle(
24232471
bundleHeaderTitle, bundleLabel, next.properties?.title, doc.label,
2472+
headerDerivesFromRecord(doc, next),
24242473
);
24252474
if (headerTitle === undefined && headerSubtitle === undefined) return next;
24262475
return {

0 commit comments

Comments
 (0)