@fasl-work/caos-app-shell 0.9.1 → 0.9.3

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.
package/dist/chart.d.ts CHANGED
@@ -68,6 +68,17 @@ declare function parityRange(...arrays: readonly (readonly (number | null)[])[])
68
68
  * by significant digits has no fixed resolution, and `data-ticks-repeat` reports what it does.
69
69
  */
70
70
  declare function tickSteps(fmt: FormatOptions | undefined): number[] | undefined;
71
+ /**
72
+ * uPlot ticks a log axis at 1 to 9 times every power of ten, from a precision table that ends near 1e-22: a log axis
73
+ * that reaches below it throws "Invalid array length" after stalling the page, and the chart is never drawn (known
74
+ * shell defect 31, p-values of 1e-124 in CAOS_Contraste, 2026-10-07). Below this floor the axis ticks at powers of ten
75
+ * only (`logDecades`), each one labelled.
76
+ */
77
+ declare const LOG_TABLE_FLOOR = 1e-22;
78
+ /** The powers of ten within [min, max], thinned to at most `most` (every k-th decade, the top one kept). */
79
+ declare function logDecades(min: number, max: number, most?: number): number[];
80
+ /** The smallest positive value among columns of values and the lower end of a fixed range; Infinity if none. */
81
+ declare function smallestPositive(columns: readonly (readonly (number | null)[])[], range?: readonly [number, number] | null): number;
71
82
  /** How many tick labels repeat the label before them (empty labels apart); the gate fails a chart with any (G6). */
72
83
  declare function repeatedLabels(labels: readonly (string | null | undefined)[]): number;
73
84
  /**
@@ -98,4 +109,4 @@ declare function drawMarks(u: uPlot, marks: {
98
109
  */
99
110
  declare function UPlotChart({ x, y, series, marks, height, onCursor, onPick, parity }: UPlotChartProps): react.JSX.Element;
100
111
 
