opencode-extended-sidebar 0.2.90
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/.oesignore +17 -0
- package/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/oes.json +11 -0
- package/package.json +100 -0
- package/scripts/install-git-hooks.mjs +29 -0
- package/src/pware.oc.core/constants/index.ts +18 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +13 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -0
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -0
- package/src/pware.oc.core/git/index.ts +7 -0
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -0
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -0
- package/src/pware.oc.core/index.ts +18 -0
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -0
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -0
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -0
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -0
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -0
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -0
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -0
- package/src/pware.oc.core/pware.oc.core.oes.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -0
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -0
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +175 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -0
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -0
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -0
- package/src/pware.oc.omo/constants/index.ts +15 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -0
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -0
- package/src/pware.oc.omo/index.ts +7 -0
- package/src/pware.oc.omo/resolver/index.ts +19 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +361 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -0
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -0
- package/src/pware.oc.opencode/constants/index.ts +10 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -0
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -0
- package/src/pware.oc.opencode/index.ts +8 -0
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -0
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -0
- package/src/pware.oc.opencode/resolver/index.ts +170 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +162 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +185 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +211 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +26 -0
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +236 -0
- package/src/pware.oc.perf/index.ts +14 -0
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -0
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -0
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +927 -0
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -0
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -0
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -0
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +462 -0
- package/src/pware.oc.runtime/index.ts +13 -0
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +79 -0
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +253 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +95 -0
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +129 -0
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -0
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +43 -0
- package/src/pware.oc.runtime/resolver/index.ts +224 -0
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -0
- package/src/pware.oc.ui/index.ts +12 -0
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +339 -0
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -0
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -0
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -0
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -0
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +526 -0
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1629 -0
- package/src/pware.oc.ui.tsx +98 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { plot } from "asciichart"
|
|
2
|
+
import { chart, renderToString, sparkBar, sparkHistogram, sparkGauge, sparkDonut } from "@crafter/charts"
|
|
3
|
+
import { quantileSorted, sampleStandardDeviation } from "simple-statistics"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pure series-pipeline helpers for the Perf charts.
|
|
7
|
+
*
|
|
8
|
+
* Stateless and self-contained: no TUI, host, db, or `core` imports — every
|
|
9
|
+
* helper turns a plain array (or string) into a plain array (or string), so
|
|
10
|
+
* each is unit-testable in isolation and safe to run on the render path.
|
|
11
|
+
*
|
|
12
|
+
* Todo 3 builds the chart/stat render helpers (`asciiTrend`, `shareBar`, …)
|
|
13
|
+
* on top of these in this same file.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fill nulls so the series is dense: leading nulls become the first known
|
|
18
|
+
* value, trailing nulls become the last known value, and interior runs are
|
|
19
|
+
* linearly interpolated between the nearest known neighbours on each side.
|
|
20
|
+
* Empty input or all-null returns `[]`.
|
|
21
|
+
*/
|
|
22
|
+
export function interpolateSeries(values: Array<number | null>): number[] {
|
|
23
|
+
if (values.length === 0) return []
|
|
24
|
+
const first = values.findIndex((v) => v !== null)
|
|
25
|
+
if (first < 0) return []
|
|
26
|
+
|
|
27
|
+
const out = new Array<number>(values.length)
|
|
28
|
+
let prevIdx = first
|
|
29
|
+
let prevVal = values[first]!
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < first; i += 1) out[i] = prevVal
|
|
32
|
+
out[first] = prevVal
|
|
33
|
+
|
|
34
|
+
for (let i = first + 1; i < values.length; i += 1) {
|
|
35
|
+
const v = values[i]
|
|
36
|
+
if (v === null) continue
|
|
37
|
+
for (let j = prevIdx + 1; j < i; j += 1) {
|
|
38
|
+
out[j] = prevVal + ((v - prevVal) * (j - prevIdx)) / (i - prevIdx)
|
|
39
|
+
}
|
|
40
|
+
out[i] = v
|
|
41
|
+
prevIdx = i
|
|
42
|
+
prevVal = v
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let i = prevIdx + 1; i < values.length; i += 1) out[i] = prevVal
|
|
46
|
+
|
|
47
|
+
return out
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Centered moving average over an odd window. Edge-clamped: at the ends the
|
|
52
|
+
* mean uses only the neighbours that exist, so the first and last points are
|
|
53
|
+
* averaged over fewer values. Length shorter than `window` returns a shallow
|
|
54
|
+
* copy unchanged.
|
|
55
|
+
*/
|
|
56
|
+
export function smoothSeries(values: number[], window = 3): number[] {
|
|
57
|
+
if (values.length < window) return [...values]
|
|
58
|
+
const half = Math.floor(window / 2)
|
|
59
|
+
const out = new Array<number>(values.length)
|
|
60
|
+
for (let i = 0; i < values.length; i += 1) {
|
|
61
|
+
const lo = Math.max(0, i - half)
|
|
62
|
+
const hi = Math.min(values.length - 1, i + half)
|
|
63
|
+
let sum = 0
|
|
64
|
+
for (let j = lo; j <= hi; j += 1) sum += values[j]!
|
|
65
|
+
out[i] = sum / (hi - lo + 1)
|
|
66
|
+
}
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Bucket-average down to at most `width` points. When the series already fits
|
|
72
|
+
* (`values.length <= width`) a copy is returned unchanged. Each output point is
|
|
73
|
+
* the mean of its bucket; empty buckets (possible when `width` is 0) are never
|
|
74
|
+
* emitted.
|
|
75
|
+
*/
|
|
76
|
+
export function downsampleAvg(values: number[], width: number): number[] {
|
|
77
|
+
if (values.length <= width || width < 1) return [...values]
|
|
78
|
+
const size = Math.ceil(values.length / width)
|
|
79
|
+
const out: number[] = []
|
|
80
|
+
for (let i = 0; i < values.length; i += size) {
|
|
81
|
+
const end = Math.min(i + size, values.length)
|
|
82
|
+
let sum = 0
|
|
83
|
+
for (let j = i; j < end; j += 1) sum += values[j]!
|
|
84
|
+
out.push(sum / (end - i))
|
|
85
|
+
}
|
|
86
|
+
return out
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Strip ANSI SGR sequences (`\x1b[31m`, `\x1b[0m`, …), leaving the plain text.
|
|
91
|
+
* OpenTUI renders colours via props, never escape codes.
|
|
92
|
+
*/
|
|
93
|
+
export function stripAnsi(s: string): string {
|
|
94
|
+
return s.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Chart y-axis label: an integer for `|x| ≥ 10`, else one decimal. Drops
|
|
99
|
+
* asciichart's default `toFixed(2)` trailing zeros (e.g. `4652.00` → `4652`).
|
|
100
|
+
*/
|
|
101
|
+
export function axisLabel(x: number): string {
|
|
102
|
+
return Math.abs(x) >= 10 ? String(Math.round(x)) : x.toFixed(1)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Plain-ASCII trend line for a series: interpolate nulls → smooth (window 3)
|
|
107
|
+
* → downsample to at most `width` points → asciichart `plot`. No `colors`
|
|
108
|
+
* config is passed, so the output is ANSI-free. Y-axis labels use `axisLabel`
|
|
109
|
+
* (rounded) instead of asciichart's `toFixed(2)`. Empty or all-null input
|
|
110
|
+
* returns `""`. Optional `min` / `max` pin the y-domain (the OES dialog
|
|
111
|
+
* charts leave them unset so a high flat series does not draw a dummy zero
|
|
112
|
+
* baseline).
|
|
113
|
+
*
|
|
114
|
+
* Each output row starts with an 11-column min/max label plus asciichart's
|
|
115
|
+
* 3-column offset; joined length is plot-points + {@link ASCII_TREND_AXIS_COLS}.
|
|
116
|
+
* Callers that have a hard line budget (the dialog) must pass
|
|
117
|
+
* `asciiTrendPlotWidth(lineBudget)` as `width`, or the labels wrap onto the
|
|
118
|
+
* next row and read as a second series.
|
|
119
|
+
*/
|
|
120
|
+
export const ASCII_TREND_AXIS_COLS = 13
|
|
121
|
+
|
|
122
|
+
/** Plot-point count that keeps an asciiTrend row within `lineWidth` columns. */
|
|
123
|
+
export function asciiTrendPlotWidth(lineWidth: number): number {
|
|
124
|
+
return Math.max(8, Math.floor(lineWidth) - ASCII_TREND_AXIS_COLS)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function asciiTrend(
|
|
128
|
+
values: Array<number | null>,
|
|
129
|
+
opts: { width: number; height?: number; min?: number; max?: number },
|
|
130
|
+
): string {
|
|
131
|
+
const dense = interpolateSeries(values)
|
|
132
|
+
if (dense.length === 0) return ""
|
|
133
|
+
const smoothed = smoothSeries(dense, 3)
|
|
134
|
+
const ds = downsampleAvg(smoothed, Math.max(1, opts.width))
|
|
135
|
+
const pad = " " // 11 spaces — asciichart's label field, keeps the axis aligned
|
|
136
|
+
return plot(ds, {
|
|
137
|
+
height: opts.height ?? 3,
|
|
138
|
+
format: (x: number) => (pad + axisLabel(x)).slice(-pad.length),
|
|
139
|
+
...(opts.min !== undefined ? { min: opts.min } : {}),
|
|
140
|
+
...(opts.max !== undefined ? { max: opts.max } : {}),
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Single horizontal share bar (`█`-filled) for a fraction in `[0, 1]`.
|
|
146
|
+
* `null` renders an empty bar. Output is plain (no colour config passed).
|
|
147
|
+
*/
|
|
148
|
+
export function shareBar(share: number | null, width: number): string {
|
|
149
|
+
return sparkBar(share ?? 0, 1, { width })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* One summary line of a distribution: `p50`/`p95`/`p99` via `quantileSorted`
|
|
154
|
+
* and the sample standard deviation, each formatted through the injected
|
|
155
|
+
* `fmt`. Fewer than two finite values renders `${label} —`.
|
|
156
|
+
*/
|
|
157
|
+
export function perfStatLine(
|
|
158
|
+
label: string,
|
|
159
|
+
values: Array<number | null>,
|
|
160
|
+
fmt: (n: number) => string,
|
|
161
|
+
): string {
|
|
162
|
+
const known = values.filter(
|
|
163
|
+
(v): v is number => v != null && Number.isFinite(v),
|
|
164
|
+
)
|
|
165
|
+
if (known.length < 2) return `${label} —`
|
|
166
|
+
const sorted = [...known].sort((a, b) => a - b)
|
|
167
|
+
const p50 = quantileSorted(sorted, 0.5)
|
|
168
|
+
const p95 = quantileSorted(sorted, 0.95)
|
|
169
|
+
const p99 = quantileSorted(sorted, 0.99)
|
|
170
|
+
const sd = sampleStandardDeviation(known)
|
|
171
|
+
return `${label} p50 ${fmt(p50)} · p95 ${fmt(p95)} · p99 ${fmt(p99)} · σ ${fmt(sd)}`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Binned distribution histogram of the known (finite) values, with per-bin
|
|
176
|
+
* counts. Fewer than one known value returns `""`. Plain (no colour config).
|
|
177
|
+
*/
|
|
178
|
+
export function waitHistogram(
|
|
179
|
+
values: Array<number | null>,
|
|
180
|
+
opts?: { width?: number; height?: number; bins?: number },
|
|
181
|
+
): string {
|
|
182
|
+
const known = values.filter(
|
|
183
|
+
(v): v is number => v != null && Number.isFinite(v),
|
|
184
|
+
)
|
|
185
|
+
if (known.length < 1) return ""
|
|
186
|
+
return sparkHistogram(known, {
|
|
187
|
+
width: opts?.width ?? 60,
|
|
188
|
+
height: opts?.height ?? 8,
|
|
189
|
+
bins: opts?.bins ?? 10,
|
|
190
|
+
showCounts: true,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Radial gauge for a fraction in `[0, 1]`. `sparkGauge` colours internally;
|
|
196
|
+
* `stripAnsi` removes the escape codes so the returned string is ANSI-free.
|
|
197
|
+
*/
|
|
198
|
+
export function shareGauge(
|
|
199
|
+
share: number | null,
|
|
200
|
+
opts?: { width?: number; label?: string },
|
|
201
|
+
): string {
|
|
202
|
+
return stripAnsi(
|
|
203
|
+
sparkGauge(share ?? 0, 1, { width: opts?.width ?? 40, label: opts?.label }),
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Donut for a fraction in `[0, 1]`, ANSI-stripped (see `shareGauge`).
|
|
209
|
+
*/
|
|
210
|
+
export function shareDonut(
|
|
211
|
+
share: number | null,
|
|
212
|
+
opts?: { label?: string },
|
|
213
|
+
): string {
|
|
214
|
+
return stripAnsi(sparkDonut(share ?? 0, 1, { label: opts?.label }))
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Box line sparkline for the OES status bar's live token rate. Returns one
|
|
219
|
+
* string per row (empty input → `[]`), `height` rows tall (default 2). The
|
|
220
|
+
* series is first bucket-averaged to `width` (`downsampleAvg`) then smoothed
|
|
221
|
+
* (window 3) so a spiky raw rate renders as a continuous curve instead of an
|
|
222
|
+
* aliased sawtooth. Renders through `@crafter/charts` `chart().line()` (box
|
|
223
|
+
* charset), pins the y-domain to `[0, max]` (rates are never negative),
|
|
224
|
+
* and drops the 8-column y-axis gutter so the line spans the full requested
|
|
225
|
+
* width — the TUI colours it via the `fg` prop, no ANSI.
|
|
226
|
+
*/
|
|
227
|
+
export function rateSparkline(
|
|
228
|
+
values: number[],
|
|
229
|
+
opts: { width: number; height?: number; charset?: "braille" | "block" | "ascii" | "box" },
|
|
230
|
+
): string[] {
|
|
231
|
+
if (values.length === 0) return []
|
|
232
|
+
const height = Math.max(2, Math.round(opts.height ?? 2))
|
|
233
|
+
const downsampled = downsampleAvg(values, opts.width)
|
|
234
|
+
const smoothed = smoothSeries(downsampled, 3)
|
|
235
|
+
const max = Math.max(1, ...smoothed)
|
|
236
|
+
const rows = smoothed.map((v, i) => ({ x: i, y: v }))
|
|
237
|
+
const out = renderToString(
|
|
238
|
+
chart({ width: opts.width + 8, height, charset: opts.charset ?? "braille" })
|
|
239
|
+
.data(rows, { xKey: "x" })
|
|
240
|
+
.line({ key: "y" })
|
|
241
|
+
.yDomain([0, max]),
|
|
242
|
+
)
|
|
243
|
+
return out.split("\n").map((line) => line.slice(8))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* One ASCII trend (`asciiTrend` / asciichart) per realtime series for the
|
|
248
|
+
* fullscreen Perf modal. Braille is reserved for the compact sidebar sparkline
|
|
249
|
+
* — `@crafter/charts` line marks always paint a braille canvas (even with
|
|
250
|
+
* `charset: "ascii"`), which reads as scattered dots at dialog size. Callers
|
|
251
|
+
* pick the body height (`RT_DIALOG_CHART_ROWS`). Each entry is independent;
|
|
252
|
+
* empty series are skipped. Y-domain follows the series (not pinned to
|
|
253
|
+
* `[0, max]`) so a high flat reading does not draw a dummy zero baseline that
|
|
254
|
+
* reads as a second series. `opts.width` is the **line budget** (dialog inner
|
|
255
|
+
* columns): plot points are `asciiTrendPlotWidth(width)` so the min/max labels
|
|
256
|
+
* at the start of each row stay on that row. Output is clipped to `width` as
|
|
257
|
+
* a last wrap guard. ANSI-free; the TUI colours it via the `fg` prop.
|
|
258
|
+
*/
|
|
259
|
+
export function realtimeSeriesLines(
|
|
260
|
+
series: { key: string; label: string; unit: string; values: number[] }[],
|
|
261
|
+
opts: { width: number; height: number },
|
|
262
|
+
): { key: string; label: string; unit: string; lines: string[] }[] {
|
|
263
|
+
const out: { key: string; label: string; unit: string; lines: string[] }[] = []
|
|
264
|
+
const lineWidth = Math.max(8, Math.floor(opts.width))
|
|
265
|
+
const plotWidth = asciiTrendPlotWidth(lineWidth)
|
|
266
|
+
const plotHeight = Math.max(1, opts.height - 1)
|
|
267
|
+
for (const s of series) {
|
|
268
|
+
if (s.values.length === 0) continue
|
|
269
|
+
const rendered = asciiTrend(s.values, {
|
|
270
|
+
width: plotWidth,
|
|
271
|
+
height: plotHeight,
|
|
272
|
+
})
|
|
273
|
+
const lines = rendered === "" ? [] : rendered.split("\n")
|
|
274
|
+
while (lines.length < opts.height) lines.push("")
|
|
275
|
+
if (lines.length > opts.height) lines.length = opts.height
|
|
276
|
+
out.push({
|
|
277
|
+
key: s.key,
|
|
278
|
+
label: s.label,
|
|
279
|
+
unit: s.unit,
|
|
280
|
+
lines: lines.map((line) => (line.length <= lineWidth ? line : line.slice(0, lineWidth))),
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
return out
|
|
284
|
+
}
|