Skip to content

Commit 764bd82

Browse files
committed
refactor(interp): one keyword chip renderer instead of four copies
1 parent 408d181 commit 764bd82

5 files changed

Lines changed: 25 additions & 23 deletions

File tree

packages/ui/src/components/natal-chart.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
import {
3232
type InterpSection,
3333
interpAccordionStyles,
34+
renderKeywordChips,
3435
} from '../utils/interp-accordion.js';
3536
import { display, displayList } from '../utils/localized.js';
3637
import { capitalize, lookupKey } from '../utils/string.js';
@@ -1169,11 +1170,7 @@ export class RoxyNatalChart extends RoxyDataElement<WheelChart> {
11691170
aside: [display(p, 'sign'), deg].filter(Boolean).join(' '),
11701171
body: lead,
11711172
extra: html`${detail ? html`<p>${detail}</p>` : nothing}
1172-
${
1173-
interp.keywords?.length
1174-
? html`<div class="interp-keywords">${interp.keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`
1175-
: nothing
1176-
}`,
1173+
${renderKeywordChips(interp.keywords)}`,
11771174
};
11781175
});
11791176
return this.renderInterpretation(

packages/ui/src/components/synastry-chart.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import {
1414
formatNumber,
1515
normalizeAspect,
1616
} from '../utils/format.js';
17-
import { interpAccordionStyles } from '../utils/interp-accordion.js';
17+
import {
18+
interpAccordionStyles,
19+
renderKeywordChips,
20+
} from '../utils/interp-accordion.js';
1821
import { display } from '../utils/localized.js';
1922
import { capitalize } from '../utils/string.js';
2023

@@ -583,11 +586,7 @@ export class RoxySynastryChart extends RoxyDataElement<CalculateSynastryResponse
583586
: nothing
584587
}
585588
${meaning.description?.short ? html`<p>${meaning.description.short}</p>` : nothing}
586-
${
587-
meaning.keywords?.length
588-
? html`<div class="interp-keywords">${meaning.keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`
589-
: nothing
590-
}
589+
${renderKeywordChips(meaning.keywords)}
591590
</div>
592591
</details>`;
593592
}

packages/ui/src/components/transit-wheel.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
import {
2929
type InterpSection,
3030
interpAccordionStyles,
31+
renderKeywordChips,
3132
} from '../utils/interp-accordion.js';
3233
import { display } from '../utils/localized.js';
3334
import { capitalize } from '../utils/string.js';
@@ -1035,11 +1036,7 @@ export class RoxyTransitWheel extends RoxyDataElement<CalculateTransitAspectsRes
10351036
extra: html`${t.impact ? html`<p><strong>${this.t('Impact')}:</strong> ${t.impact}</p>` : nothing}
10361037
${t.timing ? html`<p><strong>${this.t('Timing')}:</strong> ${t.timing}</p>` : nothing}
10371038
${t.guidance ? html`<p><strong>${this.t('Guidance')}:</strong> ${t.guidance}</p>` : nothing}
1038-
${
1039-
t.keywords?.length
1040-
? html`<div class="interp-keywords">${t.keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`
1041-
: nothing
1042-
}`,
1039+
${renderKeywordChips(t.keywords)}`,
10431040
};
10441041
});
10451042
return this.renderInterpretation(

packages/ui/src/utils/interp-accordion.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ export const interpAccordionStyles = css`
142142
${readingDetailStyles}
143143
`;
144144

145+
/** The keyword chips a reading carries, or nothing. One renderer because four components drew the identical row, and the markup has to match {@link readingDetailStyles}, which is the pairing that drifts when it is copied. */
146+
export function renderKeywordChips(keywords: readonly string[] | undefined) {
147+
if (!keywords?.length) return nothing;
148+
return html`<div class="interp-keywords">${keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`;
149+
}
150+
145151
/** The prose an endpoint returns ABOUT one contact: a summary, up to three labelled lines, and the keyword chips under them. Every field is optional, so a narrower response renders fewer rows rather than empty ones. */
146152
export interface ReadingDetail {
147153
summary?: string;
@@ -161,11 +167,7 @@ export function renderReadingDetail(
161167
value ? html`<p><strong>${t(label)}</strong> ${value}</p>` : nothing;
162168
return html`${d.summary ? html`<p>${d.summary}</p>` : nothing}
163169
${line('Impact:', d.impact)}${line('Timing:', d.timing)}${line('Guidance:', d.guidance)}
164-
${
165-
d.keywords?.length
166-
? html`<div class="interp-keywords">${d.keywords.map((k) => html`<span class="kw">${k}</span>`)}</div>`
167-
: nothing
168-
}`;
170+
${renderKeywordChips(d.keywords)}`;
169171
}
170172

171173
/**

packages/ui/tests/utils.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,15 +1024,22 @@ describe('utils/interp-accordion', () => {
10241024
* @remarks
10251025
* The markup and its CSS live in one module precisely so they cannot drift, but nothing stops a caller from importing only the renderer: the chips then arrive unstyled, which reads as a design choice rather than a defect and no snapshot, axe pass or layout gate reports it. That is how one card ended up with a hand-copied copy of these rules that had drifted in two ways.
10261026
*/
1027-
test('every caller of renderReadingDetail ships its styles', async () => {
1027+
test('every caller of the shared reading renderers ships their styles', async () => {
10281028
const { readdir } = await import('node:fs/promises');
10291029
const dir = 'packages/ui/src/components';
10301030
const offenders: string[] = [];
10311031
let callers = 0;
10321032
for (const file of await readdir(dir)) {
10331033
if (!file.endsWith('.ts') || file.endsWith('.test.ts')) continue;
10341034
const src = await Bun.file(`${dir}/${file}`).text();
1035-
if (!src.includes('renderReadingDetail(')) continue;
1035+
// Either helper emits `.interp-keywords`, so either one obliges the caller
1036+
// to ship the styles for it.
1037+
if (
1038+
!src.includes('renderReadingDetail(') &&
1039+
!src.includes('renderKeywordChips(')
1040+
) {
1041+
continue;
1042+
}
10361043
callers++;
10371044
// Read the `static styles` array itself, never the whole file: the import
10381045
// line alone satisfies a file-wide search, so a component that imports the

0 commit comments

Comments
 (0)