101
- export { type ChartAxis, type ChartSeries, UPlotChart, type UPlotChartProps, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
112
+ export { type ChartAxis, type ChartSeries, LOG_TABLE_FLOOR, UPlotChart, type UPlotChartProps, drawMarks, logDecades, parityRange, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
package/dist/chart.js CHANGED
@@ -37,6 +37,26 @@ function tickSteps(fmt) {
37
37
  const res = 10 ** -fmt.decimals / (fmt.percent ? 100 : 1);
38
38
  return STEPS.filter((s) => s >= res * (1 - 1e-9) && Math.abs(s / res - Math.round(s / res)) < 1e-6);
39
39
  }
40
+ var LOG_TABLE_FLOOR = 1e-22;
41
+ function logDecades(min, max, most = 8) {
42
+ if (!(min > 0) || !(max > 0) || !Number.isFinite(min) || !Number.isFinite(max) || max < min) return [];
43
+ const a = Math.ceil(Math.log10(min) - 1e-9);
44
+ const b = Math.floor(Math.log10(max) + 1e-9);
45
+ if (b < a) return [];
46
+ const step = Math.max(1, Math.ceil((b - a + 1) / most));
47
+ const out = [];
48
+ for (let k = b; k >= a; k -= step) out.unshift(Number(`1e${k}`));
49
+ return out;
50
+ }
51
+ function smallestPositive(columns, range) {
52
+ let lo = range && range[0] > 0 ? range[0] : Infinity;
53
+ for (const c of columns) for (const v of c) if (v !== null && Number.isFinite(v) && v > 0 && v < lo) lo = v;
54
+ return lo;
55
+ }
56
+ var DECADE_AXIS = {
57
+ splits: (_u, _i, min, max) => logDecades(min, max),
58
+ filter: (_u, splits) => splits
59
+ };
40
60
  function repeatedLabels(labels) {
41
61
  let n = 0;
42
62
  for (let i = 1; i < labels.length; i++) if (labels[i] && labels[i] === labels[i - 1]) n++;
@@ -110,16 +130,23 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
110
130
  const xs = useMemo(() => order ? order.map((i) => x.values[i]) : x.values, [order, x.values]);
111
131
  const ys = useMemo(() => series.map((s) => order ? order.map((i) => s.values[i] ?? null) : s.values), [order, series]);
112
132
  const range = useMemo(() => parity ? parityRange(xs, ...ys) : null, [parity, xs, ys]);
133
+ const tiny = useMemo(
134
+ () => ({
135
+ x: Boolean(x.log) && smallestPositive([xs], range) < LOG_TABLE_FLOOR,
136
+ y: Boolean(y.log) && smallestPositive(ys, range ?? y.range) < LOG_TABLE_FLOOR
137
+ }),
138
+ [x.log, y.log, xs, ys, range, y.range]
139
+ );
113
140
  const xLabel = pick(x.label, lang);
114
141
  const yLabel = pick(y.label, lang);
115
142
  const xUnit = x.unit ? pick(x.unit, lang) : "";
116
143
  const yUnit = y.unit ? pick(y.unit, lang) : "";
117
144
  const structure = JSON.stringify({
118
145
  s: series.map((s, i) => [pick(s.label, lang), seriesStyle(s, i), s.width ?? 2, s.mode ?? "line"]),
119
- x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null],
146
+ x: [xLabel, xUnit, Boolean(x.time), Boolean(x.log), x.format ?? null, tiny.x],
120
147
  p: range,
121
148
  pick: Boolean(onPick),
122
- y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null],
149
+ y: [yLabel, yUnit, Boolean(y.log), y.range ?? null, y.format ?? null, tiny.y],
123
150
  m: (marks ?? []).map((m) => [m.x, pick(m.label, lang)]),
124
151
  theme,
125
152
  lang,
@@ -168,7 +195,8 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
168
195
  label: xUnit ? `${xLabel} (${xUnit})` : xLabel,
169
196
  labelFont: font,
170
197
  values: x.time ? void 0 : tick(x.format, "x"),
171
- ...xSteps ? { incrs: xSteps } : {}
198
+ ...xSteps ? { incrs: xSteps } : {},
199
+ ...tiny.x ? DECADE_AXIS : {}
172
200
  },
173
201
  {
174
202
  stroke: fg,
@@ -179,6 +207,7 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
179
207
  labelFont: font,
180
208
  values: tick(y.format, "y"),
181
209
  ...ySteps ? { incrs: ySteps } : {},
210
+ ...tiny.y ? DECADE_AXIS : {},
182
211
  // Sized to the longest tick label. uPlot draws in device pixels with a font scaled by its pixel ratio, so the
183
212
  // label is measured in that font and divided back (measuring the unscaled font and dividing by the ratio
184
213
  // made the axis too narrow on every high-density screen; the gate runs at ratio 1 and never saw it).
@@ -282,4 +311,4 @@ function UPlotChart({ x, y, series, marks, height = 280, onCursor, onPick, parit
282
311
  );
283
312
  }
284
313
 
285
- export { UPlotChart, drawMarks, parityRange, repeatedLabels, seriesStyle, sortOrder, tickSteps };
314
+ export { LOG_TABLE_FLOOR, UPlotChart, drawMarks, logDecades, parityRange, repeatedLabels, seriesStyle, smallestPositive, sortOrder, tickSteps };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fasl-work/caos-app-shell",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Shared web-app shell, workbench primitives, content primitives, design system and measured gate for the CAOS / Faena public apps (ADR-0016, ADR-0017, ADR-0071).",
5
5
  "license": "MIT",
6
6
  "author": "Felipe Santibáñez-Leal",
package/styles.css CHANGED
@@ -493,7 +493,9 @@ svg.lucide { flex: none; }
493
493
  .caos-wb-rail { display: flex; flex-direction: column; gap: var(--space-3); min-height: 0; overflow-y: auto; overflow-x: hidden; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 0.85rem; }
494
494
  .caos-wb-rail-head { display: flex; flex-direction: column; gap: var(--space-3); padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border); }
495
495
  .caos-wb-rail-panel { display: flex; flex-direction: column; gap: 0.8rem; min-height: 0; }
496
- .caos-wb-rail label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
496
+ /* A labelled control in the rail stacks its text above it; a knob's head keeps its own row, the value at the right
497
+ (until 0.9.3 this rule outranked `.caos-knob-head` and stacked every knob, a line per knob; CAOS_APP_SHELL#77). */
498
+ .caos-wb-rail label:not(.caos-knob-head) { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.8rem; color: var(--color-fg-subtle); }
497
499
  .caos-wb-rail .select, .caos-wb-rail .range { width: 100%; }
498
500
  /* The variants wrap in the rail instead of scrolling. */
499
501
  .caos-wb-rail .caos-variant-bar { margin-bottom: 0; }