@peasant-labs/fairtrade 0.0.39 → 0.0.41

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.
Files changed (48) hide show
  1. package/THIRD_PARTY_NOTICES.md +14 -0
  2. package/dist/lib/{GraphLegend-DwLFq2JU.js → GraphLegend-CBKZsRta.js} +1401 -1488
  3. package/dist/lib/adapter-Dulh38C2.js +1214 -0
  4. package/dist/lib/analytics.css +1 -1
  5. package/dist/lib/analytics.js +289 -292
  6. package/dist/lib/commons.js +807 -805
  7. package/dist/lib/components.css +1 -1
  8. package/dist/lib/format-B3uT2wzU.js +41 -0
  9. package/dist/lib/graph.js +383 -381
  10. package/dist/lib/share-fonts/AtkinsonHyperlegible-Bold.ttf +0 -0
  11. package/dist/lib/share-fonts/AtkinsonHyperlegible-Regular.ttf +0 -0
  12. package/dist/lib/share-fonts/AtkinsonHyperlegibleMono-Bold.ttf +0 -0
  13. package/dist/lib/share-fonts/AtkinsonHyperlegibleMono-Regular.ttf +0 -0
  14. package/dist/lib/share-fonts/OFL-AtkinsonHyperlegible.txt +92 -0
  15. package/dist/lib/share-fonts/OFL-AtkinsonHyperlegibleMono.txt +93 -0
  16. package/dist/lib/share.js +1311 -0
  17. package/dist/lib/time-CpVBzcqQ.js +157 -0
  18. package/dist/lib/types/BrandMark.d.ts +2 -6
  19. package/dist/lib/types/CliOnboard.d.ts +11 -0
  20. package/dist/lib/types/DiffView.d.ts +5 -3
  21. package/dist/lib/types/Publish.d.ts +29 -7
  22. package/dist/lib/types/Switch.d.ts +8 -1
  23. package/dist/lib/types/analytics/format.d.ts +2 -1
  24. package/dist/lib/types/brand-geometry.d.ts +22 -0
  25. package/dist/lib/types/commons/providers.d.ts +1 -1
  26. package/dist/lib/types/format.d.ts +35 -4
  27. package/dist/lib/types/index.d.ts +1 -0
  28. package/dist/lib/types/share/ShareCard.d.ts +40 -0
  29. package/dist/lib/types/share/data.d.ts +54 -0
  30. package/dist/lib/types/share/fonts.d.ts +16 -0
  31. package/dist/lib/types/share/index.d.ts +5 -0
  32. package/dist/lib/types/share/marks.d.ts +23 -0
  33. package/dist/lib/types/share/metrics.d.ts +10 -0
  34. package/dist/lib/types/transcript/title.d.ts +21 -0
  35. package/dist/lib/types/ui/brand-geometry.d.ts +22 -0
  36. package/dist/lib/types/ui/format.d.ts +35 -0
  37. package/dist/lib/types/ui/provider-policy.d.ts +27 -0
  38. package/dist/lib/types/ui/transcript/adapter.d.ts +66 -0
  39. package/dist/lib/types/ui/transcript/adapter.parse.d.ts +103 -0
  40. package/dist/lib/types/ui/transcript/analytics.d.ts +213 -0
  41. package/dist/lib/types/ui/transcript/time.d.ts +47 -0
  42. package/dist/lib/types/ui/transcript/title.d.ts +84 -0
  43. package/dist/lib/types/ui/transcript/usage.d.ts +19 -0
  44. package/dist/lib/types/ui/transcript/view-model.d.ts +700 -0
  45. package/dist/lib/types/ui/transcript/wire-types.d.ts +63 -0
  46. package/dist/lib/{ui-CBsfaQMA.js → ui-Bhk29YuW.js} +4198 -5412
  47. package/dist/lib/ui.js +7 -4
  48. package/package.json +20 -6
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Compact count, one decimal at most: 999 → "999", 1234 → "1.2k", 66_731_500_000 → "66.7B".
3
+ * @param {unknown} value
4
+ * @returns {string}
5
+ */
6
+ export function formatCount(value: unknown): string;
7
+ /**
8
+ * Exact grouped integer: 272000 → "272,000". A bigint or a string of digits stays exact past the
9
+ * JS-safe range; any other value is rounded.
10
+ * @param {unknown} value
11
+ * @returns {string}
12
+ */
13
+ export function formatInteger(value: unknown): string;
14
+ /**
15
+ * A cost in US dollars at cents: "$0.30", "$1,234.56", "$0.00" for zero, "<$0.01" for a positive
16
+ * amount under one cent. Accepts the recorded decimal string as it arrives.
17
+ * @param {unknown} value
18
+ * @returns {string}
19
+ */
20
+ export function formatCost(value: unknown): string;
21
+ /**
22
+ * A decimal at fixed places: 0.99 → "0.99", 0.5 with 2 places → "0.50".
23
+ * @param {unknown} value
24
+ * @param {number} places
25
+ * @returns {string}
26
+ */
27
+ export function formatDecimal(value: unknown, places: number): string;
28
+ /**
29
+ * Calendar dates are stable across host locales; date-only inputs stay on their UTC day.
30
+ * @param {string | number | Date} value
31
+ * @returns {string}
32
+ */
33
+ export function formatDate(value: string | number | Date): string;
34
+ /** What every formatter shows for a missing or unreadable value. */
35
+ export const MISSING_NUMBER: "\u2014";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validate an untrusted provider value before it enters canonical display APIs.
3
+ * @param {unknown} harness
4
+ * @param {string} operation
5
+ * @returns {asserts harness is Harness}
6
+ */
7
+ export function assertHarness(harness: unknown, operation: string): asserts harness is Harness;
8
+ /** @param {Harness} harness @returns {string} */
9
+ export function providerDisplayName(harness: Harness): string;
10
+ /** @param {Harness} harness @returns {ProviderBrand} */
11
+ export function providerBrand(harness: Harness): ProviderBrand;
12
+ /** @param {Harness} harness @returns {ProviderAccent} */
13
+ export function providerAccent(harness: Harness): ProviderAccent;
14
+ /** @typedef {import('@peasant-labs/schema').Harness} Harness */
15
+ /** @typedef {'claude'|'gemini'|'openai'|'opencode'|'cursor'|'strike'|'pi'} ProviderBrand */
16
+ /** @typedef {'amber'|'teal'|'olive'|'mauve'|'clay'} ProviderAccent */
17
+ /** @type {readonly Harness[]} */
18
+ export const PROVIDER_HARNESSES: readonly Harness[];
19
+ /** @type {Readonly<Record<Harness, string>>} */
20
+ export const PROVIDER_DISPLAY_NAMES: Readonly<Record<Harness, string>>;
21
+ /** @type {Readonly<Record<Harness, ProviderBrand>>} */
22
+ export const PROVIDER_BRANDS: Readonly<Record<Harness, ProviderBrand>>;
23
+ /** @type {Readonly<Record<Harness, ProviderAccent>>} */
24
+ export const PROVIDER_ACCENTS: Readonly<Record<Harness, ProviderAccent>>;
25
+ export type Harness = import("@peasant-labs/schema").Harness;
26
+ export type ProviderBrand = "claude" | "gemini" | "openai" | "opencode" | "cursor" | "strike" | "pi";
27
+ export type ProviderAccent = "amber" | "teal" | "olive" | "mauve" | "clay";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Is this wire turn one of Claude Code's session metadata records rather than conversation?
3
+ * @param {TurnDetail} turn
4
+ * @param {string | undefined} harness the session's harness
5
+ * @returns {boolean}
6
+ */
7
+ export function isSessionMetadataTurn(turn: TurnDetail, harness: string | undefined): boolean;
8
+ /**
9
+ * Default turn prefilter the adapter applies independently per partition:
10
+ * preserve normalized evidence, including unknown provenance and folded refs.
11
+ * Only truly legacy rows lose empty/short banners or collapse equal adjacent
12
+ * content, preferring the tool-bearing copy. Pure, order-preserving. Exported
13
+ * so a host that pre-scopes turns can run the SAME filter.
14
+ * @param {TurnDetail[]} turns
15
+ * @returns {TurnDetail[]}
16
+ */
17
+ export function prefilterTurns(turns: TurnDetail[]): TurnDetail[];
18
+ /**
19
+ * Project the canonical wire payload (folded turns) into the cooked
20
+ * `TranscriptViewModel` every dumb transcript component renders. The SOLE
21
+ * wire-parse + legacy-git normalisation site.
22
+ *
23
+ * @param {TranscriptWireInput} payload canonical wire plus legacy git compatibility
24
+ * @param {AnnotationSummary[]} [annotations] separately-fetched entry annotations
25
+ * @param {TranscriptAnalyticsVM} [analytics] precomputed analytics; else derived on demand
26
+ * @param {AdaptTranscriptOptions} [options] optional post-resolution visible-turn projection
27
+ * @returns {TranscriptViewModel}
28
+ */
29
+ export function adaptTranscript(payload: TranscriptWireInput, annotations?: AnnotationSummary[], analytics?: TranscriptAnalyticsVM, options?: AdaptTranscriptOptions): TranscriptViewModel;
30
+ export { diffLines };
31
+ export type TranscriptWireInput = import("./wire-types.js").TranscriptWireInput;
32
+ export type SessionDetailPayload = import("./wire-types.js").SessionDetailPayload;
33
+ export type TurnDetail = import("./wire-types.js").TurnDetail;
34
+ export type ToolCallDetail = import("./wire-types.js").ToolCallDetail;
35
+ export type AnnotationSummary = import("./wire-types.js").AnnotationSummary;
36
+ export type CommitInfo = import("./wire-types.js").CommitInfo;
37
+ export type LegacyCommit = import("./wire-types.js").LegacyCommit;
38
+ export type LegacyGitContext = import("./wire-types.js").LegacyGitContext;
39
+ export type TranscriptViewModel = import("./view-model.js").TranscriptViewModel;
40
+ export type TranscriptAnalyticsVM = import("./view-model.js").TranscriptAnalyticsVM;
41
+ export type SessionVM = import("./view-model.js").SessionVM;
42
+ export type SessionGitVM = import("./view-model.js").SessionGitVM;
43
+ export type CommitVM = import("./view-model.js").CommitVM;
44
+ export type TurnVM = import("./view-model.js").TurnVM;
45
+ export type TaskGroupVM = import("./view-model.js").TaskGroupVM;
46
+ export type ToolCallVM = import("./view-model.js").ToolCallVM;
47
+ export type ToolGroup = import("./view-model.js").ToolGroup;
48
+ export type DiffLineVM = import("./view-model.js").DiffLineVM;
49
+ export type DiffHunkVM = import("./view-model.js").DiffHunkVM;
50
+ export type DiffEntryVM = import("./view-model.js").DiffEntryVM;
51
+ export type FileEntryVM = import("./view-model.js").FileEntryVM;
52
+ export type AnnotationVM = import("./view-model.js").AnnotationVM;
53
+ export type HighlightVM = import("./view-model.js").HighlightVM;
54
+ export type FilterIndexVM = import("./view-model.js").FilterIndexVM;
55
+ export type PhaseVM = import("./view-model.js").PhaseVM;
56
+ export type AdaptTranscriptOptions = {
57
+ /**
58
+ * post-resolution projection by canonical turn index
59
+ */
60
+ visibleTurnIndices?: readonly number[];
61
+ /**
62
+ * separately authorized read metadata; never durable content
63
+ */
64
+ relationshipNavigation?: import("@peasant-labs/schema").SessionRelationshipNavigation[];
65
+ };
66
+ import { diffLines } from './adapter.parse.js';
@@ -0,0 +1,103 @@
1
+ /** @typedef {import('./wire-types.js').ToolCallDetail} ToolCallDetail */
2
+ /** @typedef {import('./view-model.js').DiffLineVM} DiffLineVM */
3
+ /**
4
+ * The SOLE `JSON.parse` of a wire-encoded tool string in the lifted code. Every
5
+ * other parse helper here, the adapter, and the analytics util route through it,
6
+ * so a grep for `JSON.parse` of tool args/result yields exactly one hit. Returns
7
+ * `undefined` for empty or malformed input — callers degrade rather than throw.
8
+ *
9
+ * @param {string | undefined | null} raw
10
+ * @returns {unknown}
11
+ */
12
+ export function parseJson(raw: string | undefined | null): unknown;
13
+ /**
14
+ * Parse a tool call's `arguments` JSON string. Thin named entry point over the
15
+ * one `parseJson` site; the cooked `ToolCallVM.args` is this value (shape varies
16
+ * by tool, hence `unknown`).
17
+ * @param {string | undefined | null} raw
18
+ * @returns {unknown}
19
+ */
20
+ export function parseArgs(raw: string | undefined | null): unknown;
21
+ /**
22
+ * Parse a tool call's `result` JSON string. Thin named entry point over the one
23
+ * `parseJson` site; the cooked `ToolCallVM.output` is this value.
24
+ * @param {string | undefined | null} raw
25
+ * @returns {unknown}
26
+ */
27
+ export function parseResult(raw: string | undefined | null): unknown;
28
+ /**
29
+ * Extract the file path a tool call targets, preferring the wire `filePath`
30
+ * field and falling back to the common `file_path` / `path` / `notebook_path`
31
+ * argument keys. Consolidates the THREE duplicate `extractPath` definitions
32
+ * (tasks.ts, FilesView.tsx, file-rollup.ts) into one.
33
+ * @param {ToolCallDetail} call
34
+ * @returns {string | undefined}
35
+ */
36
+ export function extractPath(call: ToolCallDetail): string | undefined;
37
+ /**
38
+ * One old→new replacement inside an Edit / MultiEdit / NotebookEdit call.
39
+ * @typedef {object} EditPair
40
+ * @property {string} old_string
41
+ * @property {string} new_string
42
+ */
43
+ /**
44
+ * The list of old→new replacements for an edit-family tool call: the `edits[]`
45
+ * array for MultiEdit, else the single `old_string` / `new_string` pair. Used by
46
+ * BOTH the diff-hunk builder (adapter) and the churn counter (`countDiff`), so
47
+ * the argument shape is parsed in exactly one place.
48
+ * @param {ToolCallDetail} call
49
+ * @returns {EditPair[]}
50
+ */
51
+ export function editPairs(call: ToolCallDetail): EditPair[];
52
+ /**
53
+ * The full-file body a Write tool call lays down (its `content` argument).
54
+ * @param {ToolCallDetail} call
55
+ * @returns {string}
56
+ */
57
+ export function writeContent(call: ToolCallDetail): string;
58
+ /**
59
+ * Dependency-free line diff (LCS) → cooked `DiffLineVM[]`. fairtrade ships no
60
+ * `diff` package, so the adapter computes hunks itself. Line numbers are 1-based
61
+ * within the supplied snippet (the wire carries the edit snippet, not the file
62
+ * offset). Deterministic — no clock/randomness — so it is screenshot-stable.
63
+ * @param {string} oldText
64
+ * @param {string} newText
65
+ * @returns {DiffLineVM[]}
66
+ */
67
+ export function diffLines(oldText: string, newText: string): DiffLineVM[];
68
+ /**
69
+ * The cooked line diff of an edit-family or write tool call (one line list per replaced span) and
70
+ * its churn: the + and − lines of that same diff, so a rewrite of three lines counts +3 −3 and every
71
+ * count (the tool call, the diffs tab, a task's churn) agrees with the lines it heads. A span's
72
+ * trailing empty line is the newline artifact, not a line anyone wrote, so it is not counted, as the
73
+ * renderer does not show it. Undefined for any other kind, or an edit with no replacement. Memoised
74
+ * per call object, because the adapter and the analytics both ask and the line diff is quadratic in
75
+ * the span length.
76
+ * @param {ToolCallDetail} call
77
+ * @returns {{ spans: DiffLineVM[][], adds: number, dels: number } | undefined}
78
+ */
79
+ export function callDiff(call: ToolCallDetail): {
80
+ spans: DiffLineVM[][];
81
+ adds: number;
82
+ dels: number;
83
+ } | undefined;
84
+ /**
85
+ * Lines inserted / removed for an edit-family or write tool call: the churn of `callDiff`, zero
86
+ * for any other kind. Consolidates the THREE duplicate `countDiff` definitions. Pure: depends only
87
+ * on the parsed arguments, never a clock or the file system.
88
+ * @param {ToolCallDetail} call
89
+ * @returns {{ adds: number, dels: number }}
90
+ */
91
+ export function countDiff(call: ToolCallDetail): {
92
+ adds: number;
93
+ dels: number;
94
+ };
95
+ export type ToolCallDetail = import("./wire-types.js").ToolCallDetail;
96
+ export type DiffLineVM = import("./view-model.js").DiffLineVM;
97
+ /**
98
+ * One old→new replacement inside an Edit / MultiEdit / NotebookEdit call.
99
+ */
100
+ export type EditPair = {
101
+ old_string: string;
102
+ new_string: string;
103
+ };
@@ -0,0 +1,213 @@
1
+ /** @typedef {import('./wire-types.js').TurnDetail} TurnDetail */
2
+ /** @typedef {import('./wire-types.js').ToolCallDetail} ToolCallDetail */
3
+ /** @typedef {import('./wire-types.js').SessionScorecard} SessionScorecard */
4
+ /** @typedef {import('./view-model.js').PhaseVM} PhaseVM */
5
+ /** @typedef {import('./view-model.js').ScorecardBandVM} ScorecardBandVM */
6
+ /** @typedef {import('./view-model.js').AnnotationVM} AnnotationVM */
7
+ /** @typedef {import('./view-model.js').TaskGroupVM} TaskGroupVM */
8
+ /** @typedef {import('./view-model.js').TranscriptAnalyticsVM} TranscriptAnalyticsVM */
9
+ /**
10
+ * A "task" is the span from a top-level user prompt up to (but not including)
11
+ * the next one — one turn of human↔agent collaboration. The shape is the
12
+ * canonical `TaskGroup` shape, kept stable for consumer back-compat.
13
+ * @typedef {object} TaskGroup
14
+ * @property {number} startIndex display position of the starting prompt
15
+ * @property {number} endIndex display position of the last turn (inclusive)
16
+ * @property {number} promptEntryIndex wire entry index of the prompt (anchor)
17
+ * @property {number} [finalEntryIndex] wire entry index of the final assistant turn
18
+ * @property {string} prompt untruncated user prompt
19
+ * @property {number} toolCallCount
20
+ * @property {string[]} filesTouched
21
+ * @property {number} insertions
22
+ * @property {number} deletions
23
+ * @property {number} durationMs
24
+ * @property {boolean} hasErrors
25
+ * @property {number} tokens
26
+ */
27
+ /**
28
+ * Split a turn list into task groups. Each top-level (depth-0) user turn starts
29
+ * a new task; depth-1+ user turns are content blocks, NOT boundaries. A session
30
+ * that starts without a user prompt is a single task spanning everything.
31
+ * @param {TurnDetail[]} turns
32
+ * @returns {TaskGroup[]}
33
+ */
34
+ export function computeTasks(turns: TurnDetail[]): TaskGroup[];
35
+ /**
36
+ * Per-turn display labels: `"1"`, `"2"` for the prompts that open a task, and
37
+ * `"2a"`, `"2b"` for the follow-ups within it.
38
+ * @param {TurnDetail[]} turns
39
+ * @returns {string[]}
40
+ */
41
+ export function computeTurnLabels(turns: TurnDetail[]): string[];
42
+ /**
43
+ * One segment of the proportional task waterfall: a task's share of the total
44
+ * work time and the cumulative share before it (segments tile back-to-back).
45
+ * @typedef {object} WaterfallSegment
46
+ * @property {number} promptEntryIndex
47
+ * @property {number} offsetPct
48
+ * @property {number} widthPct
49
+ * @property {number} durationMs
50
+ */
51
+ /**
52
+ * Turn task groups into a relative-duration lane. When the total duration is 0
53
+ * (untimed transcript) every segment is zero-width at offset 0 — the caller
54
+ * renders the task list without bars rather than dividing by zero.
55
+ * @param {TaskGroup[]} tasks
56
+ * @returns {WaterfallSegment[]}
57
+ */
58
+ export function buildTaskWaterfall(tasks: TaskGroup[]): WaterfallSegment[];
59
+ /**
60
+ * Walk a transcript and surface error / retry / revert / subagent moments,
61
+ * sorted by display position.
62
+ * @param {TurnDetail[]} turns
63
+ * @returns {TranscriptAnnotation[]}
64
+ */
65
+ export function annotateTranscript(turns: TurnDetail[]): TranscriptAnnotation[];
66
+ /** Display label for a phase type, falling back to the raw value. @param {string} type @returns {string} */
67
+ export function phaseLabel(type: string): string;
68
+ /**
69
+ * Segment a transcript into rendered phases. `from` / `to` are WIRE turn indices
70
+ * (matching `TurnVM.index`), so a divider component can anchor by them directly.
71
+ * @param {TurnDetail[]} turns
72
+ * @returns {PhaseVM[]}
73
+ */
74
+ export function detectPhases(turns: TurnDetail[]): PhaseVM[];
75
+ /**
76
+ * Per-metric medians across the user's sessions, each field undefined when the
77
+ * sample yields no value (so the comparison line degrades rather than fabricate
78
+ * a baseline). The host supplies the sessions; the viewer never fetches them.
79
+ * @param {PersonalMedianSession[] | undefined} sessions
80
+ * @returns {PersonalMedians}
81
+ */
82
+ export function computePersonalMedians(sessions: PersonalMedianSession[] | undefined): PersonalMedians;
83
+ /**
84
+ * Derive the three scorecard axis verdict bands (token / prompt / loop
85
+ * efficiency) from the raw numeric signals. Returns `[]` when the scorecard
86
+ * carries no usable signal, so the card is omitted cleanly.
87
+ * @param {SessionScorecard | null | undefined} sc
88
+ * @param {PersonalMedians} [medians]
89
+ * @returns {ScorecardBandVM[]}
90
+ */
91
+ export function assessScorecard(sc: SessionScorecard | null | undefined, medians?: PersonalMedians): ScorecardBandVM[];
92
+ /**
93
+ * The cooked task groups for a turn list (`computeTasks` mapped onto
94
+ * `TaskGroupVM`). Exported so the adapter can populate the always-present
95
+ * `vm.tasks` from the same single computation `computeAnalytics` uses.
96
+ * @param {TurnDetail[]} turns
97
+ * @returns {TaskGroupVM[]}
98
+ */
99
+ export function computeTaskGroups(turns: TurnDetail[]): TaskGroupVM[];
100
+ /**
101
+ * Compute the cooked single-session analytics block from a (prefiltered) turn
102
+ * list. Each field is omitted when empty so the block is genuinely
103
+ * render-when-present. The adapter calls this on demand when no precomputed
104
+ * analytics is supplied, and reuses the returned `patternAnnotations` /
105
+ * `taskGroups` so nothing is derived twice.
106
+ * @param {TurnDetail[]} turns
107
+ * @param {{ scorecard?: SessionScorecard | null, medians?: PersonalMedians }} [opts]
108
+ * @returns {TranscriptAnalyticsVM}
109
+ */
110
+ export function computeAnalytics(turns: TurnDetail[], opts?: {
111
+ scorecard?: SessionScorecard | null;
112
+ medians?: PersonalMedians;
113
+ }): TranscriptAnalyticsVM;
114
+ export type TurnDetail = import("./wire-types.js").TurnDetail;
115
+ export type ToolCallDetail = import("./wire-types.js").ToolCallDetail;
116
+ export type SessionScorecard = import("./wire-types.js").SessionScorecard;
117
+ export type PhaseVM = import("./view-model.js").PhaseVM;
118
+ export type ScorecardBandVM = import("./view-model.js").ScorecardBandVM;
119
+ export type AnnotationVM = import("./view-model.js").AnnotationVM;
120
+ export type TaskGroupVM = import("./view-model.js").TaskGroupVM;
121
+ export type TranscriptAnalyticsVM = import("./view-model.js").TranscriptAnalyticsVM;
122
+ /**
123
+ * A "task" is the span from a top-level user prompt up to (but not including)
124
+ * the next one — one turn of human↔agent collaboration. The shape is the
125
+ * canonical `TaskGroup` shape, kept stable for consumer back-compat.
126
+ */
127
+ export type TaskGroup = {
128
+ /**
129
+ * display position of the starting prompt
130
+ */
131
+ startIndex: number;
132
+ /**
133
+ * display position of the last turn (inclusive)
134
+ */
135
+ endIndex: number;
136
+ /**
137
+ * wire entry index of the prompt (anchor)
138
+ */
139
+ promptEntryIndex: number;
140
+ /**
141
+ * wire entry index of the final assistant turn
142
+ */
143
+ finalEntryIndex?: number;
144
+ /**
145
+ * untruncated user prompt
146
+ */
147
+ prompt: string;
148
+ toolCallCount: number;
149
+ filesTouched: string[];
150
+ insertions: number;
151
+ deletions: number;
152
+ durationMs: number;
153
+ hasErrors: boolean;
154
+ tokens: number;
155
+ };
156
+ /**
157
+ * One segment of the proportional task waterfall: a task's share of the total
158
+ * work time and the cumulative share before it (segments tile back-to-back).
159
+ */
160
+ export type WaterfallSegment = {
161
+ promptEntryIndex: number;
162
+ offsetPct: number;
163
+ widthPct: number;
164
+ durationMs: number;
165
+ };
166
+ export type FailedAttempt = {
167
+ tools: string[];
168
+ turnRange: [number, number];
169
+ };
170
+ export type PersonalMedians = {
171
+ retryShare?: number;
172
+ specQualityScore?: number;
173
+ withinSessionReverts?: number;
174
+ };
175
+ /**
176
+ * A session sample for personal-median computation — the QualitySession subset
177
+ * the medians read. Kept structural for consumer back-compat.
178
+ */
179
+ export type PersonalMedianSession = {
180
+ totalTokens: number;
181
+ retryTokensWasted: number;
182
+ specQualityScore: number;
183
+ withinSessionReverts: number;
184
+ };
185
+ /**
186
+ * A client-side detected pattern moment. Canonical `TranscriptAnnotation` shape
187
+ * kept stable for back-compat re-export.
188
+ */
189
+ export type TranscriptAnnotation = {
190
+ /**
191
+ * display position
192
+ */
193
+ turnIndex: number;
194
+ type: "retry" | "error" | "revert" | "subagent";
195
+ label: string;
196
+ severity: "info" | "warning" | "error";
197
+ };
198
+ export type PhaseType = "planning" | "exploration" | "implementation" | "testing" | "error" | "debug" | "retry-loop" | "user-correction" | "recovery" | "abandonment";
199
+ export type PhaseSegment = {
200
+ type: PhaseType;
201
+ /**
202
+ * display position (inclusive)
203
+ */
204
+ startTurn: number;
205
+ /**
206
+ * display position (inclusive)
207
+ */
208
+ endTurn: number;
209
+ badges: {
210
+ type: PhaseType;
211
+ count: number;
212
+ }[];
213
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Is this a real recorded time? A missing, unparseable, zero, epoch or Go zero-time value
3
+ * ("0001-01-01T00:00:00Z") is unknown: nothing may render it, and no "ago" math runs on it.
4
+ *
5
+ * @param {string | null | undefined} iso
6
+ * @returns {boolean}
7
+ */
8
+ export function isKnownTime(iso: string | null | undefined): boolean;
9
+ /**
10
+ * Human-readable relative time: "just now", "3m ago", "2h ago", "4d ago"; past a week it reads as
11
+ * a date ("oct 14", or "oct 14, 2025" in another year). An unknown time reads as nothing.
12
+ *
13
+ * @param {string} iso an ISO 8601 timestamp
14
+ * @param {number} [now] reference "now" in epoch millis (defaults to `Date.now()`; pass explicitly for deterministic tests)
15
+ * @returns {string}
16
+ */
17
+ export function formatRelative(iso: string, now?: number): string;
18
+ /**
19
+ * The one duration format: human and short. "12s", "4m 30s", "4m", "1h 12m", "1h"; under a
20
+ * second "<1s"; zero, negative or unknown reads as nothing, so a caller hides the figure.
21
+ *
22
+ * @param {number | null | undefined} seconds
23
+ * @returns {string}
24
+ */
25
+ export function formatDurationSeconds(seconds: number | null | undefined): string;
26
+ /**
27
+ * A duration in minutes, in the one format: 4.5 -> "4m 30s", 76 -> "1h 16m", 0 -> "".
28
+ *
29
+ * @param {number | null | undefined} mins
30
+ * @returns {string}
31
+ */
32
+ export function formatDurationMins(mins: number | null | undefined): string;
33
+ /**
34
+ * A duration in milliseconds: a tool call reads "820ms" or "1.4s" while short, then the one
35
+ * format ("1m 15s"); zero or unknown reads as nothing.
36
+ *
37
+ * @param {number | null | undefined} ms
38
+ * @returns {string}
39
+ */
40
+ export function formatDurationMs(ms: number | null | undefined): string;
41
+ /**
42
+ * "Jan 12, 4:23 PM" — long datetime for tooltips.
43
+ *
44
+ * @param {string} iso
45
+ * @returns {string}
46
+ */
47
+ export function formatDateLong(iso: string): string;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The prompt without a harness's <user_query> wrapper: the user's own text, untouched otherwise.
3
+ * @param {string | undefined} text
4
+ * @returns {string}
5
+ */
6
+ export function unwrapUserQuery(text: string | undefined): string;
7
+ /**
8
+ * A stored session title, shown as written: the harness's <user_query> wrapper dropped and the
9
+ * whitespace collapsed, but every word, its case, its dots and an existing trailing "…" kept. For a
10
+ * title, never `summarizePrompt`, which cuts at the first sentence and recases.
11
+ * @param {string | undefined} text
12
+ * @returns {string}
13
+ */
14
+ export function cleanSessionTitle(text: string | undefined): string;
15
+ /**
16
+ * The transcript page's title, the one rule the viewer's header and the share card both use: a
17
+ * curated title (an empty one is kept), else, when the session has no normalized evidence, the
18
+ * first task's prompt or the first user turn's text, else the session id, else "transcript".
19
+ * Session titles are often a whole first prompt, so the title is bounded to 160 characters: a
20
+ * longer one is cut to 159 and "…", never through a surrogate pair.
21
+ * @param {object} parts
22
+ * @param {string} [parts.title] curated session title
23
+ * @param {boolean} [parts.hasNormalizedEvidence] true when a title must not be inferred from a prompt
24
+ * @param {string} [parts.firstTaskPrompt]
25
+ * @param {string} [parts.firstUserContent]
26
+ * @param {string} [parts.id] session id
27
+ * @returns {string}
28
+ */
29
+ export function transcriptTitle({ title, hasNormalizedEvidence, firstTaskPrompt, firstUserContent, id }: {
30
+ title?: string;
31
+ hasNormalizedEvidence?: boolean;
32
+ firstTaskPrompt?: string;
33
+ firstUserContent?: string;
34
+ id?: string;
35
+ }): string;
36
+ /**
37
+ * Produce a short, human-readable summary of a raw prompt. Heuristic, in order:
38
+ * 0. Drop a harness's <user_query> wrapper (cleanSessionTitle's rule).
39
+ * 1. Strip leading <system-reminder> blocks and leading fenced code blocks.
40
+ * 2. Pull out a <command-name> (e.g. `/mcp`) if present, keep following prose.
41
+ * 3. Strip remaining XML-ish tags, collapse whitespace, take the first sentence.
42
+ * 4. Capitalize the first letter and cap at `limit` chars with `…`.
43
+ *
44
+ * @param {string | undefined} raw
45
+ * @param {number} [limit]
46
+ * @returns {string}
47
+ */
48
+ export function summarizePrompt(raw: string | undefined, limit?: number): string;
49
+ /**
50
+ * Last path segment of a project string. `/Users/x/Projects/phaze` → `phaze`.
51
+ * Falls back to the whole string if there's no separator. Returns `''` when the
52
+ * input is empty/undefined.
53
+ *
54
+ * @param {string | undefined} project
55
+ * @returns {string}
56
+ */
57
+ export function projectLabel(project: string | undefined): string;
58
+ /**
59
+ * Compose the session detail h1.
60
+ *
61
+ * • {project}: {Summary} — project + meaningful prompt
62
+ * • {project}, {shortId} — project, no usable prompt
63
+ * • {shortId} — {Summary} — no project, but a prompt
64
+ * • Session {shortId} — nothing useful at all
65
+ *
66
+ * The full prompt text (or composed title) is returned alongside as a tooltip
67
+ * string so `<h1 title=…>` can expose the raw prompt on hover.
68
+ *
69
+ * @param {object} args
70
+ * @param {string} args.id
71
+ * @param {string | undefined} args.project
72
+ * @param {string | undefined} args.promptContent
73
+ * @param {number} [args.limit]
74
+ * @returns {{ title: string, tooltip: string }}
75
+ */
76
+ export function composeSessionTitle(args: {
77
+ id: string;
78
+ project: string | undefined;
79
+ promptContent: string | undefined;
80
+ limit?: number;
81
+ }): {
82
+ title: string;
83
+ tooltip: string;
84
+ };
@@ -0,0 +1,19 @@
1
+ /** @param {import('@peasant-labs/schema').TurnDetail[]} turns
2
+ * @returns {import('./view-model.js').UsageScopeVM[]} */
3
+ export function aggregateUsage(turns: import("@peasant-labs/schema").TurnDetail[]): import("./view-model.js").UsageScopeVM[];
4
+ export const TOKEN_FIELDS: Readonly<{
5
+ input: "input";
6
+ output: "output";
7
+ cacheRead: "cache read";
8
+ cacheWrite: "cache write";
9
+ cacheWrite1h: "cache write 1h (subset)";
10
+ reasoning: "reasoning (subset)";
11
+ totalTokens: "total tokens";
12
+ }>;
13
+ export const COST_FIELDS: Readonly<{
14
+ input: "input";
15
+ output: "output";
16
+ cacheRead: "cache read";
17
+ cacheWrite: "cache write";
18
+ total: "total";
19
+ }>;