@roxyapi/ui 0.12.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -866,11 +866,11 @@ export type TransitsRequest = {
866
866
  */
867
867
  natalChart?: {
868
868
  /**
869
- * Date in YYYY-MM-DD format.
869
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
870
870
  */
871
871
  date: string;
872
872
  /**
873
- * Time in 24-hour HH:MM:SS format.
873
+ * Time in 24-hour format. Seconds are optional and default to 00 (14:30 becomes 14:30:00); a single-digit hour is zero-padded. Out-of-range values are rejected.
874
874
  */
875
875
  time: string;
876
876
  latitude: number;
@@ -2749,7 +2749,7 @@ export type BirthChartResponse = {
2749
2749
  };
2750
2750
  };
2751
2751
  /**
2752
- * Twelve classical yogas detected against this chart: Gajakesari (three-rule parashara definition), Sunapha, Anapha, Dhurdhura, Kemadruma, Chandra Mangala, Budha-Aditya, and the five Pancha Mahapurusha (Ruchaka, Bhadra, Hamsa, Malavya, Sasa). Each entry carries an `id` (matches `GET /yoga/{id}` for full glossary lookup), a `present` boolean, and classical-text `evidence` for the rule that triggered or failed.
2752
+ * Twelve classical yogas detected against this chart: Gajakesari (three-rule parashara definition), Sunapha, Anapha, Dhurdhura, Kemadruma, Chandra Mangala, Budha-Aditya, and the five Pancha Mahapurusha (Ruchaka, Bhadra, Hamsa, Malavya, Sasa). Each entry carries an `id` (matches `GET /yoga/{id}` for full glossary lookup), a `present` boolean, a `quality` (Positive, Negative, or Both = auspicious, inauspicious, or context-dependent), and classical-text `evidence` for the rule that triggered or failed.
2753
2753
  */
2754
2754
  yogas?: Array<{
2755
2755
  /**
@@ -3586,7 +3586,7 @@ export type SadhesatiRequest = {
3586
3586
  };
3587
3587
  export type YogaDetectResponse = {
3588
3588
  /**
3589
- * Array of 12 detected yogas. Every entry carries a present boolean; filter on present === true for active yogas. Evidence text names the rule that triggered or failed.
3589
+ * Array of 12 detected yogas. Every entry carries a `present` boolean and a `quality` (Positive, Negative, or Both = auspicious, inauspicious, or context-dependent); filter on present === true for active yogas. Evidence text names the rule that triggered or failed.
3590
3590
  */
3591
3591
  yogas: Array<{
3592
3592
  /**
@@ -4703,11 +4703,11 @@ export type KpPlanetsIntervalResponse = {
4703
4703
  };
4704
4704
  export type KpPlanetsIntervalRequest = {
4705
4705
  /**
4706
- * Start datetime in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored). With timezone 0, Z suffix is treated as UTC.
4706
+ * Start datetime in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
4707
4707
  */
4708
4708
  startDatetime: string;
4709
4709
  /**
4710
- * End datetime in ISO 8601 format. Maximum 7 days from start. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
4710
+ * End datetime in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Maximum 7 days from start. Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
4711
4711
  */
4712
4712
  endDatetime: string;
4713
4713
  /**
@@ -14042,7 +14042,7 @@ export type GetBasicPanchangResponse = GetBasicPanchangResponses[keyof GetBasicP
14042
14042
  export type GetDetailedPanchangData = {
14043
14043
  body?: {
14044
14044
  /**
14045
- * Date in YYYY-MM-DD format.
14045
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
14046
14046
  */
14047
14047
  date: string;
14048
14048
  /**
@@ -14596,7 +14596,7 @@ export type GetDetailedPanchangResponses = {
14596
14596
  */
14597
14597
  panchaka: {
14598
14598
  /**
14599
- * Whether Panchaka is in force at sunrise, the panchang day reference. Can be false while startsAt and endsAt are populated when Panchaka begins later in the day.
14599
+ * True when Panchaka is in effect on this date, whether it is already running at sunrise or begins later in the day, in which case startsAt and endsAt give the window. False only when no Panchaka touches this date.
14600
14600
  */
14601
14601
  active: boolean;
14602
14602
  /**
@@ -14613,11 +14613,11 @@ export type GetDetailedPanchangResponses = {
14613
14613
  endsAt: string;
14614
14614
  };
14615
14615
  /**
14616
- * Bhadra (Vishti Karana), the 7th movable karana, avoided for all auspicious activities. Bhadra recurs roughly every 3 to 5 days and lasts about half a tithi, so most occurrences begin after sunrise rather than at it. startsAt and endsAt report the Bhadra beginning on this date regardless of whether it is active at sunrise.
14616
+ * Bhadra (Vishti Karana), the 7th movable karana, avoided for all auspicious activities. Bhadra recurs roughly every 3 to 5 days and lasts about half a tithi. active is true whenever a Bhadra is attributed to this date; startsAt and endsAt give the window, which may end on the next calendar day.
14617
14617
  */
14618
14618
  bhadra: {
14619
14619
  /**
14620
- * Whether Bhadra (Vishti Karana) is in force at sunrise, the panchang day reference moment. Can be false while startsAt and endsAt are populated when Bhadra begins later in the day.
14620
+ * True when a Bhadra (Vishti Karana) occurs on this date, in which case startsAt and endsAt give its window. False only when no Bhadra begins on this date.
14621
14621
  */
14622
14622
  active: boolean;
14623
14623
  /**
@@ -14713,7 +14713,7 @@ export type GetDetailedPanchangResponse = GetDetailedPanchangResponses[keyof Get
14713
14713
  export type GetChoghadiyaData = {
14714
14714
  body?: {
14715
14715
  /**
14716
- * Date in YYYY-MM-DD format.
14716
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
14717
14717
  */
14718
14718
  date: string;
14719
14719
  /**
@@ -14897,7 +14897,7 @@ export type GetChoghadiyaResponse = GetChoghadiyaResponses[keyof GetChoghadiyaRe
14897
14897
  export type GetHoraData = {
14898
14898
  body?: {
14899
14899
  /**
14900
- * Date in YYYY-MM-DD format.
14900
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
14901
14901
  */
14902
14902
  date: string;
14903
14903
  /**
@@ -16320,7 +16320,7 @@ export type GetKpRulingPlanetsData = {
16320
16320
  */
16321
16321
  timezone?: number | string;
16322
16322
  /**
16323
- * ISO 8601 datetime for ruling planets. Defaults to current time. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16323
+ * ISO 8601 datetime (YYYY-MM-DDTHH:MM:SS) for ruling planets. Defaults to current time. Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16324
16324
  */
16325
16325
  datetime?: string;
16326
16326
  /**
@@ -16452,11 +16452,11 @@ export type GetKpRulingPlanetsResponse = GetKpRulingPlanetsResponses[keyof GetKp
16452
16452
  export type GetKpRulingIntervalData = {
16453
16453
  body?: {
16454
16454
  /**
16455
- * Start of the interval range in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16455
+ * Start of the interval range in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16456
16456
  */
16457
16457
  startDatetime: string;
16458
16458
  /**
16459
- * End of the interval range in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16459
+ * End of the interval range in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16460
16460
  */
16461
16461
  endDatetime: string;
16462
16462
  /**
@@ -21156,7 +21156,7 @@ export type CalculatePersonalDayData = {
21156
21156
  */
21157
21157
  day: number;
21158
21158
  /**
21159
- * Target date in YYYY-MM-DD format (defaults to today)
21159
+ * Target date in YYYY-MM-DD format. Defaults to today (UTC).
21160
21160
  */
21161
21161
  targetDate?: string;
21162
21162
  };
@@ -33097,7 +33097,7 @@ export type GetCrystalPairingsData = {
33097
33097
  body?: never;
33098
33098
  path: {
33099
33099
  /**
33100
- * URL-safe crystal identifier to find pairings for (e.g., "amethyst", "rose-quartz").
33100
+ * URL-safe crystal identifier to find pairings for, case-insensitive (e.g., "amethyst", "Amethyst", "rose-quartz" all resolve).
33101
33101
  */
33102
33102
  id: string;
33103
33103
  };
@@ -34026,7 +34026,7 @@ export type GetCrystalData = {
34026
34026
  body?: never;
34027
34027
  path: {
34028
34028
  /**
34029
- * URL-safe crystal identifier (e.g., "amethyst", "rose-quartz", "black-tourmaline"). Must match an entry in the database.
34029
+ * URL-safe crystal identifier, case-insensitive (e.g., "amethyst", "Amethyst", "rose-quartz" all resolve). Must match an entry in the database.
34030
34030
  */
34031
34031
  id: string;
34032
34032
  };
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const ROXY_UI_VERSION = "0.12.1";
1
+ export declare const ROXY_UI_VERSION = "0.12.2";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roxyapi/ui",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "description": "Web components for the RoxyAPI catalog. Drop-in charts, tables, cards, forms for astrology, tarot, numerology, biorhythm, I Ching, crystals, dreams, angel numbers, and more. One key, beautiful in 30 minutes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -2,6 +2,7 @@ import { css, html, nothing, type TemplateResult } from 'lit';
2
2
  import { customElement, property } from 'lit/decorators.js';
3
3
  import { RoxyDataElement } from '../utils/base-element.js';
4
4
  import { baseStyles } from '../utils/base-styles.js';
5
+ import { formatDate, formatNumber, formatTime } from '../utils/format.js';
5
6
  import { humanize } from '../utils/string.js';
6
7
 
7
8
  /**
@@ -13,19 +14,30 @@ import { humanize } from '../utils/string.js';
13
14
  * 1. Primitive (string, number, boolean) -> single line.
14
15
  * 2. Array of primitives -> chip list.
15
16
  * 3. Array of objects with shared keys -> table.
16
- * 4. Object with title-like field -> card with key/value rows.
17
+ * 4. Object with title-like field -> card with key/value rows; object and
18
+ * object-array values promote to full-width sections below the rows so
19
+ * nested tables keep the whole container width.
17
20
  * 5. Otherwise -> definition list of all keys.
18
21
  *
22
+ * Scalar display rules (shared by rows, chips, and table cells): numbers
23
+ * round to 2 decimals, booleans read Yes/No, ISO dates and datetimes format
24
+ * for the locale, SCREAMING_SNAKE enums humanize, http(s) strings link out.
25
+ *
19
26
  * When a schema declares an `x-roxy-ui` hint, a future dispatcher can opt
20
27
  * into a hand-tuned component instead of this fallback.
21
28
  */
22
29
 
23
30
  type Json = string | number | boolean | null | Json[] | { [key: string]: Json };
31
+ type Scalar = string | number | boolean;
24
32
 
25
33
  const TITLE_KEYS = ['title', 'name', 'label', 'heading', 'overview', 'summary'];
26
34
  const IMAGE_KEYS = ['imageUrl', 'image', 'icon', 'symbol'];
27
35
  const SKIP_KEYS = ['imageUrl', 'image']; // rendered separately, not in body rows
28
36
 
37
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?$/;
38
+ const ENUM_STRING = /^[A-Z0-9]+(_[A-Z0-9]+)+$/;
39
+ const LINK_STRING = /^https?:\/\//;
40
+
29
41
  // Hard cap on recursion. Real RoxyAPI responses nest at most 5-6 deep; anything
30
42
  // deeper is either a circular reference (which would otherwise infinite-loop)
31
43
  // or a payload too rich for the generic fallback to render usefully. The
@@ -34,6 +46,21 @@ const SKIP_KEYS = ['imageUrl', 'image']; // rendered separately, not in body row
34
46
  // this same class without a separate import.
35
47
  const MAX_DEPTH = 6;
36
48
 
49
+ function isPrimitive(value: Json | undefined): value is Scalar | null {
50
+ return (
51
+ value === null || ['string', 'number', 'boolean'].includes(typeof value)
52
+ );
53
+ }
54
+
55
+ /** Object or object-bearing array: needs full width, never fits a dl value cell. */
56
+ function isComplex(value: Json): boolean {
57
+ return (
58
+ value !== null &&
59
+ typeof value === 'object' &&
60
+ !(Array.isArray(value) && value.every(isPrimitive))
61
+ );
62
+ }
63
+
37
64
  @customElement('roxy-data')
38
65
  export class RoxyData extends RoxyDataElement<Json> {
39
66
  static styles = [
@@ -47,6 +74,10 @@ export class RoxyData extends RoxyDataElement<Json> {
47
74
  box-shadow: var(--roxy-shadow-sm);
48
75
  }
49
76
 
77
+ .roxy-card a {
78
+ color: var(--roxy-accent-ink, #b45309);
79
+ }
80
+
50
81
  .roxy-title {
51
82
  font-size: var(--roxy-text-lg, 1.125rem);
52
83
  font-weight: var(--roxy-weight-bold, 600);
@@ -95,6 +126,9 @@ export class RoxyData extends RoxyDataElement<Json> {
95
126
  font-size: var(--roxy-text-xs, 0.75rem);
96
127
  }
97
128
 
129
+ .roxy-table-wrap {
130
+ overflow-x: auto;
131
+ }
98
132
  table.roxy-table {
99
133
  width: 100%;
100
134
  border-collapse: collapse;
@@ -106,6 +140,8 @@ export class RoxyData extends RoxyDataElement<Json> {
106
140
  padding: var(--roxy-space-sm, 0.5rem);
107
141
  text-align: left;
108
142
  text-transform: none;
143
+ word-break: normal;
144
+ overflow-wrap: normal;
109
145
  }
110
146
  table.roxy-table th {
111
147
  color: var(--roxy-muted, #71717a);
@@ -113,6 +149,7 @@ export class RoxyData extends RoxyDataElement<Json> {
113
149
  text-transform: capitalize;
114
150
  font-size: var(--roxy-text-xs, 0.75rem);
115
151
  letter-spacing: 0.04em;
152
+ white-space: nowrap;
116
153
  }
117
154
 
118
155
  .roxy-image {
@@ -123,7 +160,7 @@ export class RoxyData extends RoxyDataElement<Json> {
123
160
  }
124
161
 
125
162
  .roxy-section {
126
- margin-bottom: var(--roxy-space-md, 1rem);
163
+ margin-top: var(--roxy-space-md, 1rem);
127
164
  }
128
165
  .roxy-section h4 {
129
166
  font-size: var(--roxy-text-sm, 0.875rem);
@@ -143,7 +180,9 @@ export class RoxyData extends RoxyDataElement<Json> {
143
180
 
144
181
  protected renderData(data: Json) {
145
182
  if (this.depth >= MAX_DEPTH) {
146
- return html`<div class="roxy-empty" role="status">…</div>`;
183
+ return html`<div class="roxy-empty" role="status">
184
+ Nested data omitted
185
+ </div>`;
147
186
  }
148
187
  return html`<div class="roxy-card" aria-label="Generic data display">
149
188
  ${this.renderValue(data)}
@@ -152,10 +191,7 @@ export class RoxyData extends RoxyDataElement<Json> {
152
191
 
153
192
  private renderValue(value: Json): TemplateResult | typeof nothing {
154
193
  if (value === null || value === undefined) return nothing;
155
- if (typeof value === 'string') return html`<p>${value}</p>`;
156
- if (typeof value === 'number' || typeof value === 'boolean') {
157
- return html`<p>${String(value)}</p>`;
158
- }
194
+ if (isPrimitive(value)) return html`<p>${this.scalarTemplate(value)}</p>`;
159
195
  if (Array.isArray(value)) return this.renderArray(value);
160
196
  return this.renderObject(value as Record<string, Json>);
161
197
  }
@@ -164,13 +200,8 @@ export class RoxyData extends RoxyDataElement<Json> {
164
200
  if (arr.length === 0) {
165
201
  return html`<div class="roxy-empty" role="status">Empty list</div>`;
166
202
  }
167
- const allPrimitive = arr.every(
168
- (v) => v === null || ['string', 'number', 'boolean'].includes(typeof v),
169
- );
170
- if (allPrimitive) {
171
- return html`<ul class="roxy-chips">
172
- ${arr.map((v) => html`<li>${String(v)}</li>`)}
173
- </ul>`;
203
+ if (arr.every(isPrimitive)) {
204
+ return this.renderChips(arr as (Scalar | null)[]);
174
205
  }
175
206
  const allObjects = arr.every(
176
207
  (v) => v !== null && typeof v === 'object' && !Array.isArray(v),
@@ -181,22 +212,37 @@ export class RoxyData extends RoxyDataElement<Json> {
181
212
  </ol>`;
182
213
  }
183
214
 
215
+ private renderChips(arr: (Scalar | null)[]): TemplateResult {
216
+ return html`<ul class="roxy-chips">
217
+ ${arr
218
+ .filter((v): v is Scalar => v !== null)
219
+ .map((v) => html`<li>${this.formatScalar(v)}</li>`)}
220
+ </ul>`;
221
+ }
222
+
184
223
  private renderTable(rows: Record<string, Json>[]): TemplateResult {
185
224
  const keys = this.collectKeys(rows);
186
- return html`<table class="roxy-table" role="table">
187
- <thead>
188
- <tr>
189
- ${keys.map((k) => html`<th>${humanize(k)}</th>`)}
190
- </tr>
191
- </thead>
192
- <tbody>
193
- ${rows.map(
194
- (row) => html`<tr>
195
- ${keys.map((k) => html`<td>${this.formatPrimitive(row[k])}</td>`)}
196
- </tr>`,
197
- )}
198
- </tbody>
199
- </table>`;
225
+ return html`<div
226
+ class="roxy-table-wrap"
227
+ role="group"
228
+ aria-label="Data table"
229
+ tabindex="0"
230
+ >
231
+ <table class="roxy-table" role="table">
232
+ <thead>
233
+ <tr>
234
+ ${keys.map((k) => html`<th>${humanize(k)}</th>`)}
235
+ </tr>
236
+ </thead>
237
+ <tbody>
238
+ ${rows.map(
239
+ (row) => html`<tr>
240
+ ${keys.map((k) => html`<td>${this.renderCell(row[k])}</td>`)}
241
+ </tr>`,
242
+ )}
243
+ </tbody>
244
+ </table>
245
+ </div>`;
200
246
  }
201
247
 
202
248
  private renderObject(obj: Record<string, Json>): TemplateResult {
@@ -209,7 +255,7 @@ export class RoxyData extends RoxyDataElement<Json> {
209
255
  titleKey !== 'summary' && typeof obj.summary === 'string'
210
256
  ? 'summary'
211
257
  : null;
212
- const rows = Object.entries(obj).filter(
258
+ const entries = Object.entries(obj).filter(
213
259
  ([k, v]) =>
214
260
  k !== titleKey &&
215
261
  k !== summaryKey &&
@@ -217,6 +263,11 @@ export class RoxyData extends RoxyDataElement<Json> {
217
263
  v !== null &&
218
264
  v !== undefined,
219
265
  );
266
+ // Scalars and primitive arrays fit the two-column rows; objects and
267
+ // object arrays promote to full-width sections so nested tables are
268
+ // not squeezed into the value column.
269
+ const rows = entries.filter(([, v]) => !isComplex(v));
270
+ const sections = entries.filter(([, v]) => isComplex(v));
220
271
 
221
272
  return html`
222
273
  ${
@@ -243,34 +294,64 @@ export class RoxyData extends RoxyDataElement<Json> {
243
294
  </dl>`
244
295
  : nothing
245
296
  }
297
+ ${sections.map(
298
+ ([k, v]) => html`<div class="roxy-section">
299
+ <h4>${humanize(k)}</h4>
300
+ ${this.renderField(v)}
301
+ </div>`,
302
+ )}
246
303
  `;
247
304
  }
248
305
 
249
306
  private renderField(value: Json): TemplateResult | string {
250
307
  if (value === null || value === undefined) return '';
251
- if (typeof value === 'string') return value;
252
- if (typeof value === 'number' || typeof value === 'boolean')
253
- return String(value);
254
- if (Array.isArray(value)) {
255
- const allPrimitive = value.every((v) =>
256
- ['string', 'number', 'boolean'].includes(typeof v),
257
- );
258
- if (allPrimitive) {
259
- return html`<ul class="roxy-chips">
260
- ${value.map((v) => html`<li>${String(v)}</li>`)}
261
- </ul>`;
262
- }
308
+ if (isPrimitive(value)) return html`${this.scalarTemplate(value)}`;
309
+ if (Array.isArray(value) && value.every(isPrimitive)) {
310
+ return this.renderChips(value as (Scalar | null)[]);
263
311
  }
264
312
  return html`<roxy-data .data=${value} .depth=${this.depth + 1}></roxy-data>`;
265
313
  }
266
314
 
267
- private formatPrimitive(value: Json | undefined): string {
315
+ private renderCell(value: Json | undefined): TemplateResult | string {
268
316
  if (value === null || value === undefined) return '';
269
- if (typeof value === 'string') return value;
270
- if (typeof value === 'number' || typeof value === 'boolean')
271
- return String(value);
272
- if (Array.isArray(value)) return value.map(String).join(', ');
273
- return JSON.stringify(value);
317
+ if (isPrimitive(value)) return this.scalarTemplate(value);
318
+ if (Array.isArray(value) && value.every(isPrimitive)) {
319
+ return (value as (Scalar | null)[])
320
+ .filter((v): v is Scalar => v !== null)
321
+ .map((v) => this.formatScalar(v))
322
+ .join(', ');
323
+ }
324
+ // Nested object or object array inside a row: recurse instead of
325
+ // stringifying to [object Object]. `bare` drops the card surface so
326
+ // the cell does not paint a card inside a cell.
327
+ return html`<roxy-data
328
+ bare
329
+ .data=${value}
330
+ .depth=${this.depth + 1}
331
+ ></roxy-data>`;
332
+ }
333
+
334
+ private scalarTemplate(value: Scalar): TemplateResult | string {
335
+ if (typeof value === 'string' && LINK_STRING.test(value)) {
336
+ return html`<a href=${value} target="_blank" rel="noopener noreferrer"
337
+ >${value}</a
338
+ >`;
339
+ }
340
+ return this.formatScalar(value);
341
+ }
342
+
343
+ private formatScalar(value: Scalar): string {
344
+ if (typeof value === 'number') {
345
+ return formatNumber(value, 2) || String(value);
346
+ }
347
+ if (typeof value === 'boolean') return value ? 'Yes' : 'No';
348
+ if (ISO_DATE.test(value)) {
349
+ const time = formatTime(value);
350
+ const date = formatDate(value);
351
+ return time ? `${date}, ${time}` : date;
352
+ }
353
+ if (ENUM_STRING.test(value)) return humanize(value.toLowerCase());
354
+ return value;
274
355
  }
275
356
 
276
357
  private collectKeys(rows: Record<string, Json>[]): string[] {
@@ -877,11 +877,11 @@ export type TransitsRequest = {
877
877
  */
878
878
  natalChart?: {
879
879
  /**
880
- * Date in YYYY-MM-DD format.
880
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
881
881
  */
882
882
  date: string;
883
883
  /**
884
- * Time in 24-hour HH:MM:SS format.
884
+ * Time in 24-hour format. Seconds are optional and default to 00 (14:30 becomes 14:30:00); a single-digit hour is zero-padded. Out-of-range values are rejected.
885
885
  */
886
886
  time: string;
887
887
  latitude: number;
@@ -2779,7 +2779,7 @@ export type BirthChartResponse = {
2779
2779
  };
2780
2780
  };
2781
2781
  /**
2782
- * Twelve classical yogas detected against this chart: Gajakesari (three-rule parashara definition), Sunapha, Anapha, Dhurdhura, Kemadruma, Chandra Mangala, Budha-Aditya, and the five Pancha Mahapurusha (Ruchaka, Bhadra, Hamsa, Malavya, Sasa). Each entry carries an `id` (matches `GET /yoga/{id}` for full glossary lookup), a `present` boolean, and classical-text `evidence` for the rule that triggered or failed.
2782
+ * Twelve classical yogas detected against this chart: Gajakesari (three-rule parashara definition), Sunapha, Anapha, Dhurdhura, Kemadruma, Chandra Mangala, Budha-Aditya, and the five Pancha Mahapurusha (Ruchaka, Bhadra, Hamsa, Malavya, Sasa). Each entry carries an `id` (matches `GET /yoga/{id}` for full glossary lookup), a `present` boolean, a `quality` (Positive, Negative, or Both = auspicious, inauspicious, or context-dependent), and classical-text `evidence` for the rule that triggered or failed.
2783
2783
  */
2784
2784
  yogas?: Array<{
2785
2785
  /**
@@ -3632,7 +3632,7 @@ export type SadhesatiRequest = {
3632
3632
 
3633
3633
  export type YogaDetectResponse = {
3634
3634
  /**
3635
- * Array of 12 detected yogas. Every entry carries a present boolean; filter on present === true for active yogas. Evidence text names the rule that triggered or failed.
3635
+ * Array of 12 detected yogas. Every entry carries a `present` boolean and a `quality` (Positive, Negative, or Both = auspicious, inauspicious, or context-dependent); filter on present === true for active yogas. Evidence text names the rule that triggered or failed.
3636
3636
  */
3637
3637
  yogas: Array<{
3638
3638
  /**
@@ -4765,11 +4765,11 @@ export type KpPlanetsIntervalResponse = {
4765
4765
 
4766
4766
  export type KpPlanetsIntervalRequest = {
4767
4767
  /**
4768
- * Start datetime in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored). With timezone 0, Z suffix is treated as UTC.
4768
+ * Start datetime in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
4769
4769
  */
4770
4770
  startDatetime: string;
4771
4771
  /**
4772
- * End datetime in ISO 8601 format. Maximum 7 days from start. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
4772
+ * End datetime in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Maximum 7 days from start. Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
4773
4773
  */
4774
4774
  endDatetime: string;
4775
4775
  /**
@@ -14341,7 +14341,7 @@ export type GetBasicPanchangResponse = GetBasicPanchangResponses[keyof GetBasicP
14341
14341
  export type GetDetailedPanchangData = {
14342
14342
  body?: {
14343
14343
  /**
14344
- * Date in YYYY-MM-DD format.
14344
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
14345
14345
  */
14346
14346
  date: string;
14347
14347
  /**
@@ -14898,7 +14898,7 @@ export type GetDetailedPanchangResponses = {
14898
14898
  */
14899
14899
  panchaka: {
14900
14900
  /**
14901
- * Whether Panchaka is in force at sunrise, the panchang day reference. Can be false while startsAt and endsAt are populated when Panchaka begins later in the day.
14901
+ * True when Panchaka is in effect on this date, whether it is already running at sunrise or begins later in the day, in which case startsAt and endsAt give the window. False only when no Panchaka touches this date.
14902
14902
  */
14903
14903
  active: boolean;
14904
14904
  /**
@@ -14915,11 +14915,11 @@ export type GetDetailedPanchangResponses = {
14915
14915
  endsAt: string;
14916
14916
  };
14917
14917
  /**
14918
- * Bhadra (Vishti Karana), the 7th movable karana, avoided for all auspicious activities. Bhadra recurs roughly every 3 to 5 days and lasts about half a tithi, so most occurrences begin after sunrise rather than at it. startsAt and endsAt report the Bhadra beginning on this date regardless of whether it is active at sunrise.
14918
+ * Bhadra (Vishti Karana), the 7th movable karana, avoided for all auspicious activities. Bhadra recurs roughly every 3 to 5 days and lasts about half a tithi. active is true whenever a Bhadra is attributed to this date; startsAt and endsAt give the window, which may end on the next calendar day.
14919
14919
  */
14920
14920
  bhadra: {
14921
14921
  /**
14922
- * Whether Bhadra (Vishti Karana) is in force at sunrise, the panchang day reference moment. Can be false while startsAt and endsAt are populated when Bhadra begins later in the day.
14922
+ * True when a Bhadra (Vishti Karana) occurs on this date, in which case startsAt and endsAt give its window. False only when no Bhadra begins on this date.
14923
14923
  */
14924
14924
  active: boolean;
14925
14925
  /**
@@ -15017,7 +15017,7 @@ export type GetDetailedPanchangResponse = GetDetailedPanchangResponses[keyof Get
15017
15017
  export type GetChoghadiyaData = {
15018
15018
  body?: {
15019
15019
  /**
15020
- * Date in YYYY-MM-DD format.
15020
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
15021
15021
  */
15022
15022
  date: string;
15023
15023
  /**
@@ -15206,7 +15206,7 @@ export type GetChoghadiyaResponse = GetChoghadiyaResponses[keyof GetChoghadiyaRe
15206
15206
  export type GetHoraData = {
15207
15207
  body?: {
15208
15208
  /**
15209
- * Date in YYYY-MM-DD format.
15209
+ * Date in YYYY-MM-DD format. A single-digit month or day is accepted and zero-padded (2026-3-5 becomes 2026-03-05). Impossible calendar dates are rejected.
15210
15210
  */
15211
15211
  date: string;
15212
15212
  /**
@@ -16684,7 +16684,7 @@ export type GetKpRulingPlanetsData = {
16684
16684
  */
16685
16685
  timezone?: number | string;
16686
16686
  /**
16687
- * ISO 8601 datetime for ruling planets. Defaults to current time. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16687
+ * ISO 8601 datetime (YYYY-MM-DDTHH:MM:SS) for ruling planets. Defaults to current time. Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16688
16688
  */
16689
16689
  datetime?: string;
16690
16690
  /**
@@ -16821,11 +16821,11 @@ export type GetKpRulingPlanetsResponse = GetKpRulingPlanetsResponses[keyof GetKp
16821
16821
  export type GetKpRulingIntervalData = {
16822
16822
  body?: {
16823
16823
  /**
16824
- * Start of the interval range in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16824
+ * Start of the interval range in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16825
16825
  */
16826
16826
  startDatetime: string;
16827
16827
  /**
16828
- * End of the interval range in ISO 8601 format. Always interpreted as local time when a non-zero timezone is provided (Z suffix is ignored).
16828
+ * End of the interval range in ISO 8601 (YYYY-MM-DDTHH:MM:SS). Interpreted as local time when a non-zero timezone is provided (a trailing Z is accepted but ignored); with timezone 0 it is UTC.
16829
16829
  */
16830
16830
  endDatetime: string;
16831
16831
  /**
@@ -21665,7 +21665,7 @@ export type CalculatePersonalDayData = {
21665
21665
  */
21666
21666
  day: number;
21667
21667
  /**
21668
- * Target date in YYYY-MM-DD format (defaults to today)
21668
+ * Target date in YYYY-MM-DD format. Defaults to today (UTC).
21669
21669
  */
21670
21670
  targetDate?: string;
21671
21671
  };
@@ -33896,7 +33896,7 @@ export type GetCrystalPairingsData = {
33896
33896
  body?: never;
33897
33897
  path: {
33898
33898
  /**
33899
- * URL-safe crystal identifier to find pairings for (e.g., "amethyst", "rose-quartz").
33899
+ * URL-safe crystal identifier to find pairings for, case-insensitive (e.g., "amethyst", "Amethyst", "rose-quartz" all resolve).
33900
33900
  */
33901
33901
  id: string;
33902
33902
  };
@@ -34855,7 +34855,7 @@ export type GetCrystalData = {
34855
34855
  body?: never;
34856
34856
  path: {
34857
34857
  /**
34858
- * URL-safe crystal identifier (e.g., "amethyst", "rose-quartz", "black-tourmaline"). Must match an entry in the database.
34858
+ * URL-safe crystal identifier, case-insensitive (e.g., "amethyst", "Amethyst", "rose-quartz" all resolve). Must match an entry in the database.
34859
34859
  */
34860
34860
  id: string;
34861
34861
  };
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/sync-version.ts. Do not edit.
2
- export const ROXY_UI_VERSION = '0.12.1';
2
+ export const ROXY_UI_VERSION = '0.12.2';