mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Align chat naming conventions
This commit is contained in:
parent
0062590cba
commit
43e8d7fc20
35 changed files with 245 additions and 233 deletions
|
|
@ -28,10 +28,18 @@ The source tree is organized by responsibility rather than by the original singl
|
|||
- `src/domain/` contains panel-owned, generated-independent meaning models and pure derivations shared by app-server, features, workspace, and settings code. Domain code must not import app-server protocol, generated bindings, feature modules, UI, or Obsidian APIs; app-server protocol and service modules adapt generated payloads into domain models at the boundary.
|
||||
- `src/styles/` contains the authored CSS modules and `order.json` concatenation order. `scripts/build-styles.mjs` concatenates them into the ignored root `styles.css`, which remains the Obsidian release asset.
|
||||
|
||||
## Implementation Conventions
|
||||
|
||||
Keep new code near the state or API it owns. A feature may import another feature only for a capability that feature owns; feature-neutral behavior belongs in `src/shared/`, `src/domain/`, or `src/app-server/`. Lower-level modules must not import `src/features/`, and generated app-server types should stay behind `src/app-server/`.
|
||||
|
||||
Codex Panel's runtime UI is Preact-owned. Use imperative DOM writes only for explicit bridge modules, Obsidian-owned API boundaries, or rendering and measurement code that cannot be expressed cleanly as Preact components. Chat panel-visible state belongs in `ChatStateStore` and should flow through named reducer actions and the shell-state adapter. Revisit the project design notes before adding a parallel UI state or runtime path.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
Name modules by responsibility: use `Controller` only for stateful lifecycle/control surfaces, `Handler` for inbound event or request entrypoints, `Actions` for caller-facing command or callback bundles without lifecycle ownership, `Coordinator` for stateful background or cross-surface coordination, `Service` for reusable domain capabilities, `Presenter` for UI state projection, `Renderer` for render-only UI contracts, and `Host`/`Ports` for dependency boundary objects. Use `State`, `Snapshot`, `Projection`, `ViewModel`, `Options`, `Context`, `Result`, `Target`, `Capabilities`, or `ActionTargets` for data/value objects; do not use `Actions` for passive data. Use boundary/infrastructure nouns such as `Client`, `Transport`, `Cache`, `Store`, `Catalog`, `Manager`, `Bridge`, `Tracker`, `Session`, `Runtime`, `Provider`, or `Adapter` only when the object owns that concrete boundary or lifecycle role.
|
||||
|
||||
Prefer functions and factory-created objects over classes. Use a class only when it owns mutable lifecycle/resource state, extends or implements an external class-based API such as Obsidian views/modals/tabs, or represents an `Error`; do not use a class merely to group pure functions or dependencies.
|
||||
|
||||
## App-Server Bindings
|
||||
|
||||
The app-server TypeScript bindings in `src/generated/app-server/` are generated from the installed Codex CLI:
|
||||
|
|
|
|||
|
|
@ -170,15 +170,15 @@ const imperativeDomAssignmentProperties = new Set([
|
|||
const pureChatStateRestrictions = [
|
||||
{
|
||||
selector: "CallExpression[callee.object.name='Date'][callee.property.name='now']",
|
||||
message: "Keep chat state transforms deterministic; generate IDs or timestamps at the controller/view boundary.",
|
||||
message: "Keep chat state transforms deterministic; generate IDs or timestamps at an application or UI boundary.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name='Date']",
|
||||
message: "Keep chat state transforms deterministic; pass dates in from the controller/view boundary.",
|
||||
message: "Keep chat state transforms deterministic; pass dates in from an application or UI boundary.",
|
||||
},
|
||||
{
|
||||
selector: "CallExpression[callee.object.name='Math'][callee.property.name='random']",
|
||||
message: "Keep chat state transforms deterministic; generate IDs at the controller/view boundary.",
|
||||
message: "Keep chat state transforms deterministic; generate IDs at an application or UI boundary.",
|
||||
},
|
||||
{
|
||||
selector: "NewExpression[callee.name=/^(AppServerClient|ConnectionManager|Notice)$/]",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface ComposerSubmitActionsHost {
|
|||
readonly trimmedDraft: string;
|
||||
setDraft(text: string, options?: { clearSuggestions?: boolean; focus?: boolean }): void;
|
||||
};
|
||||
slashCommands: {
|
||||
slashCommandExecutor: {
|
||||
execute(command: SlashCommandName, args: string): Promise<SlashCommandExecutionResult | undefined>;
|
||||
};
|
||||
turnSubmission: {
|
||||
|
|
@ -58,7 +58,7 @@ async function sendMessage(host: ComposerSubmitActionsHost): Promise<void> {
|
|||
const slashCommand = parseSlashCommand(text);
|
||||
if (slashCommand) {
|
||||
host.composer.setDraft("", { clearSuggestions: true });
|
||||
const result = await host.slashCommands.execute(slashCommand.command, slashCommand.args);
|
||||
const result = await host.slashCommandExecutor.execute(slashCommand.command, slashCommand.args);
|
||||
if (result?.composerDraft !== undefined) {
|
||||
host.composer.setDraft(result.composerDraft, { focus: true, clearSuggestions: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { ChatStateStore } from "../state/store";
|
|||
import type { ThreadManagementActions } from "../threads/thread-management-actions";
|
||||
import type { GoalActions } from "../threads/goal-actions";
|
||||
import { submitComposer, type ComposerSubmitActions, type ComposerSubmitActionsHost } from "./composer-submit-actions";
|
||||
import { executeSlashCommandWithState, type SlashCommandHandlerHost } from "./slash-command-handler";
|
||||
import { executeSlashCommandWithState, type SlashCommandExecutorHost } from "./slash-command-executor";
|
||||
import { createTurnSubmissionActions } from "./turn-submission-actions";
|
||||
|
||||
const IMPLEMENT_PLAN_PROMPT = "Please implement this plan.";
|
||||
|
|
@ -99,7 +99,7 @@ export function createConversationTurnActions(
|
|||
setStatus: status.set,
|
||||
addSystemMessage: status.addSystemMessage,
|
||||
});
|
||||
const slashCommandHost: SlashCommandHandlerHost = {
|
||||
const slashCommandExecutorHost: SlashCommandExecutorHost = {
|
||||
stateStore,
|
||||
currentClient: client.currentClient,
|
||||
codexInput: composer.codexInput,
|
||||
|
|
@ -136,8 +136,8 @@ export function createConversationTurnActions(
|
|||
},
|
||||
setDraft: composer.setDraft,
|
||||
},
|
||||
slashCommands: {
|
||||
execute: (command, args) => executeSlashCommandWithState(slashCommandHost, command, args),
|
||||
slashCommandExecutor: {
|
||||
execute: (command, args) => executeSlashCommandWithState(slashCommandExecutorHost, command, args),
|
||||
},
|
||||
turnSubmission,
|
||||
connection: {
|
||||
|
|
@ -161,8 +161,8 @@ export function createConversationTurnActions(
|
|||
};
|
||||
}
|
||||
|
||||
async function startThreadForGoal(actions: ConversationThreadStarter, objective: string): Promise<string | null> {
|
||||
const response = await actions.startThread(objective, { syncGoal: false });
|
||||
async function startThreadForGoal(starter: ConversationThreadStarter, objective: string): Promise<string | null> {
|
||||
const response = await starter.startThread(objective, { syncGoal: false });
|
||||
return response?.threadId ?? null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type { MessageStreamAuditFact, MessageStreamNoticeSection } from "../../d
|
|||
|
||||
const DEFAULT_RUNTIME_SETTING_ALIASES = new Set(["default", "reset", "clear", "off"]);
|
||||
|
||||
export interface SlashCommandActionPorts {
|
||||
export interface SlashCommandExecutionPorts {
|
||||
startNewThread: () => Promise<void>;
|
||||
startThreadForGoal: (objective: string) => Promise<string | null>;
|
||||
resumeThread: (threadId: string) => Promise<void>;
|
||||
|
|
@ -56,7 +56,7 @@ export interface SlashCommandActionPorts {
|
|||
effortStatusLines: () => string[];
|
||||
}
|
||||
|
||||
export interface SlashCommandExecutionContext extends SlashCommandActionPorts {
|
||||
export interface SlashCommandExecutionContext extends SlashCommandExecutionPorts {
|
||||
activeThreadId: string | null;
|
||||
busy: boolean;
|
||||
listedThreads: readonly Thread[];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { Thread } from "../../../../domain/threads/model";
|
|||
import { shortThreadId } from "../../../../utils";
|
||||
import {
|
||||
executeSlashCommand as runSlashCommand,
|
||||
type SlashCommandActionPorts,
|
||||
type SlashCommandExecutionPorts,
|
||||
type SlashCommandExecutionResult,
|
||||
type ThreadReferenceInput,
|
||||
} from "./slash-command-execution";
|
||||
|
|
@ -18,7 +18,7 @@ import type { ChatStateStore } from "../state/store";
|
|||
import { currentModel, runtimeConfigOrDefault } from "../../domain/runtime/effective";
|
||||
import { runtimeSnapshotForChatState } from "../runtime/snapshot";
|
||||
|
||||
export interface SlashCommandHandlerHost extends SlashCommandActionPorts {
|
||||
export interface SlashCommandExecutorHost extends SlashCommandExecutionPorts {
|
||||
stateStore: ChatStateStore;
|
||||
currentClient: () => AppServerClient | null;
|
||||
codexInput: (text: string) => CodexInput;
|
||||
|
|
@ -34,7 +34,7 @@ function referencedThreadUnreadableMessage(): string {
|
|||
}
|
||||
|
||||
export async function executeSlashCommandWithState(
|
||||
host: SlashCommandHandlerHost,
|
||||
host: SlashCommandExecutorHost,
|
||||
command: SlashCommandName,
|
||||
args: string,
|
||||
): Promise<SlashCommandExecutionResult | undefined> {
|
||||
|
|
@ -61,7 +61,7 @@ function supportedReasoningEfforts(state: ReturnType<ChatStateStore["getState"]>
|
|||
}
|
||||
|
||||
async function referencedThreadInput(
|
||||
host: SlashCommandHandlerHost,
|
||||
host: SlashCommandExecutorHost,
|
||||
client: AppServerClient,
|
||||
thread: Thread,
|
||||
message: string,
|
||||
|
|
@ -709,7 +709,7 @@ export function cloneChatState(state: ChatState): ChatState {
|
|||
archiveConfirmThreadId: state.ui.archiveConfirmThreadId,
|
||||
rename: { ...state.ui.rename },
|
||||
goalEditor: { ...state.ui.goalEditor },
|
||||
messageActions: { ...state.ui.messageActions },
|
||||
messageActionMenu: { ...state.ui.messageActionMenu },
|
||||
disclosures: cloneDisclosureUiState(state.ui.disclosures),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ type ChatGoalEditorUiState =
|
|||
readonly tokenBudgetDraft: number | null;
|
||||
};
|
||||
|
||||
interface ChatMessageActionsUiState {
|
||||
readonly forkActionsItemId: string | null;
|
||||
interface ChatMessageActionMenuUiState {
|
||||
readonly forkMenuItemId: string | null;
|
||||
}
|
||||
|
||||
const CHAT_DISCLOSURE_BUCKETS = [
|
||||
|
|
@ -46,7 +46,7 @@ export interface ChatUiState {
|
|||
readonly archiveConfirmThreadId: string | null;
|
||||
readonly rename: ChatRenameUiState;
|
||||
readonly goalEditor: ChatGoalEditorUiState;
|
||||
readonly messageActions: ChatMessageActionsUiState;
|
||||
readonly messageActionMenu: ChatMessageActionMenuUiState;
|
||||
readonly disclosures: ChatDisclosureUiState;
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ export type UiAction =
|
|||
| { type: "ui/goal-editor-started"; threadId: string | null; objective: string; tokenBudget: number | null }
|
||||
| { type: "ui/goal-editor-draft-updated"; objective: string }
|
||||
| { type: "ui/goal-editor-closed" }
|
||||
| { type: "ui/message-fork-actions-set"; itemId: string | null }
|
||||
| { type: "ui/message-fork-menu-set"; itemId: string | null }
|
||||
| DisclosureSetAction;
|
||||
|
||||
export function initialUiState(): ChatUiState {
|
||||
|
|
@ -76,7 +76,7 @@ export function initialUiState(): ChatUiState {
|
|||
archiveConfirmThreadId: null,
|
||||
rename: initialRenameUiState(),
|
||||
goalEditor: initialGoalEditorUiState(),
|
||||
messageActions: initialMessageActionsUiState(),
|
||||
messageActionMenu: initialMessageActionMenuUiState(),
|
||||
disclosures: initialDisclosureUiState(),
|
||||
};
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ export function isUiAction(action: { type: string }): action is UiAction {
|
|||
case "ui/goal-editor-started":
|
||||
case "ui/goal-editor-draft-updated":
|
||||
case "ui/goal-editor-closed":
|
||||
case "ui/message-fork-actions-set":
|
||||
case "ui/message-fork-menu-set":
|
||||
case "ui/disclosure-set":
|
||||
return true;
|
||||
default:
|
||||
|
|
@ -138,8 +138,8 @@ export function reduceUiSlice(state: ChatUiState, action: UiAction): ChatUiState
|
|||
return patchObject(state, { goalEditor: goalEditorDraftUpdated(state.goalEditor, action.objective) });
|
||||
case "ui/goal-editor-closed":
|
||||
return patchObject(state, { goalEditor: initialGoalEditorUiState() });
|
||||
case "ui/message-fork-actions-set":
|
||||
return patchObject(state, { messageActions: { forkActionsItemId: action.itemId } });
|
||||
case "ui/message-fork-menu-set":
|
||||
return patchObject(state, { messageActionMenu: { forkMenuItemId: action.itemId } });
|
||||
case "ui/disclosure-set":
|
||||
return setDisclosureSlice(state, action.bucket, action.id, action.open);
|
||||
}
|
||||
|
|
@ -228,8 +228,8 @@ function initialGoalEditorUiState(): ChatGoalEditorUiState {
|
|||
return { kind: "closed" };
|
||||
}
|
||||
|
||||
function initialMessageActionsUiState(): ChatMessageActionsUiState {
|
||||
return { forkActionsItemId: null };
|
||||
function initialMessageActionMenuUiState(): ChatMessageActionMenuUiState {
|
||||
return { forkMenuItemId: null };
|
||||
}
|
||||
|
||||
function initialDisclosureUiState(): ChatDisclosureUiState {
|
||||
|
|
|
|||
|
|
@ -5,19 +5,19 @@ import type { ThreadTitleService } from "../../../threads/thread-title-service";
|
|||
import type { ChatState } from "../state/root-reducer";
|
||||
import type { ChatStateStore } from "../state/store";
|
||||
|
||||
export interface AutoTitleActionsHost {
|
||||
export interface AutoTitleCoordinatorHost {
|
||||
stateStore: ChatStateStore;
|
||||
completedTurnTitleContext: ThreadTitleService["completedTurnContext"];
|
||||
generateTitleFromContext: ThreadTitleService["generate"];
|
||||
renameGeneratedTitle(threadId: string, title: string, options: { shouldPublish: () => boolean }): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface AutoTitleActions {
|
||||
export interface AutoTitleCoordinator {
|
||||
resetThreadTurnPresence(hadTurns: boolean): void;
|
||||
maybeAutoTitleThread(threadId: string, turnId: string, completedSummary: ThreadConversationSummary | null): void;
|
||||
}
|
||||
|
||||
export function createAutoTitleActions(host: AutoTitleActionsHost): AutoTitleActions {
|
||||
export function createAutoTitleCoordinator(host: AutoTitleCoordinatorHost): AutoTitleCoordinator {
|
||||
let activeThreadHadTurns = false;
|
||||
const attemptedThreadIds = new Set<string>();
|
||||
const inFlightThreadIds = new Set<string>();
|
||||
|
|
@ -65,6 +65,6 @@ export function createAutoTitleActions(host: AutoTitleActionsHost): AutoTitleAct
|
|||
};
|
||||
}
|
||||
|
||||
function state(host: AutoTitleActionsHost): ChatState {
|
||||
function state(host: AutoTitleCoordinatorHost): ChatState {
|
||||
return host.stateStore.getState();
|
||||
}
|
||||
|
|
@ -12,18 +12,18 @@ export interface PlanImplementationTarget {
|
|||
|
||||
export function forkCandidatesFromItems(items: readonly MessageStreamItem[]): readonly ForkCandidate[] {
|
||||
const turnOutcomeItemsByTurn = new Map<string, ForkCandidate>();
|
||||
for (const { item, actions } of messageStreamSemanticClassifications(items)) {
|
||||
if (!item.turnId || !actions.isTurnOutcome) continue;
|
||||
for (const { item, capabilities } of messageStreamSemanticClassifications(items)) {
|
||||
if (!item.turnId || !capabilities.isTurnOutcome) continue;
|
||||
turnOutcomeItemsByTurn.set(item.turnId, { itemId: item.id, turnId: item.turnId });
|
||||
}
|
||||
return [...turnOutcomeItemsByTurn.values()];
|
||||
}
|
||||
|
||||
export function latestImplementablePlanTargetFromItems(items: readonly MessageStreamItem[]): PlanImplementationTarget | null {
|
||||
const classification = [...messageStreamSemanticClassifications(items)].reverse().find((item) => item.actions.canImplementPlan);
|
||||
const classification = [...messageStreamSemanticClassifications(items)].reverse().find((item) => item.capabilities.canImplementPlan);
|
||||
return classification ? { itemId: classification.item.id } : null;
|
||||
}
|
||||
|
||||
export function isCompletedTurnOutcomeMessage(item: MessageStreamItem): boolean {
|
||||
return messageStreamSemanticClassifications([item])[0]?.actions.isTurnOutcome ?? false;
|
||||
return messageStreamSemanticClassifications([item])[0]?.capabilities.isTurnOutcome ?? false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type {
|
|||
MessageStreamLifecycle,
|
||||
MessageStreamMeaning,
|
||||
MessageStreamPlacement,
|
||||
MessageStreamSemanticActions,
|
||||
MessageStreamSemanticCapabilities,
|
||||
MessageStreamSemanticClassification,
|
||||
} from "./types";
|
||||
|
||||
|
|
@ -26,13 +26,13 @@ function messageStreamSemanticClassification(
|
|||
placement,
|
||||
meaning,
|
||||
...definedProp("lifecycle", lifecycle),
|
||||
actions: messageStreamSemanticActions({ item, placement, meaning, ...definedProp("lifecycle", lifecycle) }),
|
||||
capabilities: messageStreamSemanticCapabilities({ item, placement, meaning, ...definedProp("lifecycle", lifecycle) }),
|
||||
};
|
||||
}
|
||||
|
||||
function messageStreamSemanticActions(
|
||||
function messageStreamSemanticCapabilities(
|
||||
classification: Pick<MessageStreamSemanticClassification, "item" | "placement" | "meaning" | "lifecycle">,
|
||||
): MessageStreamSemanticActions {
|
||||
): MessageStreamSemanticCapabilities {
|
||||
const { item, placement, meaning, lifecycle } = classification;
|
||||
const isDialogueOutcome =
|
||||
placement.scope === "turn" &&
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export interface MessageStreamLifecycle {
|
|||
state: Exclude<ExecutionState, null>;
|
||||
}
|
||||
|
||||
export interface MessageStreamSemanticActions {
|
||||
export interface MessageStreamSemanticCapabilities {
|
||||
canForkFromHere: boolean;
|
||||
canRollbackToPrompt: boolean;
|
||||
canImplementPlan: boolean;
|
||||
|
|
@ -69,5 +69,5 @@ export interface MessageStreamSemanticClassification {
|
|||
placement: MessageStreamPlacement;
|
||||
meaning: MessageStreamMeaning;
|
||||
lifecycle?: MessageStreamLifecycle;
|
||||
actions: MessageStreamSemanticActions;
|
||||
capabilities: MessageStreamSemanticCapabilities;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type { ChatViewDeferredTasks } from "../application/lifecycle";
|
|||
import type { ChatConnectionPhase } from "../application/state/root-reducer";
|
||||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { createThreadGoalSyncActions } from "../application/threads/goal-actions";
|
||||
import type { AutoTitleActions } from "../application/threads/auto-title-actions";
|
||||
import type { AutoTitleCoordinator } from "../application/threads/auto-title-coordinator";
|
||||
import { runtimeSnapshotForChatState } from "../application/runtime/snapshot";
|
||||
import { createChatServerDiagnosticsActions, type ChatServerDiagnosticsActions } from "../app-server/actions/diagnostics";
|
||||
import { createChatServerMetadataActions, type ChatServerMetadataActions } from "../app-server/actions/metadata";
|
||||
|
|
@ -39,7 +39,7 @@ interface ChatPanelConnectionBundleInput {
|
|||
localItemIds: LocalIdSource;
|
||||
status: ChatPanelConnectionStatus;
|
||||
goalSync: ChatPanelGoalSyncActions;
|
||||
autoTitle: AutoTitleActions;
|
||||
autoTitleCoordinator: AutoTitleCoordinator;
|
||||
}
|
||||
|
||||
interface ChatPanelConnectionBundleHost {
|
||||
|
|
@ -97,7 +97,7 @@ export function createConnectionBundle(
|
|||
input: ChatPanelConnectionBundleInput,
|
||||
): ChatPanelConnectionBundle {
|
||||
const { environment, stateStore } = host;
|
||||
const { connection, currentClient, localItemIds, status, goalSync, autoTitle } = input;
|
||||
const { connection, currentClient, localItemIds, status, goalSync, autoTitleCoordinator } = input;
|
||||
const serverMetadata = createChatServerMetadataActions({
|
||||
stateStore,
|
||||
vaultPath: environment.plugin.settingsRef.vaultPath,
|
||||
|
|
@ -149,7 +149,7 @@ export function createConnectionBundle(
|
|||
serverMetadata.applyAppServerMetadataSnapshot();
|
||||
},
|
||||
maybeNameThread: (threadId, turnId, completedSummary) => {
|
||||
autoTitle.maybeAutoTitleThread(threadId, turnId, completedSummary);
|
||||
autoTitleCoordinator.maybeAutoTitleThread(threadId, turnId, completedSummary);
|
||||
},
|
||||
upsertActiveThread: (thread) => {
|
||||
environment.plugin.threadCatalog.upsertFromAppServer(thread);
|
||||
|
|
@ -179,7 +179,7 @@ export function createConnectionBundle(
|
|||
},
|
||||
setStatus: status.set,
|
||||
resetThreadTurnPresence: (hadTurns: boolean) => {
|
||||
autoTitle.resetThreadTurnPresence(hadTurns);
|
||||
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
refreshLiveState: () => {
|
||||
host.refreshLiveState();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { messageStreamItems } from "../application/state/message-stream";
|
|||
import type { ChatStateStore } from "../application/state/store";
|
||||
import type { ChatResumeWorkTracker, ChatViewDeferredTasks } from "../application/lifecycle";
|
||||
import { createGoalActions, createThreadGoalSyncActions } from "../application/threads/goal-actions";
|
||||
import { createAutoTitleActions, type AutoTitleActions } from "../application/threads/auto-title-actions";
|
||||
import { createAutoTitleCoordinator, type AutoTitleCoordinator } from "../application/threads/auto-title-coordinator";
|
||||
import { HistoryController } from "../application/threads/history-controller";
|
||||
import type { IdentitySync } from "../application/threads/identity-sync";
|
||||
import { createThreadLifecycleParts } from "../application/threads/lifecycle-parts";
|
||||
|
|
@ -171,8 +171,8 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
const currentClient = () => connection.currentClient();
|
||||
const status = createSessionStatus(stateStore, localItemIds);
|
||||
const titleService = createSessionThreadTitleService(host, currentClient);
|
||||
const autoTitle = createSessionAutoTitleActions(host, currentClient, titleService);
|
||||
const history = createSessionHistoryController(host, currentClient, status, autoTitle);
|
||||
const autoTitleCoordinator = createSessionAutoTitleCoordinator(host, currentClient, titleService);
|
||||
const history = createSessionHistoryController(host, currentClient, status, autoTitleCoordinator);
|
||||
const invalidateThreadWork = () => {
|
||||
host.resumeWork.invalidate();
|
||||
history.invalidate();
|
||||
|
|
@ -200,7 +200,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
currentClient,
|
||||
localItemIds,
|
||||
goalSync,
|
||||
autoTitle,
|
||||
autoTitleCoordinator,
|
||||
status,
|
||||
},
|
||||
);
|
||||
|
|
@ -222,7 +222,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
ensureConnected,
|
||||
status,
|
||||
goals,
|
||||
autoTitle,
|
||||
autoTitleCoordinator,
|
||||
history,
|
||||
invalidateThreadWork,
|
||||
);
|
||||
|
|
@ -263,7 +263,7 @@ export function createChatPanelSessionGraph(host: ChatPanelSessionGraphHost): Ch
|
|||
serverThreads,
|
||||
serverDiagnostics,
|
||||
goals,
|
||||
autoTitle,
|
||||
autoTitleCoordinator,
|
||||
invalidateThreadWork,
|
||||
startNewThread,
|
||||
runtimeProjection: {
|
||||
|
|
@ -421,12 +421,12 @@ function createSessionThreadTitleService(host: ChatPanelSessionGraphHost, curren
|
|||
});
|
||||
}
|
||||
|
||||
function createSessionAutoTitleActions(
|
||||
function createSessionAutoTitleCoordinator(
|
||||
host: ChatPanelSessionGraphHost,
|
||||
currentClient: CurrentAppServerClient,
|
||||
titleService: ThreadTitleService,
|
||||
): AutoTitleActions {
|
||||
return createAutoTitleActions({
|
||||
): AutoTitleCoordinator {
|
||||
return createAutoTitleCoordinator({
|
||||
stateStore: host.stateStore,
|
||||
completedTurnTitleContext: (turnId, completedSummary) => titleService.completedTurnContext(turnId, completedSummary),
|
||||
generateTitleFromContext: (context) => titleService.generate(context),
|
||||
|
|
@ -450,7 +450,7 @@ function createSessionHistoryController(
|
|||
host: ChatPanelSessionGraphHost,
|
||||
currentClient: CurrentAppServerClient,
|
||||
status: ChatPanelSessionStatus,
|
||||
autoTitle: AutoTitleActions,
|
||||
autoTitleCoordinator: AutoTitleCoordinator,
|
||||
): HistoryController {
|
||||
return new HistoryController({
|
||||
stateStore: host.stateStore,
|
||||
|
|
@ -463,7 +463,7 @@ function createSessionHistoryController(
|
|||
host.messageScrollIntent.forceBottom();
|
||||
},
|
||||
setThreadTurnPresence: (hadTurns) => {
|
||||
autoTitle.resetThreadTurnPresence(hadTurns);
|
||||
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -584,7 +584,7 @@ function createSessionThreadLifecycle(
|
|||
ensureConnected: () => Promise<void>,
|
||||
status: ChatPanelSessionStatus,
|
||||
goals: ChatPanelGoalActions,
|
||||
autoTitle: AutoTitleActions,
|
||||
autoTitleCoordinator: AutoTitleCoordinator,
|
||||
history: HistoryController,
|
||||
invalidateThreadWork: () => void,
|
||||
): ChatPanelThreadLifecycle {
|
||||
|
|
@ -619,7 +619,7 @@ function createSessionThreadLifecycle(
|
|||
},
|
||||
goals,
|
||||
resetThreadTurnPresence: (hadTurns) => {
|
||||
autoTitle.resetThreadTurnPresence(hadTurns);
|
||||
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -747,7 +747,7 @@ function createComposerAndTurnActions(
|
|||
serverThreads: ChatServerThreadActions;
|
||||
serverDiagnostics: ChatServerDiagnosticsActions;
|
||||
goals: ChatPanelGoalActions;
|
||||
autoTitle: AutoTitleActions;
|
||||
autoTitleCoordinator: AutoTitleCoordinator;
|
||||
invalidateThreadWork: () => void;
|
||||
startNewThread: () => Promise<void>;
|
||||
runtimeProjection: {
|
||||
|
|
@ -773,7 +773,7 @@ function createComposerAndTurnActions(
|
|||
serverThreads,
|
||||
serverDiagnostics,
|
||||
goals,
|
||||
autoTitle,
|
||||
autoTitleCoordinator,
|
||||
invalidateThreadWork,
|
||||
startNewThread,
|
||||
runtimeProjection,
|
||||
|
|
@ -836,7 +836,7 @@ function createComposerAndTurnActions(
|
|||
notifyActiveThreadIdentityChanged(host);
|
||||
},
|
||||
resetTurnPresence: (hadTurns) => {
|
||||
autoTitle.resetThreadTurnPresence(hadTurns);
|
||||
autoTitleCoordinator.resetThreadTurnPresence(hadTurns);
|
||||
},
|
||||
},
|
||||
composer: {
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export class ChatPanelSession implements ChatSurfaceHandle {
|
|||
goal: this.graph.surface.goal,
|
||||
messageStream: this.graph.render.messageStreamPresenter,
|
||||
composer: {
|
||||
controller: this.graph.composer.controller,
|
||||
renderer: this.graph.composer.controller,
|
||||
actions: {
|
||||
submit: () => void this.graph.composer.submission.submit(),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ChatPanelToolbar, type ChatPanelToolbarSurface } from "./surface/toolba
|
|||
import type { ToolbarActions } from "../ui/toolbar";
|
||||
import { ChatPanelGoal, type ChatPanelGoalSurface } from "./surface/goal-projection";
|
||||
import { ChatPanelMessageStream, type ChatPanelMessageStreamPresenter } from "./surface/message-stream-presenter";
|
||||
import { ChatPanelComposer, type ChatPanelComposerActions, type ChatPanelComposerController } from "./surface/composer-projection";
|
||||
import { ChatPanelComposer, type ChatPanelComposerActions, type ChatPanelComposerRenderer } from "./surface/composer-projection";
|
||||
import { ChatPanelShellStateContext, createChatPanelShellState, syncChatPanelShellState, type ChatPanelShellState } from "./shell-state";
|
||||
|
||||
export interface ChatPanelShellParts {
|
||||
|
|
@ -16,7 +16,7 @@ export interface ChatPanelShellParts {
|
|||
goal: ChatPanelGoalSurface;
|
||||
messageStream: ChatPanelMessageStreamPresenter;
|
||||
composer: {
|
||||
controller: ChatPanelComposerController;
|
||||
renderer: ChatPanelComposerRenderer;
|
||||
actions: ChatPanelComposerActions;
|
||||
};
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ function ChatPanelShell({ showToolbar, parts, shellState }: ChatPanelShellProps
|
|||
</div>
|
||||
<ChatPanelMessageStream presenter={parts.messageStream} />
|
||||
<div className="codex-panel__region codex-panel__region--composer" data-codex-panel-shell-region="composer">
|
||||
<ChatPanelComposer controller={parts.composer.controller} actions={parts.composer.actions} />
|
||||
<ChatPanelComposer renderer={parts.composer.renderer} actions={parts.composer.actions} />
|
||||
</div>
|
||||
</div>
|
||||
</ChatPanelShellStateContext.Provider>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export interface ChatPanelComposerSurface {
|
|||
};
|
||||
}
|
||||
|
||||
export interface ChatPanelComposerController {
|
||||
export interface ChatPanelComposerRenderer {
|
||||
renderState(state: ChatPanelComposerShellState, actions: ChatPanelComposerActions): ComposerShellProps;
|
||||
}
|
||||
|
||||
|
|
@ -91,14 +91,14 @@ function composerPlaceholder(threadName: string | null): string {
|
|||
}
|
||||
|
||||
export function ChatPanelComposer({
|
||||
controller,
|
||||
renderer,
|
||||
actions,
|
||||
}: {
|
||||
controller: ChatPanelComposerController;
|
||||
renderer: ChatPanelComposerRenderer;
|
||||
actions: ChatPanelComposerActions;
|
||||
}): UiNode {
|
||||
const state = composerStateFromShellState(useChatPanelShellState());
|
||||
return h(ComposerShell, controller.renderState(state, actions));
|
||||
return h(ComposerShell, renderer.renderState(state, actions));
|
||||
}
|
||||
|
||||
export function chatPanelComposerProjection(
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { ChatPanelMessageStreamShellState } from "../shell-state";
|
|||
import { pendingRequestBlockSnapshotFromState } from "../../presentation/pending-requests/snapshot";
|
||||
import type { PendingRequestBlockActions, PendingRequestBlockState } from "../../application/pending-requests/block";
|
||||
import type { ChatTurnDiffViewState } from "../../domain/turn-diff";
|
||||
import type { MessageStreamTextActions } from "../../presentation/message-stream/text-view";
|
||||
import type { MessageStreamTextActionTargets } from "../../presentation/message-stream/text-view";
|
||||
|
||||
interface ChatMessageStreamActions {
|
||||
rollbackThread: (threadId: string) => void;
|
||||
|
|
@ -40,7 +40,7 @@ interface ChatMessageStreamRequests {
|
|||
export interface ChatMessageStreamSurfaceContext {
|
||||
vaultPath: string;
|
||||
setDisclosureOpen: (bucket: ChatDisclosureBucket, id: string, open: boolean) => void;
|
||||
setForkActionsItem: (itemId: string | null) => void;
|
||||
setForkMenuItem: (itemId: string | null) => void;
|
||||
loadOlderTurns: () => void;
|
||||
renderObsidianMarkdown: (element: HTMLElement, text: string) => void;
|
||||
renderStreamMarkdown: (element: HTMLElement, text: string) => void;
|
||||
|
|
@ -64,7 +64,7 @@ interface MessageStreamStateProjection {
|
|||
activeThreadId: string | null;
|
||||
workspaceRoot: string;
|
||||
disclosures: ChatDisclosureUiState;
|
||||
forkActionsItemId: string | null;
|
||||
forkMenuItemId: string | null;
|
||||
viewBlocks: readonly MessageStreamViewBlock[];
|
||||
}
|
||||
|
||||
|
|
@ -79,8 +79,8 @@ export function createMessageStreamSurfaceContext(options: MessageStreamSurfaceC
|
|||
setDisclosureOpen: (bucket, id, open) => {
|
||||
options.dispatch({ type: "ui/disclosure-set", bucket, id, open });
|
||||
},
|
||||
setForkActionsItem: (itemId) => {
|
||||
options.dispatch({ type: "ui/message-fork-actions-set", itemId });
|
||||
setForkMenuItem: (itemId) => {
|
||||
options.dispatch({ type: "ui/message-fork-menu-set", itemId });
|
||||
},
|
||||
loadOlderTurns: options.loadOlderTurns,
|
||||
renderObsidianMarkdown: options.renderObsidianMarkdown,
|
||||
|
|
@ -111,8 +111,8 @@ function messageStreamContextFromProjection(
|
|||
workspaceRoot: projection.workspaceRoot,
|
||||
disclosures: projection.disclosures,
|
||||
onDisclosureToggle: context.setDisclosureOpen,
|
||||
forkActionsItemId: projection.forkActionsItemId,
|
||||
onForkActionsToggle: context.setForkActionsItem,
|
||||
forkMenuItemId: projection.forkMenuItemId,
|
||||
onForkMenuToggle: context.setForkMenuItem,
|
||||
loadOlderTurns: context.loadOlderTurns,
|
||||
renderObsidianMarkdown: context.renderObsidianMarkdown,
|
||||
renderStreamMarkdown: context.renderStreamMarkdown,
|
||||
|
|
@ -152,14 +152,14 @@ function messageStreamStateProjection(
|
|||
const rollbackCandidate = busy ? null : messageStreamRollbackCandidate(state.messageStream);
|
||||
const forkCandidates = busy ? [] : forkCandidatesFromItems(items);
|
||||
const implementPlanTarget = implementPlanTargetFromState(state);
|
||||
const textActionsByItemId = textActionsForMessageStreamItems(rollbackCandidate, forkCandidates, implementPlanTarget);
|
||||
const textActionTargetsByItemId = textActionTargetsForMessageStreamItems(rollbackCandidate, forkCandidates, implementPlanTarget);
|
||||
const activeTurn = activeTurnId({ lifecycle: state.turn.lifecycle });
|
||||
|
||||
return {
|
||||
activeThreadId: state.activeThread.id,
|
||||
workspaceRoot,
|
||||
disclosures: state.ui.disclosures,
|
||||
forkActionsItemId: state.ui.messageActions.forkActionsItemId,
|
||||
forkMenuItemId: state.ui.messageActionMenu.forkMenuItemId,
|
||||
viewBlocks: messageStreamViewBlocks({
|
||||
activeThreadId: state.activeThread.id,
|
||||
activeTurnId: activeTurn,
|
||||
|
|
@ -170,31 +170,35 @@ function messageStreamStateProjection(
|
|||
activeItems,
|
||||
workspaceRoot,
|
||||
turnDiffs: state.messageStream.turnDiffs,
|
||||
textActionsByItemId,
|
||||
textActionTargetsByItemId,
|
||||
pendingRequests: messageStreamBlockItemsEmpty(stableItems, activeItems) ? null : pendingRequestBlockFromContext(context),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function textActionsForMessageStreamItems(
|
||||
function textActionTargetsForMessageStreamItems(
|
||||
rollbackCandidate: MessageStreamRollbackCandidate | null,
|
||||
forkCandidates: readonly ForkCandidate[],
|
||||
implementPlanTarget: PlanImplementationTarget | null,
|
||||
): ReadonlyMap<string, MessageStreamTextActions> {
|
||||
const byItemId = new Map<string, MessageStreamTextActions>();
|
||||
): ReadonlyMap<string, MessageStreamTextActionTargets> {
|
||||
const byItemId = new Map<string, MessageStreamTextActionTargets>();
|
||||
for (const candidate of forkCandidates) {
|
||||
patchTextActions(byItemId, candidate.itemId, { fork: { itemId: candidate.itemId, turnId: candidate.turnId } });
|
||||
patchTextActionTargets(byItemId, candidate.itemId, { fork: { itemId: candidate.itemId, turnId: candidate.turnId } });
|
||||
}
|
||||
if (rollbackCandidate) {
|
||||
patchTextActions(byItemId, rollbackCandidate.itemId, { rollback: true });
|
||||
patchTextActionTargets(byItemId, rollbackCandidate.itemId, { rollback: true });
|
||||
}
|
||||
if (implementPlanTarget) {
|
||||
patchTextActions(byItemId, implementPlanTarget.itemId, { implementPlan: implementPlanTarget });
|
||||
patchTextActionTargets(byItemId, implementPlanTarget.itemId, { implementPlan: implementPlanTarget });
|
||||
}
|
||||
return byItemId;
|
||||
}
|
||||
|
||||
function patchTextActions(byItemId: Map<string, MessageStreamTextActions>, itemId: string, patch: MessageStreamTextActions): void {
|
||||
function patchTextActionTargets(
|
||||
byItemId: Map<string, MessageStreamTextActionTargets>,
|
||||
itemId: string,
|
||||
patch: MessageStreamTextActionTargets,
|
||||
): void {
|
||||
byItemId.set(itemId, { ...byItemId.get(itemId), ...patch });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ function isCompletedTurnDetailItem(classification: MessageStreamSemanticClassifi
|
|||
|
||||
function turnOutcomeItemsByTurn(items: readonly MessageStreamSemanticClassification[]): Map<string, string> {
|
||||
const turnOutcomeIdByTurn = new Map<string, string>();
|
||||
for (const { item, actions } of items) {
|
||||
if (!item.turnId || !actions.isTurnOutcome) continue;
|
||||
for (const { item, capabilities } of items) {
|
||||
if (!item.turnId || !capabilities.isTurnOutcome) continue;
|
||||
turnOutcomeIdByTurn.set(item.turnId, item.id);
|
||||
}
|
||||
return turnOutcomeIdByTurn;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export interface MessageStreamForkTarget {
|
|||
|
||||
export type MessageStreamPlanImplementationTarget = PlanImplementationTarget;
|
||||
|
||||
export interface MessageStreamTextActions {
|
||||
export interface MessageStreamTextActionTargets {
|
||||
fork?: MessageStreamForkTarget;
|
||||
rollback?: true;
|
||||
implementPlan?: MessageStreamPlanImplementationTarget;
|
||||
|
|
@ -66,7 +66,7 @@ export interface MessageStreamTextView {
|
|||
renderMode: MessageStreamTextRenderMode;
|
||||
collapsible: boolean;
|
||||
copyText?: string;
|
||||
actions: MessageStreamTextActions;
|
||||
actionTargets: MessageStreamTextActionTargets;
|
||||
metadata: MessageStreamTextMetadataView;
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ type MessageStreamTextRenderMode = "text" | "streamMarkdown" | "obsidianMarkdown
|
|||
export function messageStreamTextView(
|
||||
item: MessageStreamItem,
|
||||
annotations?: MessageStreamItemAnnotations,
|
||||
options: { activeTurnId?: string | null; actions?: MessageStreamTextActions } = {},
|
||||
options: { activeTurnId?: string | null; actionTargets?: MessageStreamTextActionTargets } = {},
|
||||
): MessageStreamTextView {
|
||||
const renderMode = textRenderMode(item);
|
||||
const body = bodyForTextItem(item);
|
||||
|
|
@ -88,7 +88,7 @@ export function messageStreamTextView(
|
|||
renderMode,
|
||||
collapsible: item.kind === "message" && item.role === "user",
|
||||
...definedProp("copyText", copyTextForTextItem(item, options.activeTurnId ?? null)),
|
||||
actions: options.actions ?? {},
|
||||
actionTargets: options.actionTargets ?? {},
|
||||
metadata: textMetadataView(item, annotations),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { AgentRunSummary, MessageStreamItem, TaskProgressMessageStreamItem
|
|||
import { messageStreamLayoutBlocks, type MessageStreamItemAnnotations, type MessageStreamLayoutBlock } from "./layout";
|
||||
import { detailView, type DetailView } from "./detail-view";
|
||||
import { messageStreamRenderFamily } from "./render-family";
|
||||
import { messageStreamTextView, type MessageStreamTextActions, type MessageStreamTextView } from "./text-view";
|
||||
import { messageStreamTextView, type MessageStreamTextActionTargets, type MessageStreamTextView } from "./text-view";
|
||||
import {
|
||||
activeAgentRunSummary,
|
||||
agentRunSummaryView,
|
||||
|
|
@ -32,7 +32,7 @@ export interface MessageStreamPresentationBlockInput {
|
|||
activeItems?: readonly MessageStreamItem[] | undefined;
|
||||
workspaceRoot?: string | null | undefined;
|
||||
turnDiffs?: ReadonlyMap<string, string> | undefined;
|
||||
textActionsByItemId?: ReadonlyMap<string, MessageStreamTextActions> | undefined;
|
||||
textActionTargetsByItemId?: ReadonlyMap<string, MessageStreamTextActionTargets> | undefined;
|
||||
pendingRequests?: PendingRequestMessageStreamBlockInput | null | undefined;
|
||||
}
|
||||
|
||||
|
|
@ -300,7 +300,7 @@ function messageStreamRenderedItemView(
|
|||
kind: "text",
|
||||
view: messageStreamTextView(classification.item, annotations, {
|
||||
activeTurnId: input.activeTurnId,
|
||||
...definedProp("actions", input.textActionsByItemId?.get(classification.item.id)),
|
||||
...definedProp("actionTargets", input.textActionTargetsByItemId?.get(classification.item.id)),
|
||||
}),
|
||||
};
|
||||
case "detail":
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ export interface TextItemContentContext extends TextItemDetailStateContext {
|
|||
}
|
||||
|
||||
export interface TextItemActionContext extends TextItemDetailStateContext {
|
||||
forkActionsItemId: string | null;
|
||||
onForkActionsToggle?: (itemId: string | null) => void;
|
||||
forkMenuItemId: string | null;
|
||||
onForkMenuToggle?: (itemId: string | null) => void;
|
||||
copyText?: (text: string) => void;
|
||||
onImplementPlan?: (target: MessageStreamPlanImplementationTarget) => void;
|
||||
onRollback?: () => void;
|
||||
|
|
|
|||
|
|
@ -41,23 +41,23 @@ function Text({ view, context }: { view: MessageStreamTextView; context: TextIte
|
|||
}
|
||||
|
||||
function TextHeader({ view, context }: { view: MessageStreamTextView; context: TextItemActionContext }): UiNode {
|
||||
const forkActionsOpen = context.forkActionsItemId === view.id;
|
||||
const forkMenuOpen = context.forkMenuItemId === view.id;
|
||||
const roleRef = useRef<HTMLDivElement | null>(null);
|
||||
const { fork, implementPlan, rollback } = view.actions;
|
||||
const { fork, implementPlan, rollback } = view.actionTargets;
|
||||
|
||||
useEffect(() => {
|
||||
if (!forkActionsOpen) return;
|
||||
if (!forkMenuOpen) return;
|
||||
const doc = roleRef.current?.ownerDocument;
|
||||
if (!doc) return;
|
||||
const closeOnOutsidePointer = (event: PointerEvent) => {
|
||||
if (event.target instanceof Node && roleRef.current?.contains(event.target)) return;
|
||||
context.onForkActionsToggle?.(null);
|
||||
context.onForkMenuToggle?.(null);
|
||||
};
|
||||
return listenDomEvent(doc, "pointerdown", closeOnOutsidePointer, true);
|
||||
}, [context, forkActionsOpen]);
|
||||
}, [context, forkMenuOpen]);
|
||||
|
||||
const copyAction =
|
||||
view.copyText !== undefined && context.copyText && !forkActionsOpen ? (
|
||||
view.copyText !== undefined && context.copyText && !forkMenuOpen ? (
|
||||
<TextAction
|
||||
icon="copy"
|
||||
label="Copy message"
|
||||
|
|
@ -67,15 +67,15 @@ function TextHeader({ view, context }: { view: MessageStreamTextView; context: T
|
|||
) : null;
|
||||
|
||||
return (
|
||||
<div ref={roleRef} className={`codex-panel__message-role${forkActionsOpen ? " codex-panel__message-role--fork-open" : ""}`}>
|
||||
<div ref={roleRef} className={`codex-panel__message-role${forkMenuOpen ? " codex-panel__message-role--fork-open" : ""}`}>
|
||||
<span>{view.roleLabel}</span>
|
||||
{forkActionsOpen && fork ? (
|
||||
{forkMenuOpen && fork ? (
|
||||
<TextAction
|
||||
icon="archive"
|
||||
label="Fork and archive"
|
||||
className="codex-panel__fork-and-archive-message"
|
||||
onClick={() => {
|
||||
context.onForkActionsToggle?.(null);
|
||||
context.onForkMenuToggle?.(null);
|
||||
context.onFork?.(fork, true);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -84,15 +84,15 @@ function TextHeader({ view, context }: { view: MessageStreamTextView; context: T
|
|||
)}
|
||||
{fork ? (
|
||||
<TextAction
|
||||
icon={forkActionsOpen ? "file-plus-corner" : "lucide-split"}
|
||||
label={forkActionsOpen ? "Fork" : "Fork from here"}
|
||||
icon={forkMenuOpen ? "file-plus-corner" : "lucide-split"}
|
||||
label={forkMenuOpen ? "Fork" : "Fork from here"}
|
||||
className="codex-panel__fork-message"
|
||||
onClick={() => {
|
||||
if (forkActionsOpen) {
|
||||
context.onForkActionsToggle?.(null);
|
||||
if (forkMenuOpen) {
|
||||
context.onForkMenuToggle?.(null);
|
||||
context.onFork?.(fork, false);
|
||||
} else {
|
||||
context.onForkActionsToggle?.(view.id);
|
||||
context.onForkMenuToggle?.(view.id);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function createHost(draft: string) {
|
|||
},
|
||||
setDraft,
|
||||
},
|
||||
slashCommands: { execute },
|
||||
slashCommandExecutor: { execute },
|
||||
turnSubmission: { sendTurnText },
|
||||
connection: {
|
||||
currentClient: () => client,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import { createChatState } from "../../../../../src/features/chat/application/st
|
|||
import { createChatStateStore } from "../../../../../src/features/chat/application/state/store";
|
||||
import {
|
||||
executeSlashCommandWithState,
|
||||
type SlashCommandHandlerHost,
|
||||
} from "../../../../../src/features/chat/application/conversation/slash-command-handler";
|
||||
type SlashCommandExecutorHost,
|
||||
} from "../../../../../src/features/chat/application/conversation/slash-command-executor";
|
||||
import type { Thread } from "../../../../../src/domain/threads/model";
|
||||
|
||||
const textInput = (text: string): CodexInput => [{ type: "text", text }];
|
||||
|
|
@ -24,14 +24,14 @@ function thread(id: string, name: string | null = null): Thread {
|
|||
};
|
||||
}
|
||||
|
||||
type SlashCommandHostOverrides = Partial<SlashCommandHandlerHost>;
|
||||
type SlashCommandExecutorHostOverrides = Partial<SlashCommandExecutorHost>;
|
||||
|
||||
function createHost(overrides: SlashCommandHostOverrides = {}) {
|
||||
function createHost(overrides: SlashCommandExecutorHostOverrides = {}) {
|
||||
const stateStore = createChatStateStore(createChatState());
|
||||
const threadTurnsList = vi.fn().mockResolvedValue({ data: [] });
|
||||
const client = { threadTurnsList } as unknown as AppServerClient;
|
||||
const compactThread = vi.fn().mockResolvedValue(undefined);
|
||||
const host: SlashCommandHandlerHost = {
|
||||
const host: SlashCommandExecutorHost = {
|
||||
stateStore,
|
||||
currentClient: () => client,
|
||||
codexInput: vi.fn((text: string) => textInput(text)),
|
||||
|
|
@ -34,7 +34,7 @@ describe("message stream semantic classification", () => {
|
|||
{ scope: "turn", turnId: "turn", turnRole: "steer" },
|
||||
{ scope: "turn", turnId: "turn", turnRole: "outcome" },
|
||||
]);
|
||||
expect(semantic[2]?.actions).toMatchObject({ isTurnOutcome: true, canForkFromHere: true });
|
||||
expect(semantic[2]?.capabilities).toMatchObject({ isTurnOutcome: true, canForkFromHere: true });
|
||||
});
|
||||
|
||||
it("uses local steer provenance before falling back to turn order", () => {
|
||||
|
|
@ -175,13 +175,13 @@ describe("message stream semantic classification", () => {
|
|||
meaning: { plane: "dialogue", event: "proposal" },
|
||||
placement: { scope: "turn", turnRole: "detail" },
|
||||
lifecycle: { state: "running" },
|
||||
actions: { canImplementPlan: false, isTurnOutcome: false },
|
||||
capabilities: { canImplementPlan: false, isTurnOutcome: false },
|
||||
});
|
||||
expect(completed).toMatchObject({
|
||||
meaning: { plane: "dialogue", event: "proposal" },
|
||||
placement: { scope: "turn", turnRole: "outcome" },
|
||||
lifecycle: { state: "completed" },
|
||||
actions: { canImplementPlan: true, isTurnOutcome: true },
|
||||
capabilities: { canImplementPlan: true, isTurnOutcome: true },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ function shellParts(): ChatPanelShellParts {
|
|||
}),
|
||||
},
|
||||
composer: {
|
||||
controller: {
|
||||
renderer: {
|
||||
renderState: (state) => ({
|
||||
viewId: "view",
|
||||
draft: state.composer.draft,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ describe("MessageStreamPresenter scroll pinning", () => {
|
|||
expect(projection.context.workspaceRoot).toBe("/repo");
|
||||
expect(projection.blocks).toEqual([{ kind: "empty", key: "empty" }]);
|
||||
expect(projection.context.disclosures.textDetails.size).toBe(0);
|
||||
expect(projection.context.forkActionsItemId).toBeNull();
|
||||
expect(projection.context.forkMenuItemId).toBeNull();
|
||||
});
|
||||
|
||||
it("wires message stream disclosure actions through the surface context", () => {
|
||||
|
|
@ -578,8 +578,8 @@ function messageStreamSurfaceContext(options: {
|
|||
setDisclosureOpen: (bucket, id, open) => {
|
||||
options.dispatch({ type: "ui/disclosure-set", bucket, id, open });
|
||||
},
|
||||
setForkActionsItem: (itemId) => {
|
||||
options.dispatch({ type: "ui/message-fork-actions-set", itemId });
|
||||
setForkMenuItem: (itemId) => {
|
||||
options.dispatch({ type: "ui/message-fork-menu-set", itemId });
|
||||
},
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderObsidianMarkdown: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ function shellParts(store: ReturnType<typeof createChatStateStore>, toolbarActio
|
|||
}),
|
||||
},
|
||||
composer: {
|
||||
controller: {
|
||||
renderer: {
|
||||
renderState: () => ({
|
||||
viewId: "view",
|
||||
draft: "",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ describe("chatReducer", () => {
|
|||
state = chatStateWith(state, { composer: { suggestionsDismissedSignature: "dismissed" } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { approvalDetails: new Set(["1:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { textDetails: new Set(["previous:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { messageActions: { forkActionsItemId: "previous" } } });
|
||||
state = chatStateWith(state, { ui: { messageActionMenu: { forkMenuItemId: "previous" } } });
|
||||
state = chatStateWith(state, {
|
||||
ui: { goalEditor: { kind: "editing", threadId: "thread", objectiveDraft: "draft", tokenBudgetDraft: null } },
|
||||
});
|
||||
|
|
@ -79,7 +79,7 @@ describe("chatReducer", () => {
|
|||
expect(next.composer.suggestions).toEqual([]);
|
||||
expect(next.composer.suggestionsDismissedSignature).toBeNull();
|
||||
expect(uiDisclosureCount(next)).toBe(0);
|
||||
expect(next.ui.messageActions.forkActionsItemId).toBeNull();
|
||||
expect(next.ui.messageActionMenu.forkMenuItemId).toBeNull();
|
||||
expect(next.ui.goalEditor.kind).toBe("closed");
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe("chatReducer", () => {
|
|||
state = chatStateWith(state, { runtime: { selectedCollaborationMode: "plan" } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { approvalDetails: new Set(["1:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { disclosures: { textDetails: new Set(["previous:details"]) } } });
|
||||
state = chatStateWith(state, { ui: { messageActions: { forkActionsItemId: "previous" } } });
|
||||
state = chatStateWith(state, { ui: { messageActionMenu: { forkMenuItemId: "previous" } } });
|
||||
state = chatStateWith(state, {
|
||||
ui: { goalEditor: { kind: "editing", threadId: "previous-thread", objectiveDraft: "draft", tokenBudgetDraft: null } },
|
||||
});
|
||||
|
|
@ -139,7 +139,7 @@ describe("chatReducer", () => {
|
|||
expect(next.runtime.activeCollaborationMode).toBeNull();
|
||||
expect(next.runtime.selectedCollaborationMode).toBe("default");
|
||||
expect(uiDisclosureCount(next)).toBe(0);
|
||||
expect(next.ui.messageActions.forkActionsItemId).toBeNull();
|
||||
expect(next.ui.messageActionMenu.forkMenuItemId).toBeNull();
|
||||
expect(next.ui.goalEditor.kind).toBe("closed");
|
||||
});
|
||||
|
||||
|
|
@ -590,7 +590,7 @@ describe("chatReducer", () => {
|
|||
expect(state.ui.toolbarPanel).toBe("status-panel");
|
||||
});
|
||||
|
||||
it("updates typed disclosures, message actions, goal editor, and user input drafts through typed UI actions", () => {
|
||||
it("updates typed disclosures, message action menu, goal editor, and user input drafts through typed UI actions", () => {
|
||||
let state = chatStateFixture();
|
||||
|
||||
state = chatReducer(state, { type: "ui/disclosure-set", bucket: "approvalDetails", id: "1:details", open: true });
|
||||
|
|
@ -598,8 +598,8 @@ describe("chatReducer", () => {
|
|||
state = chatReducer(state, { type: "ui/disclosure-set", bucket: "goalObjectiveExpanded", id: "thread", open: true });
|
||||
expect(state.ui.disclosures.goalObjectiveExpanded.has("thread")).toBe(true);
|
||||
|
||||
state = chatReducer(state, { type: "ui/message-fork-actions-set", itemId: "message-1" });
|
||||
expect(state.ui.messageActions.forkActionsItemId).toBe("message-1");
|
||||
state = chatReducer(state, { type: "ui/message-fork-menu-set", itemId: "message-1" });
|
||||
expect(state.ui.messageActionMenu.forkMenuItemId).toBe("message-1");
|
||||
|
||||
state = chatReducer(state, { type: "ui/goal-editor-started", threadId: "thread", objective: "old", tokenBudget: 10 });
|
||||
state = chatReducer(state, { type: "ui/goal-editor-draft-updated", objective: "new" });
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import type { AppServerClient } from "../../../../src/app-server/connection/clie
|
|||
import { createChatStateStore } from "../../../../src/features/chat/application/state/store";
|
||||
import { messageStreamItems } from "../../../../src/features/chat/application/state/message-stream";
|
||||
import {
|
||||
createAutoTitleActions,
|
||||
type AutoTitleActions,
|
||||
type AutoTitleActionsHost,
|
||||
} from "../../../../src/features/chat/application/threads/auto-title-actions";
|
||||
createAutoTitleCoordinator,
|
||||
type AutoTitleCoordinator,
|
||||
type AutoTitleCoordinatorHost,
|
||||
} from "../../../../src/features/chat/application/threads/auto-title-coordinator";
|
||||
import { threadTitleContextFromMessageStreamItems } from "../../../../src/features/chat/application/threads/title-context";
|
||||
import { createThreadTitleService } from "../../../../src/features/threads/thread-title-service";
|
||||
import type { Thread } from "../../../../src/domain/threads/model";
|
||||
|
|
@ -15,11 +15,11 @@ import type { ThreadTitleContext } from "../../../../src/domain/threads/title-ge
|
|||
import { DEFAULT_SETTINGS } from "../../../../src/settings/model";
|
||||
import { deferred } from "../../../support/async";
|
||||
|
||||
describe("AutoTitleActions", () => {
|
||||
describe("AutoTitleCoordinator", () => {
|
||||
it("prefers visible turn items over completed turn summaries for active streamed turns", async () => {
|
||||
const setThreadName = vi.fn().mockResolvedValue({});
|
||||
const generateThreadTitle = vi.fn().mockResolvedValue("Visible context title");
|
||||
const { actions, stateStore } = actionsFixture({
|
||||
const { coordinator, stateStore } = coordinatorFixture({
|
||||
currentClient: () => fakeClient({ setThreadName }),
|
||||
generateThreadTitle,
|
||||
});
|
||||
|
|
@ -39,7 +39,7 @@ describe("AutoTitleActions", () => {
|
|||
],
|
||||
});
|
||||
|
||||
actions.maybeAutoTitleThread("thread", "turn", {
|
||||
coordinator.maybeAutoTitleThread("thread", "turn", {
|
||||
userText: "Completed payload request.",
|
||||
assistantText: "Completed payload response.",
|
||||
});
|
||||
|
|
@ -55,7 +55,7 @@ describe("AutoTitleActions", () => {
|
|||
it("uses visible turn items when completed turn summaries are unavailable", async () => {
|
||||
const setThreadName = vi.fn().mockResolvedValue({});
|
||||
const generateThreadTitle = vi.fn().mockResolvedValue("Visible context title");
|
||||
const { actions, stateStore } = actionsFixture({
|
||||
const { coordinator, stateStore } = coordinatorFixture({
|
||||
currentClient: () => fakeClient({ setThreadName }),
|
||||
generateThreadTitle,
|
||||
});
|
||||
|
|
@ -75,7 +75,7 @@ describe("AutoTitleActions", () => {
|
|||
],
|
||||
});
|
||||
|
||||
actions.maybeAutoTitleThread("thread", "turn", null);
|
||||
coordinator.maybeAutoTitleThread("thread", "turn", null);
|
||||
await flushPromises();
|
||||
|
||||
expect(generateThreadTitle).toHaveBeenCalledWith({
|
||||
|
|
@ -88,12 +88,12 @@ describe("AutoTitleActions", () => {
|
|||
it("does not apply a completed auto-title after the thread leaves the list", async () => {
|
||||
const generatedTitle = deferred<string | null>();
|
||||
const setThreadName = vi.fn().mockResolvedValue({});
|
||||
const { actions, stateStore, notifyThreadRenamed } = actionsFixture({
|
||||
const { coordinator, stateStore, notifyThreadRenamed } = coordinatorFixture({
|
||||
currentClient: () => fakeClient({ setThreadName }),
|
||||
generateThreadTitle: vi.fn(() => generatedTitle.promise),
|
||||
});
|
||||
|
||||
actions.maybeAutoTitleThread("thread", "turn", { userText: "Please name this.", assistantText: "Done." });
|
||||
coordinator.maybeAutoTitleThread("thread", "turn", { userText: "Please name this.", assistantText: "Done." });
|
||||
await flushPromises();
|
||||
|
||||
stateStore.dispatch({ type: "thread-list/applied", threads: [] });
|
||||
|
|
@ -107,12 +107,12 @@ describe("AutoTitleActions", () => {
|
|||
it("does not overwrite a manual name when auto-title save finishes later", async () => {
|
||||
const savedName = deferred<object>();
|
||||
const setThreadName = vi.fn(() => savedName.promise);
|
||||
const { actions, stateStore, notifyThreadRenamed } = actionsFixture({
|
||||
const { coordinator, stateStore, notifyThreadRenamed } = coordinatorFixture({
|
||||
currentClient: () => fakeClient({ setThreadName }),
|
||||
generateThreadTitle: vi.fn().mockResolvedValue("Generated title"),
|
||||
});
|
||||
|
||||
actions.maybeAutoTitleThread("thread", "turn", { userText: "Please name this.", assistantText: "Done." });
|
||||
coordinator.maybeAutoTitleThread("thread", "turn", { userText: "Please name this.", assistantText: "Done." });
|
||||
await flushPromises();
|
||||
expect(setThreadName).toHaveBeenCalledWith("thread", "Generated title");
|
||||
|
||||
|
|
@ -125,13 +125,13 @@ describe("AutoTitleActions", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function actionsFixture(
|
||||
function coordinatorFixture(
|
||||
overrides: {
|
||||
currentClient?: () => AppServerClient;
|
||||
generateThreadTitle?: (context: ThreadTitleContext) => Promise<string | null>;
|
||||
} = {},
|
||||
): AutoTitleActionsHost & {
|
||||
actions: AutoTitleActions;
|
||||
): AutoTitleCoordinatorHost & {
|
||||
coordinator: AutoTitleCoordinator;
|
||||
notifyThreadRenamed: ReturnType<typeof vi.fn>;
|
||||
} {
|
||||
const stateStore = createChatStateStore();
|
||||
|
|
@ -159,8 +159,8 @@ function actionsFixture(
|
|||
if (options.shouldPublish()) notifyThreadRenamed(threadId, value);
|
||||
return true;
|
||||
},
|
||||
} satisfies AutoTitleActionsHost;
|
||||
return { ...host, notifyThreadRenamed, actions: createAutoTitleActions(host) };
|
||||
} satisfies AutoTitleCoordinatorHost;
|
||||
return { ...host, notifyThreadRenamed, coordinator: createAutoTitleCoordinator(host) };
|
||||
}
|
||||
|
||||
function fakeClient(options: { setThreadName?: ReturnType<typeof vi.fn> } = {}): AppServerClient {
|
||||
|
|
@ -25,7 +25,7 @@ import {
|
|||
withMessageContentScrollHeight,
|
||||
} from "./test-helpers";
|
||||
|
||||
describe("message stream rendering and message actions", () => {
|
||||
describe("message stream rendering and message action menu", () => {
|
||||
it("inserts completed-turn activity groups between conversation blocks", () => {
|
||||
const parent = document.createElement("div");
|
||||
const baseContext = {
|
||||
|
|
@ -34,7 +34,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }),
|
||||
};
|
||||
|
|
@ -125,7 +125,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: testDisclosures({ activityGroups: ["t1"] }),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -163,7 +163,7 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "review-1", kind: "reviewResult", role: "tool", text: "Auto-review denied this command." }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -201,7 +201,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -243,7 +243,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown,
|
||||
})[0];
|
||||
|
|
@ -277,7 +277,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element, text) => element.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -315,10 +315,10 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [...items],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
textActionsByItemId: new Map([["u2", { rollback: true }]]),
|
||||
textActionTargetsByItemId: new Map([["u2", { rollback: true }]]),
|
||||
onRollback,
|
||||
});
|
||||
|
||||
|
|
@ -353,7 +353,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText,
|
||||
|
|
@ -372,7 +372,7 @@ describe("message stream rendering and message actions", () => {
|
|||
});
|
||||
|
||||
it("expands assistant fork actions in the copy action region and defaults repeat clicks to plain fork", () => {
|
||||
const onForkActionsToggle = vi.fn();
|
||||
const onForkMenuToggle = vi.fn();
|
||||
const onFork = vi.fn();
|
||||
const item: MessageStreamItem = {
|
||||
id: "a1",
|
||||
|
|
@ -392,12 +392,12 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
onForkActionsToggle,
|
||||
forkMenuItemId: null,
|
||||
onForkMenuToggle,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
textActionsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
textActionTargetsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
onFork,
|
||||
})[0];
|
||||
|
||||
|
|
@ -407,7 +407,7 @@ describe("message stream rendering and message actions", () => {
|
|||
expect(initialFork.getAttribute("aria-label")).toBe("Fork from here");
|
||||
expect(initialFork.getAttribute("data-icon")).toBe("lucide-split");
|
||||
initialFork.click();
|
||||
expect(onForkActionsToggle).toHaveBeenCalledWith("a1");
|
||||
expect(onForkMenuToggle).toHaveBeenCalledWith("a1");
|
||||
|
||||
const openBlock = messageStreamBlocks({
|
||||
activeThreadId: "thread",
|
||||
|
|
@ -416,12 +416,12 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: "a1",
|
||||
onForkActionsToggle,
|
||||
forkMenuItemId: "a1",
|
||||
onForkMenuToggle,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
textActionsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
textActionTargetsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
onFork,
|
||||
})[0];
|
||||
|
||||
|
|
@ -456,12 +456,12 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: "a1",
|
||||
onForkActionsToggle: vi.fn(),
|
||||
forkMenuItemId: "a1",
|
||||
onForkMenuToggle: vi.fn(),
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
textActionsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
textActionTargetsByItemId: new Map([["a1", { fork: { itemId: "a1", turnId: "turn-1" } }]]),
|
||||
onFork,
|
||||
})[0];
|
||||
|
||||
|
|
@ -479,7 +479,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text: `markdown:${text}` }),
|
||||
};
|
||||
|
|
@ -537,7 +537,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown,
|
||||
};
|
||||
|
|
@ -612,7 +612,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => {
|
||||
markdownRenderer.renderObsidianMarkdown(element, text);
|
||||
|
|
@ -686,7 +686,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderObsidianMarkdown: vi.fn(),
|
||||
renderStreamMarkdown,
|
||||
|
|
@ -739,7 +739,7 @@ describe("message stream rendering and message actions", () => {
|
|||
historyCursor: null,
|
||||
loadingHistory: false,
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderObsidianMarkdown: (element: HTMLElement, text: string) => {
|
||||
markdownRenderer.renderObsidianMarkdown(element, text);
|
||||
|
|
@ -820,7 +820,7 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent: HTMLElement, text: string) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
|
|
@ -853,11 +853,11 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
textActionsByItemId: new Map([["p1", { implementPlan: { itemId: "p1" } }]]),
|
||||
textActionTargetsByItemId: new Map([["p1", { implementPlan: { itemId: "p1" } }]]),
|
||||
onImplementPlan,
|
||||
})[0];
|
||||
|
||||
|
|
@ -931,7 +931,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText: vi.fn(),
|
||||
|
|
@ -950,11 +950,11 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "u1", kind: "message", messageKind: "user", role: "user", text: "latest", copyText: "latest", turnId: "turn-1" }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
copyText,
|
||||
textActionsByItemId: new Map([["u1", { rollback: true }]]),
|
||||
textActionTargetsByItemId: new Map([["u1", { rollback: true }]]),
|
||||
onRollback,
|
||||
})[0];
|
||||
|
||||
|
|
@ -999,7 +999,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: testDisclosures({ userMessageExpanded: [...expandedMessages] }),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
onDisclosureToggle,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
|
|
@ -1054,7 +1054,7 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "u1", kind: "message", messageKind: "user", role: "user", text: "short", turnId: "turn-1" }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1081,7 +1081,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1099,7 +1099,7 @@ describe("message stream rendering and message actions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "u1", kind: "message", messageKind: "user", role: "user", text: "running", turnId: "turn-1" }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1129,7 +1129,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1167,7 +1167,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1204,7 +1204,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1235,7 +1235,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1264,7 +1264,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1299,7 +1299,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -1338,7 +1338,7 @@ describe("message stream rendering and message actions", () => {
|
|||
],
|
||||
turnDiffs: new Map([["turn", "diff --git a/src/main.ts b/src/main.ts\n@@\n-old\n+new"]]),
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
openTurnDiff,
|
||||
|
|
@ -1386,7 +1386,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -1417,7 +1417,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -1458,7 +1458,7 @@ describe("message stream rendering and message actions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
openTurnDiff: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ describe("pending request renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown,
|
||||
})[0];
|
||||
|
|
@ -412,7 +412,7 @@ describe("pending request renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -464,7 +464,7 @@ describe("pending request renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -485,7 +485,7 @@ describe("pending request renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "a1", kind: "message", role: "assistant", text: "Done", messageKind: "assistantResponse", messageState: "completed" }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
pendingRequests: pendingRequestContext({
|
||||
|
|
@ -507,7 +507,7 @@ describe("pending request renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [{ id: "a1", kind: "message", role: "assistant", text: "Done", messageKind: "assistantResponse", messageState: "completed" }],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
pendingRequests: pendingRequestContext({
|
||||
|
|
@ -537,7 +537,7 @@ describe("pending request renderer decisions", () => {
|
|||
{ id: "a1", kind: "message", role: "assistant", text: "Done", messageKind: "assistantResponse", messageState: "completed" },
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element, text) => element.createDiv({ text }),
|
||||
pendingRequests: pendingRequestContext({
|
||||
|
|
@ -567,7 +567,7 @@ describe("pending request renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
pendingRequests: pendingRequestContext({
|
||||
|
|
@ -593,7 +593,7 @@ describe("pending request renderer decisions", () => {
|
|||
{ id: "a1", kind: "message", role: "assistant", text: "Done", messageKind: "assistantResponse", messageState: "completed" },
|
||||
] satisfies MessageStreamItem[],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -85,7 +85,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: testDisclosures({ activityGroups: ["turn"] }),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
}).find((item) => item.key === "activity:turn-turn-activity");
|
||||
|
|
@ -115,7 +115,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -146,7 +146,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -174,7 +174,7 @@ describe("message stream item renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item] satisfies MessageStreamItem[],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element: HTMLElement, text: string) => element.createDiv({ text }),
|
||||
};
|
||||
|
|
@ -206,7 +206,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -234,7 +234,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -269,7 +269,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -322,7 +322,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: testDisclosures({ activityGroups: ["turn"], details: ["hook-1:details"] }),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -361,7 +361,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -419,7 +419,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
pendingRequests: {
|
||||
|
|
@ -485,7 +485,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -543,7 +543,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -581,7 +581,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -623,7 +623,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
})[0];
|
||||
|
|
@ -661,7 +661,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
onDisclosureToggle,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
|
|
@ -712,7 +712,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -745,7 +745,7 @@ describe("message stream item renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element, text) => element.createDiv({ text }),
|
||||
}),
|
||||
|
|
@ -769,7 +769,7 @@ describe("message stream item renderer decisions", () => {
|
|||
loadingHistory: false,
|
||||
items: [item],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (element, text) => element.createDiv({ text }),
|
||||
}),
|
||||
|
|
@ -807,7 +807,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
@ -842,7 +842,7 @@ describe("message stream item renderer decisions", () => {
|
|||
},
|
||||
],
|
||||
disclosures: emptyDisclosures(),
|
||||
forkActionsItemId: null,
|
||||
forkMenuItemId: null,
|
||||
loadOlderTurns: vi.fn(),
|
||||
renderMarkdown: (parent, text) => parent.createDiv({ text }),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import type {
|
|||
} from "../../../../../src/features/chat/ui/message-stream/context";
|
||||
import type { MessageStreamItem } from "../../../../../src/features/chat/domain/message-stream/items";
|
||||
import { messageStreamViewBlocks } from "../../../../../src/features/chat/presentation/message-stream/view-model";
|
||||
import type { MessageStreamTextActions } from "../../../../../src/features/chat/presentation/message-stream/text-view";
|
||||
import type { MessageStreamTextActionTargets } from "../../../../../src/features/chat/presentation/message-stream/text-view";
|
||||
import { MessageStreamViewport } from "../../../../../src/features/chat/ui/message-stream/viewport";
|
||||
import { renderUiRoot, unmountUiRoot } from "../../../../../src/shared/ui/ui-root";
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export function messageStreamBlocks(
|
|||
activeItems: context.activeItems,
|
||||
workspaceRoot: normalized.workspaceRoot,
|
||||
turnDiffs: context.turnDiffs,
|
||||
textActionsByItemId: context.textActionsByItemId,
|
||||
textActionTargetsByItemId: context.textActionTargetsByItemId,
|
||||
pendingRequests: pendingRequestBlockInput(context),
|
||||
});
|
||||
const blocks = rawMessageStreamBlocks(viewBlocks, normalized);
|
||||
|
|
@ -61,9 +61,9 @@ function messageStreamBlockItemsEmpty(context: TestMessageStreamContext): boolea
|
|||
|
||||
type TestMessageStreamContext = Omit<
|
||||
MessageStreamContext,
|
||||
"disclosures" | "forkActionsItemId" | "renderObsidianMarkdown" | "renderStreamMarkdown"
|
||||
"disclosures" | "forkMenuItemId" | "renderObsidianMarkdown" | "renderStreamMarkdown"
|
||||
> &
|
||||
Partial<Pick<MessageStreamContext, "disclosures" | "forkActionsItemId" | "renderObsidianMarkdown" | "renderStreamMarkdown">> & {
|
||||
Partial<Pick<MessageStreamContext, "disclosures" | "forkMenuItemId" | "renderObsidianMarkdown" | "renderStreamMarkdown">> & {
|
||||
renderMarkdown?: (parent: HTMLElement, text: string) => void;
|
||||
turnLifecycle: MessageStreamTurnLifecycleState;
|
||||
historyCursor: string | null;
|
||||
|
|
@ -72,7 +72,7 @@ type TestMessageStreamContext = Omit<
|
|||
stableItems?: readonly MessageStreamItem[];
|
||||
activeItems?: readonly MessageStreamItem[];
|
||||
turnDiffs?: ReadonlyMap<string, string>;
|
||||
textActionsByItemId?: ReadonlyMap<string, MessageStreamTextActions>;
|
||||
textActionTargetsByItemId?: ReadonlyMap<string, MessageStreamTextActionTargets>;
|
||||
};
|
||||
|
||||
type MessageStreamTurnLifecycleState =
|
||||
|
|
@ -108,7 +108,7 @@ function normalizeMessageStreamContext(context: TestMessageStreamContext): Messa
|
|||
return {
|
||||
...context,
|
||||
disclosures: context.disclosures ?? emptyDisclosures(),
|
||||
forkActionsItemId: context.forkActionsItemId ?? null,
|
||||
forkMenuItemId: context.forkMenuItemId ?? null,
|
||||
renderObsidianMarkdown,
|
||||
renderStreamMarkdown,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue