@lotics/ui 44.9.0 → 44.10.0
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/MIGRATION.md +27 -0
- package/docs/ai_patterns.md +10 -1
- package/docs/catalog.md +12 -4
- package/package.json +1 -1
- package/src/agent_run_pane.tsx +56 -10
- package/src/ledger.tsx +8 -1
- package/src/matrix.tsx +17 -12
- package/src/table_fit.ts +15 -5
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,33 @@ Breaking changes, newest first — normally per major, plus the rare minor that
|
|
|
4
4
|
anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
|
|
5
5
|
this file exists only to move an app from one release to the next.
|
|
6
6
|
|
|
7
|
+
## 44.9.1 — a pivot's figures align, and a ledger row stops losing its own name
|
|
8
|
+
|
|
9
|
+
No API is removed and nothing fails to compile. Three renders change.
|
|
10
|
+
|
|
11
|
+
**`Matrix` right-aligns its figures instead of centring them.** Every value, column
|
|
12
|
+
header and total was `align="center"` while the cells also set `tabular` — and tabular
|
|
13
|
+
figures exist so digits line up in a column, which centring throws away. Measured on a
|
|
14
|
+
real pivot: centred, 24 figures painted across **14 distinct right edges**; right-aligned,
|
|
15
|
+
**12** — exactly one per column. No `align` prop came with this: a matrix is one measure
|
|
16
|
+
across two dimensions, so its cells cannot want different alignments, and a prop for a
|
|
17
|
+
centred case nobody has is surface without a consumer.
|
|
18
|
+
|
|
19
|
+
**`Matrix` row labels carry default ink, not `muted`.** A pivot whose rows are entities
|
|
20
|
+
rendered every entity name a step quieter than the numbers about it, inverting how the
|
|
21
|
+
grid is read — you find the row by its name, then scan across.
|
|
22
|
+
|
|
23
|
+
**`MATRIX_COL_GAP` is exported.** It was the literal `3` in four places, so a caller
|
|
24
|
+
sizing its own column budget had to hard-code it and could drift from what the grid draws.
|
|
25
|
+
|
|
26
|
+
**A `LedgerRow`'s `meta` yields before its label, and by 3×.** Only the label carried a
|
|
27
|
+
`flexShrink`, and RN-web resolves the unstyled caption to `1` as well — so under pressure
|
|
28
|
+
both shrank proportionally, and because a caption is usually the longer string, the
|
|
29
|
+
SUBORDINATE text ended up wider than the row's identity. Measured in a 180px row: before,
|
|
30
|
+
the label kept 31px of the 77 it needs while the caption kept 41 of 103; after, the label
|
|
31
|
+
keeps 55 and the caption 16. Any caller that dropped `meta` at small widths to dodge this
|
|
32
|
+
can stop.
|
|
33
|
+
|
|
7
34
|
## 44.8.0 — a value column gets its own floor, and eight defaults stop fighting their own docs
|
|
8
35
|
|
|
9
36
|
No API is removed. Three of these change what an existing screen renders, so read them even
|
package/docs/ai_patterns.md
CHANGED
|
@@ -464,12 +464,21 @@ Reach for it whenever an agent run happens inside a dialog — which is nearly a
|
|
|
464
464
|
const run = useAgentRun("intake"); // @lotics/app-sdk
|
|
465
465
|
<AgentRunScope> {/* wraps the Dialog, like ClarifyWizardScope */}
|
|
466
466
|
<Dialog …>
|
|
467
|
-
<AgentRunPane run={run} onCancel={back} />
|
|
467
|
+
<AgentRunPane run={run} onCancel={back} onLanding={landed} /> {/* content */}
|
|
468
468
|
<DialogFooter><AgentRunActions run={run} onStop={back} /></DialogFooter> {/* actions */}
|
|
469
469
|
</Dialog>
|
|
470
470
|
</AgentRunScope>
|
|
471
471
|
```
|
|
472
472
|
|
|
473
|
+
**Pass `onLanding` if the run's RESULT drives anything.** The pane owns the `answerChoice` call, so
|
|
474
|
+
after a question it is the only thing holding how the answered leg ended — your own `run()` promise
|
|
475
|
+
already resolved, with `parked`, and never resolves again. Give `onLanding` the same handler you
|
|
476
|
+
give `run()`: it is typed from your run (no cast), and it re-fires if answering leads to a follow-up
|
|
477
|
+
ask. Omit it and an answered run completes with the host none the wiser — the transcript reads
|
|
478
|
+
"done", your review step never opens, and the paid read is discarded. A host that instead reads its
|
|
479
|
+
result off `status`/`output` does not need it, but is then re-deriving from a render what it was
|
|
480
|
+
handed directly.
|
|
481
|
+
|
|
473
482
|
**The parked question REPLACES the feed — that is the contract, not a style.** The run is blocked
|
|
474
483
|
on the answer, so the question is the only thing to act on; it gets the dialog's own scroller and
|
|
475
484
|
its actions sit outside it. Stacked under the transcript in an unscrollable box — the arrangement
|
package/docs/catalog.md
CHANGED
|
@@ -1407,8 +1407,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1407
1407
|
- **`table`** — `Table` + `TableRow` + `TableCell`: the paginated high-volume register
|
|
1408
1408
|
(columns defined once, `sortLabels` localizable; rows are `PressableRow`-based).
|
|
1409
1409
|
CONTAINER-RESPONSIVE with no prop (measures itself, like `Breakdown`/`DetailTable`): when
|
|
1410
|
-
the width can't fit every column it hides columns by `TableColumn.priority` (
|
|
1411
|
-
|
|
1410
|
+
the width can't fit every column it hides columns by `TableColumn.priority` (**`1` = highest,
|
|
1411
|
+
as in P1/P2/P3** — the smallest number is the last to go; default = column order, rightmost
|
|
1412
|
+
first; column 0 — the identity — never drops), and
|
|
1412
1413
|
below the two-column floor every row STACKS: the identity cell (column 0) HEADS the pile
|
|
1413
1414
|
unlabelled — its column name would only restate the value — and each REMAINING cell renders
|
|
1414
1415
|
as a `DetailRow`-spread line (muted label left, value at the right edge — one vocabulary with
|
|
@@ -1705,7 +1706,11 @@ component rather than showing it at zero.
|
|
|
1705
1706
|
- **`matrix`** — `Matrix`: the PIVOT cross-tab — band-compound `Matrix` root +
|
|
1706
1707
|
`Matrix.Header` (corner + axis labels) + `Matrix.Grid` (`display` number|heat|both — the
|
|
1707
1708
|
cells, pressable, the value IN the cell) + `Matrix.Totals` (row + column + grand) +
|
|
1708
|
-
`Matrix.Legend`. Pick over `Heatmap` when the NUMBER and totals matter.
|
|
1709
|
+
`Matrix.Legend`. Pick over `Heatmap` when the NUMBER and totals matter. Figures are
|
|
1710
|
+
RIGHT-aligned — they are `tabular`, and centring them defeats the aligned column that is
|
|
1711
|
+
the reason to read a pivot at all — and row labels carry default ink, because a row's
|
|
1712
|
+
label is its identity, not chrome. `MATRIX_COL_GAP` is exported for a caller budgeting
|
|
1713
|
+
its own column widths.
|
|
1709
1714
|
- **`matrix_totals`** — the data layer for `Matrix`: `matrixTotals` (the cross-tab
|
|
1710
1715
|
aggregation) + `MatrixAxisItem` / `MatrixCellRef` / `MatrixTotalsResult`; React-free, so a
|
|
1711
1716
|
KPI can be driven off the same numbers the grid shows.
|
|
@@ -2139,7 +2144,10 @@ component rather than showing it at zero.
|
|
|
2139
2144
|
works and, the moment it asks, renders the question IN PLACE OF the feed — in the dialog's own
|
|
2140
2145
|
scroller, with the wizard's verbs pinned in the `DialogFooter` via `AgentRunActions`. Replacing
|
|
2141
2146
|
rather than stacking is the CONTRACT: stacked under the transcript a multi-question ask clips
|
|
2142
|
-
its own Submit and strands a live `awaiting_input` run. `
|
|
2147
|
+
its own Submit and strands a live `awaiting_input` run. `onLanding` hands back how the ANSWERED
|
|
2148
|
+
leg ended — the pane makes that call, so without it the continuation's outcome is lost and a host
|
|
2149
|
+
waiting on its own `run()` promise waits forever (that promise already resolved `parked`). A
|
|
2150
|
+
refused answer keeps the question up and shows why, rather than a dead Submit. `run` is a structural shape
|
|
2143
2151
|
(`AgentRunLike`), so `useAgentRun()` satisfies it with no `@lotics/app-sdk` dependency and a
|
|
2144
2152
|
mock satisfies it with no backend — `AgentRunQuestion` names the question shape so a template
|
|
2145
2153
|
or test DECLARES one rather than mapping from `ClarifyWizardQuestion` (an option is a label +
|
package/package.json
CHANGED
package/src/agent_run_pane.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useCallback, useMemo, type ReactNode } from "react";
|
|
1
|
+
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
3
|
import { AgentRun, type AgentRunProps } from "./agent_run";
|
|
4
4
|
import { Button } from "./button";
|
|
5
|
+
import { Text } from "./text";
|
|
5
6
|
import { useLoticsLocale } from "./locale";
|
|
6
7
|
import type { AgentUIPart } from "./agent_transform";
|
|
7
8
|
import { ClarifyWizard, ClarifyWizardActions, ClarifyWizardScope, type ClarifyWizardAnswer } from "./clarify_wizard";
|
|
@@ -31,12 +32,20 @@ export interface AgentRunQuestion {
|
|
|
31
32
|
allow_custom?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export interface AgentRunLike {
|
|
35
|
+
export interface AgentRunLike<TLanding = unknown> {
|
|
35
36
|
status: "idle" | "streaming" | "awaiting_input" | "completed" | "error";
|
|
36
37
|
parts: readonly AgentUIPart[];
|
|
37
38
|
/** Non-null exactly while parked. */
|
|
38
39
|
pendingChoice: { questions: AgentRunQuestion[] } | null;
|
|
39
|
-
|
|
40
|
+
/**
|
|
41
|
+
* GENERIC in what it resolves, and that is the whole point of the parameter.
|
|
42
|
+
* A run leg reports HOW it ended — settled with an output, parked again on a
|
|
43
|
+
* follow-up, failed, aborted — and this pane is the thing that calls it, so it
|
|
44
|
+
* is the only thing holding that answer. Naming the type would mean importing
|
|
45
|
+
* `@lotics/app-sdk` and breaking the one-way boundary above; inferring it from
|
|
46
|
+
* the caller's own run costs nothing and hands `onLanding` back fully typed.
|
|
47
|
+
*/
|
|
48
|
+
answerChoice: (answers: { value: string; custom: boolean }[]) => Promise<TLanding>;
|
|
40
49
|
/**
|
|
41
50
|
* STOP the run — server-side, not just locally. `useAgentRun().cancel` is
|
|
42
51
|
* exactly this; do NOT pass its `abort`, which detaches the listener and
|
|
@@ -61,12 +70,30 @@ export function AgentRunScope({ children }: { children: ReactNode }) {
|
|
|
61
70
|
return <ClarifyWizardScope>{children}</ClarifyWizardScope>;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
export interface AgentRunPaneProps {
|
|
65
|
-
run: AgentRunLike
|
|
73
|
+
export interface AgentRunPaneProps<TLanding = unknown> {
|
|
74
|
+
run: AgentRunLike<TLanding>;
|
|
66
75
|
labelForCall?: AgentRunProps["labelForCall"];
|
|
67
76
|
renderToolOutput?: AgentRunProps["renderToolOutput"];
|
|
68
77
|
/** Abandon a parked question — the run is left for the operator to retry. */
|
|
69
78
|
onCancel: () => void;
|
|
79
|
+
/**
|
|
80
|
+
* How the ANSWERED leg ended. Required for correctness in any host that acts
|
|
81
|
+
* on a run's result, because this pane owns the `answerChoice` call and the
|
|
82
|
+
* caller's own `run()` promise already resolved — with `parked` — and will
|
|
83
|
+
* never resolve again.
|
|
84
|
+
*
|
|
85
|
+
* Without it the continuation's outcome had nowhere to go: the pane discarded
|
|
86
|
+
* it, and a host that had been told a landing is how you learn an outcome sat
|
|
87
|
+
* on a finished transcript with nothing to advance to. That shipped, and cost
|
|
88
|
+
* a paid extraction every time an agent asked a question.
|
|
89
|
+
*
|
|
90
|
+
* Re-fires for a follow-up ask: answering can land `parked` again.
|
|
91
|
+
*
|
|
92
|
+
* A host reading its result off hook state instead does not need this — but it
|
|
93
|
+
* is then re-deriving from a render what it was handed directly, which is the
|
|
94
|
+
* pattern the landing union exists to retire.
|
|
95
|
+
*/
|
|
96
|
+
onLanding?: (landing: TLanding) => void;
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
/**
|
|
@@ -80,8 +107,9 @@ export interface AgentRunPaneProps {
|
|
|
80
107
|
* dialog's height and clipped its own Submit. Every app rebuilt this arrangement
|
|
81
108
|
* by hand and it only had to be got wrong once.
|
|
82
109
|
*/
|
|
83
|
-
export function AgentRunPane(props: AgentRunPaneProps) {
|
|
84
|
-
const { run, labelForCall, renderToolOutput, onCancel } = props;
|
|
110
|
+
export function AgentRunPane<TLanding>(props: AgentRunPaneProps<TLanding>) {
|
|
111
|
+
const { run, labelForCall, renderToolOutput, onCancel, onLanding } = props;
|
|
112
|
+
const loc = useLoticsLocale().agentRun;
|
|
85
113
|
// The DIALOG's gutter, not this pane's own container measurement. Asking
|
|
86
114
|
// `useContainerSize()` here answered "small" for a 760px dialog while the
|
|
87
115
|
// header, scroll area and footer asked the SCREEN and answered "not small" —
|
|
@@ -101,17 +129,35 @@ export function AgentRunPane(props: AgentRunPaneProps) {
|
|
|
101
129
|
})),
|
|
102
130
|
[pending],
|
|
103
131
|
);
|
|
132
|
+
// A REFUSED answer (400 invalid, 409 raced cancel) leaves the run parked and
|
|
133
|
+
// still answerable, so the wizard stays up and the reason goes above it. It
|
|
134
|
+
// used to be a bare `void`, which made that an unhandled rejection: the button
|
|
135
|
+
// did nothing, the question stayed, and nothing said why.
|
|
136
|
+
const [answerError, setAnswerError] = useState<string | null>(null);
|
|
104
137
|
const submit = useCallback(
|
|
105
138
|
(answers: ClarifyWizardAnswer[]) => {
|
|
106
|
-
|
|
139
|
+
setAnswerError(null);
|
|
140
|
+
run.answerChoice(answers.map((a) => ({ value: a.value, custom: a.custom }))).then(
|
|
141
|
+
(landing) => onLanding?.(landing),
|
|
142
|
+
// A rejection is always an `Error` on the SDK's path, but a promise can
|
|
143
|
+
// carry anything, so the fallback is the localized label rather than
|
|
144
|
+
// whatever `String()` makes of a non-Error — "[object Object]" is not a
|
|
145
|
+
// thing to show an operator who is mid-answer.
|
|
146
|
+
(e: unknown) => setAnswerError(e instanceof Error ? e.message : loc.error),
|
|
147
|
+
);
|
|
107
148
|
},
|
|
108
|
-
[run],
|
|
149
|
+
[run, onLanding, loc],
|
|
109
150
|
);
|
|
110
151
|
|
|
111
152
|
if (pending) {
|
|
112
153
|
return (
|
|
113
154
|
<DialogScrollArea>
|
|
114
|
-
<View style={{ paddingVertical: 4 }}>
|
|
155
|
+
<View style={{ paddingVertical: 4, gap: 8 }}>
|
|
156
|
+
{answerError !== null ? (
|
|
157
|
+
<Text size="sm" color="danger">
|
|
158
|
+
{answerError}
|
|
159
|
+
</Text>
|
|
160
|
+
) : null}
|
|
115
161
|
<ClarifyWizard questions={questions} onSubmit={submit} onCancel={onCancel} />
|
|
116
162
|
</View>
|
|
117
163
|
</DialogScrollArea>
|
package/src/ledger.tsx
CHANGED
|
@@ -237,7 +237,7 @@ export function LedgerRow(props: LedgerRowProps) {
|
|
|
237
237
|
{label}
|
|
238
238
|
</Text>
|
|
239
239
|
{meta ? (
|
|
240
|
-
<Text size="xs" color="muted" numberOfLines={1}>
|
|
240
|
+
<Text size="xs" color="muted" numberOfLines={1} style={styles.yield}>
|
|
241
241
|
{meta}
|
|
242
242
|
</Text>
|
|
243
243
|
) : null}
|
|
@@ -357,6 +357,13 @@ const styles = StyleSheet.create({
|
|
|
357
357
|
rowHovered: { backgroundColor: colors.zinc[50] },
|
|
358
358
|
grow: { flexGrow: 1, flexShrink: 1 },
|
|
359
359
|
shrink: { flexShrink: 1 },
|
|
360
|
+
// The META yields BEFORE the label, and by more. Only the label carried a
|
|
361
|
+
// shrink, so under width pressure the row'''s IDENTITY was the one thing that
|
|
362
|
+
// gave way while its subordinate qualifier held full width — a narrow ledger
|
|
363
|
+
// read "Tổng thu (chư…" beside an intact "03/06, Cash". A caption qualifies a
|
|
364
|
+
// number; the label names it, and the name is what a reader needs when the
|
|
365
|
+
// row is too tight for both.
|
|
366
|
+
yield: { flexShrink: 3 },
|
|
360
367
|
total: { gap: 6 },
|
|
361
368
|
// The BASIS row, the only one whose two sides can differ in height: its label may
|
|
362
369
|
// carry a stacked `meta` while the figure stays one line. Centring then measures the
|
package/src/matrix.tsx
CHANGED
|
@@ -25,6 +25,11 @@ interface MatrixContextValue {
|
|
|
25
25
|
totalColWidth: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** Gap between a matrix's columns. Exported because a caller sizing its own
|
|
29
|
+
* column budget needs the same number the grid renders with — hard-coding it
|
|
30
|
+
* is how a width budget and the layout drift apart. */
|
|
31
|
+
export const MATRIX_COL_GAP = 3;
|
|
32
|
+
|
|
28
33
|
const MatrixContext = createContext<MatrixContextValue | null>(null);
|
|
29
34
|
|
|
30
35
|
function useMatrix(): MatrixContextValue {
|
|
@@ -138,12 +143,12 @@ function MatrixHeader({ corner, totalLabel }: MatrixHeaderProps) {
|
|
|
138
143
|
)}
|
|
139
144
|
</View>
|
|
140
145
|
{cols.map((c) => (
|
|
141
|
-
<Text key={c.key} size="xs" color="muted" align="
|
|
146
|
+
<Text key={c.key} size="xs" color="muted" align="right" numberOfLines={1} style={styles.colCell}>
|
|
142
147
|
{c.label}
|
|
143
148
|
</Text>
|
|
144
149
|
))}
|
|
145
150
|
{hasTotals ? (
|
|
146
|
-
<Text size="xs" color="muted" weight="medium" align="
|
|
151
|
+
<Text size="xs" color="muted" weight="medium" align="right" numberOfLines={1} style={[styles.totalCol, { width: totalColWidth }]}>
|
|
147
152
|
{resolvedTotalLabel}
|
|
148
153
|
</Text>
|
|
149
154
|
) : null}
|
|
@@ -163,14 +168,14 @@ function MatrixGrid({ display = "both" }: MatrixGridProps) {
|
|
|
163
168
|
<View style={styles.grid}>
|
|
164
169
|
{rows.map((r) => (
|
|
165
170
|
<View key={r.key} style={styles.row}>
|
|
166
|
-
<Text size="sm"
|
|
171
|
+
<Text size="sm" numberOfLines={1} style={[styles.rowLabel, { width: rowLabelWidth }]}>
|
|
167
172
|
{r.label}
|
|
168
173
|
</Text>
|
|
169
174
|
{cols.map((c) => (
|
|
170
175
|
<MatrixCell key={c.key} ctx={ctx} display={display} row={r} col={c} value={value(r.key, c.key)} />
|
|
171
176
|
))}
|
|
172
177
|
{hasTotals ? (
|
|
173
|
-
<Text size="sm" weight="semibold" tabular align="
|
|
178
|
+
<Text size="sm" weight="semibold" tabular align="right" numberOfLines={1} style={[styles.totalCol, { width: totalColWidth }]}>
|
|
174
179
|
{formatValue(rowTotals.get(r.key) ?? 0)}
|
|
175
180
|
</Text>
|
|
176
181
|
) : null}
|
|
@@ -206,7 +211,7 @@ function MatrixCell({ ctx, display, row, col, value }: MatrixCellProps) {
|
|
|
206
211
|
const label = `${row.label}, ${col.label}: ${formatValue(value)}`;
|
|
207
212
|
|
|
208
213
|
const content = showNumber ? (
|
|
209
|
-
<Text size="sm" weight="medium" tabular align="
|
|
214
|
+
<Text size="sm" weight="medium" tabular align="right" color={value === 0 ? "zinc-500" : "default"}>
|
|
210
215
|
{value === 0 ? "" : formatValue(value)}
|
|
211
216
|
</Text>
|
|
212
217
|
) : null;
|
|
@@ -254,11 +259,11 @@ function MatrixTotals({ label }: MatrixTotalsProps) {
|
|
|
254
259
|
{label ?? locale.matrix.total}
|
|
255
260
|
</Text>
|
|
256
261
|
{cols.map((c) => (
|
|
257
|
-
<Text key={c.key} size="sm" weight="semibold" tabular align="
|
|
262
|
+
<Text key={c.key} size="sm" weight="semibold" tabular align="right" numberOfLines={1} style={styles.colCell}>
|
|
258
263
|
{formatValue(colTotals.get(c.key) ?? 0)}
|
|
259
264
|
</Text>
|
|
260
265
|
))}
|
|
261
|
-
<Text size="sm" weight="semibold" tabular align="
|
|
266
|
+
<Text size="sm" weight="semibold" tabular align="right" numberOfLines={1} style={[styles.totalCol, { width: totalColWidth }]}>
|
|
262
267
|
{formatValue(grandTotal)}
|
|
263
268
|
</Text>
|
|
264
269
|
</View>
|
|
@@ -295,21 +300,21 @@ Matrix.Legend = MatrixLegend;
|
|
|
295
300
|
|
|
296
301
|
const styles = StyleSheet.create({
|
|
297
302
|
container: {
|
|
298
|
-
gap:
|
|
303
|
+
gap: MATRIX_COL_GAP,
|
|
299
304
|
},
|
|
300
305
|
headRow: {
|
|
301
306
|
flexDirection: "row",
|
|
302
307
|
alignItems: "center",
|
|
303
|
-
gap:
|
|
308
|
+
gap: MATRIX_COL_GAP,
|
|
304
309
|
paddingBottom: 2,
|
|
305
310
|
},
|
|
306
311
|
grid: {
|
|
307
|
-
gap:
|
|
312
|
+
gap: MATRIX_COL_GAP,
|
|
308
313
|
},
|
|
309
314
|
row: {
|
|
310
315
|
flexDirection: "row",
|
|
311
316
|
alignItems: "center",
|
|
312
|
-
gap:
|
|
317
|
+
gap: MATRIX_COL_GAP,
|
|
313
318
|
},
|
|
314
319
|
rowLabel: {
|
|
315
320
|
paddingRight: 8,
|
|
@@ -333,7 +338,7 @@ const styles = StyleSheet.create({
|
|
|
333
338
|
totalsRow: {
|
|
334
339
|
flexDirection: "row",
|
|
335
340
|
alignItems: "center",
|
|
336
|
-
gap:
|
|
341
|
+
gap: MATRIX_COL_GAP,
|
|
337
342
|
marginTop: 3,
|
|
338
343
|
paddingTop: 8,
|
|
339
344
|
borderTopWidth: 1,
|
package/src/table_fit.ts
CHANGED
|
@@ -20,12 +20,22 @@ export interface TableFitColumn {
|
|
|
20
20
|
key: string;
|
|
21
21
|
/** Fixed width in px; omit for a flexible column. */
|
|
22
22
|
width?: number;
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
/** This column's claim on space, **`1` = highest**, the way P1/P2/P3 rank a
|
|
24
|
+
* bug: the column you least want to lose gets the SMALLEST number. Said the
|
|
25
|
+
* other way round it is the same rule — the larger the number, the sooner the
|
|
26
|
+
* column drops, and ties drop right-to-left. The FIRST column is the row's
|
|
27
|
+
* identity and never drops.
|
|
26
28
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
+
* Stated rank-first deliberately. "Higher drops first" is accurate and was
|
|
30
|
+
* all this said, but a reader who takes `priority` to mean "bigger is more
|
|
31
|
+
* important" inverts it and gives their most important column the LARGEST
|
|
32
|
+
* number — which is then the first column the fit sheds. That failure is
|
|
33
|
+
* invisible at desk width and shows only on a phone, so it reviews as
|
|
34
|
+
* correct. It has been read backwards once already.
|
|
35
|
+
*
|
|
36
|
+
* An unannotated column defaults to `columns.length + index`, which sits
|
|
37
|
+
* above the small ranks a real annotation uses rather than interleaved with
|
|
38
|
+
* them, so an
|
|
29
39
|
* unannotated register still sheds right-to-left AND an explicit priority is
|
|
30
40
|
* strictly safer than none. Defaulting to the bare index made annotation
|
|
31
41
|
* actively harmful: marking your most important column `priority: 1` TIED it
|