Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,5 @@ jobs:
)
echo "Created PR: ${PR_URL}"

gh pr merge "${PR_URL}" --auto --squash
echo "Auto-merge enabled on ${PR_URL}"
gh pr merge "${PR_URL}" --squash --admin
echo "Merged ${PR_URL}"
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@protolabsai/proto",
"version": "0.25.11",
"version": "0.25.12",
"publishConfig": {
"access": "public"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@protolabs/proto",
"version": "0.25.11",
"version": "0.25.12",
"description": "proto",
"repository": {
"type": "git",
Expand Down
13 changes: 9 additions & 4 deletions packages/cli/src/ui/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,15 @@ export const Header: React.FC<HeaderProps> = ({
{/* Left side: ASCII logo (only if enough space) */}
{showLogo && (
<>
<Box flexShrink={0}>
<Gradient colors={gradientColors}>
<Text>{displayLogo}</Text>
</Gradient>
<Box flexShrink={0} flexDirection="column">
{displayLogo
.split('\n')
.slice(1) // trim leading empty line from template literal
.map((line, i) => (
<Gradient key={i} colors={gradientColors}>
<Text>{line.padEnd(logoWidth)}</Text>
</Gradient>
))}
</Box>
{/* Fixed gap between logo and info panel */}
<Box width={logoGap} />
Expand Down
39 changes: 8 additions & 31 deletions packages/cli/src/ui/components/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { Box, Static } from 'ink';
import { useMemo } from 'react';
import { HistoryItemDisplay } from './HistoryItemDisplay.js';
import { TruncatedHistoryBanner } from './TruncatedHistoryBanner.js';
import { ShowMoreLines } from './ShowMoreLines.js';
import { Notifications } from './Notifications.js';
import { OverflowProvider } from '../contexts/OverflowContext.js';
Expand All @@ -22,18 +21,6 @@ import { DebugModeNotification } from './DebugModeNotification.js';
// usage.
const MAX_GEMINI_MESSAGE_LINES = 65536;

/**
* Maximum number of history items to keep in the Static render window.
* Items before this window have already been printed to the terminal and
* do not need to be held in the React tree. Ink's Static identifies
* already-printed items by React key, so the slice does not cause
* re-printing — only genuinely new items at the tail are emitted.
*
* On historyRemountKey change (terminal clear + view switch), only the
* windowed items are reprinted instead of the full unbounded history.
*/
const STATIC_HISTORY_WINDOW = 200;

export const MainContent = () => {
const { version } = useAppContext();
const uiState = useUIState();
Expand All @@ -45,25 +32,16 @@ export const MainContent = () => {
availableTerminalHeight,
} = uiState;

const staticItems = useMemo(() => {
const history = uiState.history;
const truncatedCount = Math.max(0, history.length - STATIC_HISTORY_WINDOW);
const visibleHistory =
truncatedCount > 0 ? history.slice(-STATIC_HISTORY_WINDOW) : history;

return [
// NOTE: Ink's <Static> tracks rendered items by array INDEX (not React key).
// It stores the last-rendered length and slices from that index on each
// render. If the array ever shrinks or stays the same length, the index
// overshoots and nothing new is printed. The array passed to Static must
// therefore only ever grow — never shrink or stay constant length.
const staticItems = useMemo(() => [
<AppHeader key="app-header" version={version} />,
<DebugModeNotification key="debug-notification" />,
<Notifications key="notifications" />,
...(truncatedCount > 0
? [
<TruncatedHistoryBanner
key="truncated-banner"
count={truncatedCount}
/>,
]
: []),
...visibleHistory.map((h) => (
...uiState.history.map((h) => (
<HistoryItemDisplay
terminalWidth={terminalWidth}
mainAreaWidth={mainAreaWidth}
Expand All @@ -75,8 +53,7 @@ export const MainContent = () => {
commands={uiState.slashCommands}
/>
)),
];
}, [
], [
uiState.history,
uiState.slashCommands,
version,
Expand Down
35 changes: 6 additions & 29 deletions packages/cli/src/ui/components/agent-view/AgentChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@ import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
import { useKeypress } from '../../hooks/useKeypress.js';
import { agentMessagesToHistoryItems } from './agentHistoryAdapter.js';
import { AgentHeader } from './AgentHeader.js';
import { TruncatedHistoryBanner } from '../TruncatedHistoryBanner.js';

// How many committed items to keep in the Static render window.
// Mirrors STATIC_HISTORY_WINDOW in MainContent.tsx.
const AGENT_STATIC_HISTORY_WINDOW = 200;

// ─── Main Component ─────────────────────────────────────────

Expand Down Expand Up @@ -209,33 +204,17 @@ export const AgentChatView = ({ agentId }: AgentChatViewProps) => {

// Build the Static items array. Must be called unconditionally (before any
// early return) to satisfy the Rules of Hooks.
const staticItems = useMemo(() => {
const truncatedCount = Math.max(
0,
committedItems.length - AGENT_STATIC_HISTORY_WINDOW,
);
const visibleItems =
truncatedCount > 0
? committedItems.slice(-AGENT_STATIC_HISTORY_WINDOW)
: committedItems;

return [
// NOTE: Ink's <Static> tracks rendered items by array INDEX (not React key).
// The array must only ever grow — never shrink or stay constant length.
const staticItems = useMemo(() => [
<AgentHeader
key="agent-header"
modelId={agentModelId}
modelName={agent?.modelName ?? ''}
workingDirectory={agentWorkingDir}
gitBranch={agentGitBranch}
/>,
...(truncatedCount > 0
? [
<TruncatedHistoryBanner
key="truncated-banner"
count={truncatedCount}
/>,
]
: []),
...visibleItems.map((item) => (
...committedItems.map((item) => (
<HistoryItemDisplay
key={item.id}
item={item}
Expand All @@ -244,8 +223,7 @@ export const AgentChatView = ({ agentId }: AgentChatViewProps) => {
mainAreaWidth={contentWidth}
/>
)),
];
}, [
], [
committedItems,
agentModelId,
agent?.modelName,
Expand All @@ -270,8 +248,7 @@ export const AgentChatView = ({ agentId }: AgentChatViewProps) => {
{/* Committed message history.
key includes historyRemountKey: when refreshStatic() clears the
terminal it bumps the key, forcing Static to remount and re-emit
all items on the cleared screen. The windowed slice limits
reprint cost to AGENT_STATIC_HISTORY_WINDOW items max. */}
all items on the cleared screen. */}
<Static key={`agent-${agentId}-${historyRemountKey}`} items={staticItems}>
{(item) => item}
</Static>
Expand Down
17 changes: 1 addition & 16 deletions packages/cli/src/ui/hooks/useHistoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@
import { useState, useRef, useCallback } from 'react';
import type { HistoryItem } from '../types.js';

/**
* Maximum number of history items to retain in React state.
* Items beyond this cap are pruned from the front — they have already been
* printed to the terminal by Ink's Static and do not need to be reconciled.
* Must be >= STATIC_HISTORY_WINDOW (200) in MainContent.tsx so the render
* window always has items available.
*/
const MAX_HISTORY_ITEMS = 500;

// Type for the updater function passed to updateHistoryItem
type HistoryItemUpdater = (
prevItem: HistoryItem,
Expand Down Expand Up @@ -70,13 +61,7 @@ export function useHistory(): UseHistoryManagerReturn {
return prevHistory; // Don't add the duplicate
}
}
const next = [...prevHistory, newItem];
// Prune the oldest items once the cap is exceeded. Items that are
// removed have already been committed to the terminal by Ink's Static
// and no longer need to live in React state.
return next.length > MAX_HISTORY_ITEMS
? next.slice(-MAX_HISTORY_ITEMS)
: next;
return [...prevHistory, newItem];
});
return id; // Return the generated ID (even if not added, to keep signature)
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-core",
"version": "0.25.11",
"version": "0.25.12",
"description": "proto core",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/test-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-test-utils",
"version": "0.25.11",
"version": "0.25.12",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/web-templates/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/web-templates",
"version": "0.25.11",
"version": "0.25.12",
"description": "Web templates bundled as embeddable JS/CSS strings",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/webui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qwen-code/webui",
"version": "0.25.11",
"version": "0.25.12",
"description": "Shared UI components for proto packages",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
Loading