Skip to content

Commit f3bd1c6

Browse files
committed
feat(vedic): render the modern planets as their own row group, label the B.V. Raman frame, and bind the ayanamsa labels to the spec
1 parent ad9801a commit f3bd1c6

6 files changed

Lines changed: 74 additions & 10 deletions

File tree

bun.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"@hey-api/openapi-ts": "0.99.0",
4646
"@lit-labs/rollup-plugin-minify-html-literals": "^0.2.0",
4747
"@playwright/test": "^1.61.1",
48-
"@roxyapi/sdk": "^1.2.53",
48+
"@roxyapi/sdk": "^1.2.57",
4949
"@types/bun": "^1.3.14",
5050
"@types/node": "^26.1.1",
5151
"@types/react": "^19.2.17",

packages/ui/src/components/vedic-planets-table.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { RoxyDataElement } from '../utils/base-element.js';
66
import { baseStyles } from '../utils/base-styles.js';
77
import { formatSignPosition } from '../utils/degree.js';
88
import { chevron, disclosureStyles } from '../utils/disclosure.js';
9-
import { formatNumber } from '../utils/format.js';
9+
import { formatNumber, formatWithSanskrit } from '../utils/format.js';
1010
import { capitalize } from '../utils/string.js';
1111

1212
/**
@@ -108,6 +108,12 @@ export class RoxyVedicPlanetsTable extends RoxyDataElement<BirthChartResponse> {
108108
tbody tr.lagna {
109109
background: color-mix(in srgb, var(--roxy-accent, #f59e0b) 10%, transparent);
110110
}
111+
/* The moderns are separated from the nine grahas by a RULE, never a tint. A tint
112+
would read as emphasis the tradition does not give them, and a tinted row is
113+
exactly where this library has measured muted text below the AA floor before. */
114+
tbody tr.modern-first {
115+
border-top-width: 2px;
116+
}
111117
td.graha {
112118
font-weight: var(--roxy-weight-bold, 600);
113119
color: var(--roxy-fg, #0a0a0a);
@@ -268,7 +274,8 @@ export class RoxyVedicPlanetsTable extends RoxyDataElement<BirthChartResponse> {
268274
nakshatra lord, house, its state in all three avastha systems, and retrograde
269275
state. Jagradadi and Deeptadi are read from sign dignity, which the nodes and
270276
the Lagna do not have, so those two cells are blank on the Rahu, Ketu and
271-
Lagna rows.
277+
Lagna rows. Uranus, Neptune and Pluto appear only when asked for and rule no
278+
sign, so every avastha and house cell is blank on their rows too.
272279
</caption>
273280
<thead>
274281
<tr>
@@ -322,6 +329,7 @@ export class RoxyVedicPlanetsTable extends RoxyDataElement<BirthChartResponse> {
322329
<td>${p.isRetrograde ? html`<span class="retro">R</span>` : nothing}</td>
323330
</tr>`;
324331
})}
332+
${this.renderModernRows()}
325333
</tbody>
326334
</table>
327335
</div>
@@ -333,6 +341,39 @@ export class RoxyVedicPlanetsTable extends RoxyDataElement<BirthChartResponse> {
333341
</div>`;
334342
}
335343

344+
/**
345+
* Uranus, Neptune and Pluto, present only when the caller sent `modernPlanets: true`.
346+
*
347+
* @remarks
348+
* They arrive in their OWN array rather than inside `meta`, which is deliberate on the API side and load-bearing here: a component iterating `meta` must not pick them up by accident, because classical Jyotish is defined over nine grahas. The moderns rule no sign, so they have no dignity and therefore no Baladi, Jagradadi or Deeptadi state and no house lordship.
349+
*
350+
* Those cells are left BLANK rather than filled with a zero or a dash, matching what the nodes and the Lagna already do two columns over. A zero would read as a measured state of zero strength, which is a different and false claim from "this system does not apply here".
351+
*/
352+
private renderModernRows() {
353+
const moderns = this.data?.modernPlanets ?? [];
354+
if (moderns.length === 0) return nothing;
355+
return moderns.map((m, i) => {
356+
const signGlyph = SIGN_GLYPH[capitalize(m.rashi ?? '')] ?? '';
357+
return html`<tr class="modern ${i === 0 ? 'modern-first' : ''}">
358+
<td class="graha">${formatWithSanskrit(m.planet, m.sanskritName)}</td>
359+
<td>
360+
${signGlyph ? html`<span class="glyph">${signGlyph}</span>` : nothing}${m.rashi ?? ''}
361+
</td>
362+
<td class="num">
363+
${typeof m.longitude === 'number' ? formatSignPosition(m.longitude) : ''}
364+
</td>
365+
<td>${m.nakshatra?.name ?? ''}</td>
366+
<td class="num">${m.nakshatra?.pada ?? ''}</td>
367+
<td>${m.nakshatra?.lord ?? ''}</td>
368+
<td></td>
369+
<td></td>
370+
<td></td>
371+
<td></td>
372+
<td>${m.isRetrograde ? html`<span class="retro">R</span>` : nothing}</td>
373+
</tr>`;
374+
});
375+
}
376+
336377
private renderCombustion() {
337378
const combust = this.data?.combustion ?? [];
338379
if (combust.length === 0) return nothing;

packages/ui/src/utils/format.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,15 @@ export function formatAspectName(a: { type?: string }): string {
144144
/**
145145
* Display label for an ayanamsa identifier. `kp-newcomb` -> `KP Newcomb`.
146146
*
147-
* The raw values are API enums. KP is an initialism and must stay uppercase, which {@link humanize} alone cannot know, so the three known frames are mapped explicitly and anything else degrades to a humanized slug rather than rendering the enum.
147+
* The raw values are API enums, and every one of them needs a human form that {@link humanize} cannot derive: KP is an initialism that must stay uppercase, and `raman` is a person, B.V. Raman, not a word. Anything unmapped degrades to a humanized slug rather than rendering the enum, which is a safe fallback and NOT a licence to skip the map: `raman` shipped as a frame months after this map was written and read as a bare "Raman" until 2026-08-03. `format.test.ts` now fails if the committed spec gains a frame this map lacks.
148148
*/
149-
const AYANAMSA_LABEL: Record<string, string> = {
149+
export const AYANAMSA_LABEL: Record<string, string> = {
150150
'kp-newcomb': 'KP Newcomb',
151151
'kp-old': 'KP Old',
152152
lahiri: 'Lahiri',
153+
raman: 'B.V. Raman',
154+
155+
custom: 'Custom',
153156
};
154157

155158
export function formatAyanamsa(type: unknown, degrees?: unknown): string {

packages/ui/tests/utils.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
polarToCartesian,
2020
} from '../src/utils/degree.js';
2121
import {
22+
AYANAMSA_LABEL,
2223
distinctSanskrit,
2324
formatAspectName,
2425
formatAyanamsa,
@@ -553,12 +554,25 @@ describe('shared display formatters', () => {
553554
expect(formatDateRange(undefined, undefined)).toBe('');
554555
});
555556

557+
test('every ayanamsa the spec accepts has a human label', async () => {
558+
// The map is hand-written because no humanizer can know that KP is an initialism or
559+
// that Raman is a person. That is exactly why it goes stale: `raman` shipped months
560+
// after the map and rendered as a bare "Raman" until someone read the output. Bind it
561+
// to the committed spec so the next frame cannot arrive unlabelled.
562+
const spec = await Bun.file('specs/openapi.json').json();
563+
const frames: string[] =
564+
spec.components.schemas.BirthChartRequest.properties.ayanamsa.enum;
565+
expect(frames.length).toBeGreaterThan(3);
566+
expect(frames.filter((f) => !AYANAMSA_LABEL[f])).toEqual([]);
567+
});
568+
556569
test('formatAyanamsa keeps KP uppercase and degrades unknown frames', () => {
557570
expect(formatAyanamsa('kp-newcomb', 23.6214)).toBe(
558571
'KP Newcomb (23.62\u00b0)',
559572
);
560573
expect(formatAyanamsa('lahiri', 23.72)).toBe('Lahiri (23.72\u00b0)');
561574
expect(formatAyanamsa('lahiri')).toBe('Lahiri');
575+
expect(formatAyanamsa('raman', 22.5)).toBe('B.V. Raman (22.5\u00b0)');
562576
expect(formatAyanamsa('raman-something')).toBe('Raman something');
563577
});
564578

scripts/refresh-samples.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ async function main() {
161161
},
162162
}),
163163
),
164+
// `modernPlanets` and `avasthaInfo` default to FALSE on the API, and the planets
165+
// table renders a row group and three columns that exist only when they are sent.
166+
// A sample that omits them leaves those renderers permanently unaudited, since the
167+
// audit can only see what the fixture contains.
164168
run('vedic-planets', () =>
165169
roxy.vedicAstrology.generateBirthChart({
166170
body: {
@@ -169,6 +173,8 @@ async function main() {
169173
latitude: PERSON1.latitude,
170174
longitude: PERSON1.longitude,
171175
timezone: PERSON1.timezone,
176+
modernPlanets: true,
177+
avasthaInfo: true,
172178
},
173179
}),
174180
),

0 commit comments

Comments
 (0)