@@ -980,7 +1248,7 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
{activeTab === "chat" && (
- {messages.length === 0 ? (
+ {messages.length === 0 && !sending ? (
✨
@@ -1013,6 +1281,29 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
))
)}
+
+ {/* Live Thinking Indicator */}
+ {sending && (
+
+
+
+
+
+
+
+ {activeAgent.name} is thinking…
+
+ {(thinkingElapsedMs / 1000).toFixed(1)}s
+
+
+
+ {thinkingStage || "Working through prompt…"}
+
+ {thinkingTrace && (
+
{thinkingTrace}
+ )}
+
+ )}
)}
- {/* SUGGESTIONS TAB: Step through each line suggestion with Accept / Deny / Copy to Note */}
+ {/* SUGGESTIONS TAB */}
{activeTab === "suggestions" && (
{pendingSuggestions.length > 0 && (
@@ -1103,7 +1394,26 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
)}
- {pendingSuggestions.length === 0 ? (
+ {sending && (
+
+
+
+
+
+
+
+ {activeAgent.name} is thinking…
+
+ {(thinkingElapsedMs / 1000).toFixed(1)}s
+
+
+
+ {thinkingStage || "Generating line-by-line suggestions…"}
+
+
+ )}
+
+ {pendingSuggestions.length === 0 && !sending ? (
No pending line suggestions yet.
) : (
@@ -1270,6 +1580,38 @@ export function AISidebar({ fullPage = false }: AISidebarProps) {
)}
+
+ {/* Non-intrusive Context & t/s Telemetry Footer */}
+
+
+
+ ctx {(contextStats.used / 1000).toFixed(1)}k / {(contextStats.total / 1024).toFixed(0)}k ({contextStats.pct.toFixed(0)}%)
+
+
+
+
+
+
+ {isResizing
+ ? `${sidebarWidth}px${isSnapped ? " · snapped" : ""}`
+ : tokensPerSec != null
+ ? `${tokensPerSec} t/s · ${lastOutputTokens} tok`
+ : "— t/s"}
+
+
);
}
diff --git a/apps/web/src/store/settings.ts b/apps/web/src/store/settings.ts
index eccc214..841385f 100644
--- a/apps/web/src/store/settings.ts
+++ b/apps/web/src/store/settings.ts
@@ -250,6 +250,7 @@ export interface AppSettings {
// Navigation & AI Sidebars
navCollapsed: boolean;
aiSidebarOpen: boolean;
+ aiSidebarWidth: number;
// Spell & grammar (Chunk #7)
enableSpellcheck: boolean;
enableGrammarCheck: boolean;
@@ -278,6 +279,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
focusMode: false,
navCollapsed: false,
aiSidebarOpen: false,
+ aiSidebarWidth: 370,
// Spell & grammar (Chunk #7)
enableSpellcheck: true,
enableGrammarCheck: false,
diff --git a/packages/ai/src/ai-session.ts b/packages/ai/src/ai-session.ts
index cacd4a9..0141939 100644
--- a/packages/ai/src/ai-session.ts
+++ b/packages/ai/src/ai-session.ts
@@ -328,6 +328,25 @@ export class AIStore {
});
}
+ /** Update a suggestion's line number or text anchor when editor blocks shift. */
+ async updateSuggestion(
+ sessionId: string,
+ suggestionId: string,
+ patch: Partial
>,
+ ): Promise {
+ return this.withTransaction(async () => {
+ const session = this.require(sessionId);
+ const suggestion = session.suggestions.find((s) => s.id === suggestionId);
+ if (!suggestion) return this.clone(session);
+ if (patch.lineNumber !== undefined) suggestion.lineNumber = patch.lineNumber;
+ if (patch.originalText !== undefined) suggestion.originalText = patch.originalText;
+ if (patch.newText !== undefined) suggestion.newText = patch.newText;
+ if (patch.text !== undefined) suggestion.text = patch.text;
+ session.updatedAt = nowIso();
+ return this.clone(session);
+ });
+ }
+
/** Add a note to a session and persist it. */
async addNote(sessionId: string, note: Omit): Promise {
return this.withTransaction(async () => {
diff --git a/packages/ui/src/AppShell.css b/packages/ui/src/AppShell.css
index 43dcb1b..ac4cfb0 100644
--- a/packages/ui/src/AppShell.css
+++ b/packages/ui/src/AppShell.css
@@ -184,6 +184,47 @@ body {
width: 100%;
}
+/* Sidebar Project Switcher Dropdown */
+.ps-sidebar-project-box {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 6px 8px;
+ background: var(--ps-surface-2);
+ border: 1px solid var(--ps-border);
+ border-radius: 8px;
+}
+.ps-sidebar-project-row {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.ps-sidebar-project-select {
+ flex: 1;
+ min-width: 0;
+ padding: 5px 8px !important;
+ font-size: 13px !important;
+ font-weight: 600;
+ background: var(--ps-surface) !important;
+}
+.ps-sidebar-project-meta {
+ font-size: 11.5px;
+ color: var(--ps-text-muted);
+ padding-left: 26px;
+}
+.ps-project-switcher-collapsed {
+ position: relative;
+ overflow: hidden;
+}
+.ps-project-switcher-native-overlay {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+}
+
.ps-topbar {
grid-area: topbar;
display: flex;
@@ -648,8 +689,9 @@ body {
flex-shrink: 0;
}
-/* ---- AI Sidebar (Toggleable Right Rail) ---- */
+/* ---- AI Sidebar (Toggleable & Resizable Right Rail) ---- */
.ps-ai-sidebar {
+ position: relative;
width: 370px;
flex-shrink: 0;
height: 100%;
@@ -660,6 +702,28 @@ body {
font-family: var(--ps-font-ui);
overflow: hidden;
}
+.ps-ai-sidebar.is-resizing {
+ user-select: none;
+}
+.ps-ai-resize-handle {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: -3px;
+ width: 7px;
+ cursor: col-resize;
+ z-index: 25;
+ background: transparent;
+ transition: background 0.15s ease;
+}
+.ps-ai-resize-handle:hover,
+.ps-ai-sidebar.is-resizing .ps-ai-resize-handle {
+ background: color-mix(in srgb, var(--ps-accent) 55%, transparent);
+}
+.ps-ai-sidebar.is-resizing .ps-ai-resize-handle.is-snapped {
+ background: var(--ps-success);
+ box-shadow: 0 0 8px var(--ps-success);
+}
.ps-ai-fullpage {
width: 100%;
max-width: 900px;
@@ -909,6 +973,99 @@ body {
flex-wrap: wrap;
}
+/* AI Thinking Indicator */
+.ps-ai-thinking {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ background: color-mix(in srgb, var(--ps-accent) 10%, var(--ps-surface-2));
+ border: 1px dashed color-mix(in srgb, var(--ps-accent) 55%, var(--ps-border));
+ font-size: 12.5px;
+}
+.ps-ai-thinking-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ color: var(--ps-accent);
+}
+.ps-ai-thinking-timer {
+ margin-left: auto;
+ font-family: var(--ps-font-mono);
+ font-size: 11px;
+ color: var(--ps-text-muted);
+}
+.ps-ai-thinking-stage {
+ font-size: 12px;
+ color: var(--ps-text);
+}
+.ps-ai-thinking-trace {
+ font-size: 11.5px;
+ font-style: italic;
+ color: var(--ps-text-muted);
+ border-left: 2px solid var(--ps-border);
+ padding-left: 8px;
+}
+.ps-ai-thinking-dots {
+ display: inline-flex;
+ align-items: center;
+ gap: 3px;
+}
+.ps-ai-thinking-dots > span {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--ps-accent);
+ animation: ps-thinking-bounce 1.2s infinite ease-in-out both;
+}
+.ps-ai-thinking-dots > span:nth-child(1) { animation-delay: -0.24s; }
+.ps-ai-thinking-dots > span:nth-child(2) { animation-delay: -0.12s; }
+@keyframes ps-thinking-bounce {
+ 0%, 80%, 100% { transform: scale(0.45); opacity: 0.4; }
+ 40% { transform: scale(1); opacity: 1; }
+}
+
+/* Non-intrusive AI Telemetry Bar (Context & t/s) */
+.ps-ai-telemetry-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ padding: 5px 12px;
+ border-top: 1px solid var(--ps-border);
+ background: var(--ps-surface-2);
+ font-family: var(--ps-font-mono);
+ font-size: 11px;
+ color: var(--ps-text-muted);
+ flex-shrink: 0;
+}
+.ps-ai-telemetry-ctx {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ min-width: 0;
+}
+.ps-ai-telemetry-meter {
+ width: 44px;
+ height: 4px;
+ border-radius: 999px;
+ background: var(--ps-border);
+ overflow: hidden;
+ display: inline-block;
+}
+.ps-ai-telemetry-meter-fill {
+ display: block;
+ height: 100%;
+ background: var(--ps-accent);
+ border-radius: 999px;
+ transition: width 0.2s ease;
+}
+.ps-ai-telemetry-speed {
+ white-space: nowrap;
+ font-variant-numeric: tabular-nums;
+}
+
/* ---- Projects Page & Template Wizard ---- */
.ps-projects-grid {
display: grid;