Skip to content

Commit 6dab970

Browse files
committed
refactor(tui): move the transcript boundary predicates out of the class
Neither predicate reads instance state or touches the UI, so they belong with the other transcript-component helpers rather than as private methods.
1 parent e623f1b commit 6dab970

2 files changed

Lines changed: 43 additions & 38 deletions

File tree

apps/pythinker-code/src/tui/pythinker-tui.ts

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,7 @@ import {
9393
import { StepSummaryComponent } from './components/messages/step-summary';
9494
import { ThinkingComponent } from './components/messages/thinking';
9595
import { ToolCallComponent } from './components/messages/tool-call';
96-
import {
97-
ReplayTurnBoundaryComponent,
98-
UserMessageComponent,
99-
} from './components/messages/user-message';
96+
import { UserMessageComponent } from './components/messages/user-message';
10097
import { ActivityPaneComponent, type ActivityPaneMode } from './components/panes/activity-pane';
10198
import { QueuePaneComponent } from './components/panes/queue-pane';
10299
import type { TuiConfig } from './config';
@@ -175,7 +172,8 @@ import { notifyTerminalOnce } from './utils/terminal-notification';
175172
import { installTerminalThemeTracking } from './utils/terminal-theme';
176173
import { detectTmuxKeyboardWarning } from './utils/tmux-keyboard';
177174
import {
178-
getTranscriptComponentEntry,
175+
isFoldSegmentBoundaryComponent,
176+
isTurnBoundaryComponent,
179177
markTranscriptComponent,
180178
} from './utils/transcript-component-metadata';
181179
import { nextTranscriptId } from './utils/transcript-id';
@@ -2787,34 +2785,6 @@ export class PythinkerTUI {
27872785
this.state.ui.requestRender();
27882786
}
27892787

2790-
private isTurnBoundaryComponent(child: Component): boolean {
2791-
if (
2792-
!(child instanceof UserMessageComponent) &&
2793-
!(child instanceof SkillActivationComponent) &&
2794-
!(child instanceof PluginCommandComponent) &&
2795-
!(child instanceof ReplayTurnBoundaryComponent)
2796-
) {
2797-
return false;
2798-
}
2799-
const entry = getTranscriptComponentEntry(child);
2800-
if (entry === undefined) return false;
2801-
// Live user messages / slash activations have an undefined turnId; replayed
2802-
// ones get a `replay:N` turnId. Both start a new turn. Steer messages carry
2803-
// a defined non-replay turnId and are not boundaries.
2804-
return entry.turnId === undefined || entry.turnId.startsWith('replay:');
2805-
}
2806-
2807-
/**
2808-
* Fold-segment boundary: everything {@link isTurnBoundaryComponent} counts,
2809-
* plus the cron card. A cron-fired turn mounts no user message, so without
2810-
* the card as a boundary its output would share the previous user turn's
2811-
* fold segment - and the completed-turn assistant cap would fold that turn's
2812-
* final answer into the step summary.
2813-
*/
2814-
private isFoldSegmentBoundaryComponent(child: Component): boolean {
2815-
return this.isTurnBoundaryComponent(child) || child instanceof CronMessageComponent;
2816-
}
2817-
28182788
private trimTranscriptWindow(): boolean {
28192789
if (!TRANSCRIPT_WINDOW_ENABLED || TRANSCRIPT_MAX_TURNS <= 0) return false;
28202790
// Session replay already caps history to its own turn limit; trimming during
@@ -2828,7 +2798,7 @@ export class PythinkerTUI {
28282798
// the rest of the turn would be left behind.
28292799
const boundaries: number[] = [];
28302800
for (let i = 0; i < children.length; i++) {
2831-
if (this.isTurnBoundaryComponent(children[i]!)) boundaries.push(i);
2801+
if (isTurnBoundaryComponent(children[i]!)) boundaries.push(i);
28322802
}
28332803

28342804
const turns = groupTurns(this.state.transcriptEntries);
@@ -2865,7 +2835,7 @@ export class PythinkerTUI {
28652835
let boundariesSeen = 0;
28662836
let cutoff = 0;
28672837
for (let i = 0; i < children.length; i++) {
2868-
if (this.isTurnBoundaryComponent(children[i]!)) {
2838+
if (isTurnBoundaryComponent(children[i]!)) {
28692839
if (boundariesSeen === boundariesToRemove) {
28702840
cutoff = i;
28712841
break;
@@ -2919,7 +2889,7 @@ export class PythinkerTUI {
29192889
// Find the start of the current turn (last turn-starting user message).
29202890
let turnStart = -1;
29212891
for (let i = children.length - 1; i >= 0; i--) {
2922-
if (this.isFoldSegmentBoundaryComponent(children[i]!)) {
2892+
if (isFoldSegmentBoundaryComponent(children[i]!)) {
29232893
turnStart = i;
29242894
break;
29252895
}
@@ -3001,7 +2971,7 @@ export class PythinkerTUI {
30012971

30022972
const boundaries: number[] = [];
30032973
for (let i = 0; i < children.length; i++) {
3004-
if (this.isFoldSegmentBoundaryComponent(children[i]!)) boundaries.push(i);
2974+
if (isFoldSegmentBoundaryComponent(children[i]!)) boundaries.push(i);
30052975
}
30062976
if (boundaries.length === 0) return;
30072977

@@ -3297,7 +3267,7 @@ export class PythinkerTUI {
32973267
// components that have no entry in the metadata map.
32983268
const boundaries: number[] = [];
32993269
for (let i = 0; i < children.length; i++) {
3300-
if (this.isTurnBoundaryComponent(children[i]!)) boundaries.push(i);
3270+
if (isTurnBoundaryComponent(children[i]!)) boundaries.push(i);
33013271
}
33023272
const expandCutoff =
33033273
TRANSCRIPT_EXPAND_TURNS <= 0

apps/pythinker-code/src/tui/utils/transcript-component-metadata.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { Component } from '@pymodel/pi-tui';
22

3+
import { CronMessageComponent } from '../components/messages/cron-message';
4+
import { PluginCommandComponent } from '../components/messages/plugin-command';
5+
import { SkillActivationComponent } from '../components/messages/skill-activation';
6+
import { ReplayTurnBoundaryComponent, UserMessageComponent } from '../components/messages/user-message';
37
import type { TranscriptEntry } from '../types';
48

59
const componentEntries = new WeakMap<Component, TranscriptEntry>();
@@ -13,3 +17,34 @@ export function getTranscriptComponentEntry(
1317
): TranscriptEntry | undefined {
1418
return componentEntries.get(component);
1519
}
20+
21+
/**
22+
* Turn boundary: the component that starts a new user turn in the transcript.
23+
* Live user messages / slash activations have an undefined turnId; replayed
24+
* ones get a `replay:N` turnId. Both start a new turn. Steer messages carry a
25+
* defined non-replay turnId and are not boundaries.
26+
*/
27+
export function isTurnBoundaryComponent(child: Component): boolean {
28+
if (
29+
!(child instanceof UserMessageComponent) &&
30+
!(child instanceof SkillActivationComponent) &&
31+
!(child instanceof PluginCommandComponent) &&
32+
!(child instanceof ReplayTurnBoundaryComponent)
33+
) {
34+
return false;
35+
}
36+
const entry = getTranscriptComponentEntry(child);
37+
if (entry === undefined) return false;
38+
return entry.turnId === undefined || entry.turnId.startsWith('replay:');
39+
}
40+
41+
/**
42+
* Fold-segment boundary: everything {@link isTurnBoundaryComponent} counts,
43+
* plus the cron card. A cron-fired turn mounts no user message, so without the
44+
* card as a boundary its output would share the previous user turn's fold
45+
* segment - and the completed-turn assistant cap would fold that turn's final
46+
* answer into the step summary.
47+
*/
48+
export function isFoldSegmentBoundaryComponent(child: Component): boolean {
49+
return isTurnBoundaryComponent(child) || child instanceof CronMessageComponent;
50+
}

0 commit comments

Comments
 (0)