From 39d4a2f5c47e0c40cbc9e9338f0664d89c1ea93b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 23 Sep 2026 13:16:36 +0000 Subject: [PATCH 1/4] fix: improve dashboard UI (View as row, icon, chat markdown) Address three dashboard UI issues from product feedback: 1. Hide View as row in immersive mode (DashboardWorkspace) - ProfileSwitch is now hidden when immersive mode is active - Reclaims vertical space in the dashboard editor - File: src/pages/Home.jsx 2. Change dashboard icon to monochrome outline style - Removed purple filled square background (#534AB7) - Now uses simple grey LineChart icon that matches app iconography - Files: src/components/sections/DashboardWorkspace.jsx, src/components/sections/DashboardWorkspace.module.css 3. Add markdown rendering to dashboard chat bubbles - Agent responses now render with ReactMarkdown + remark-gfm - Added CSS styles for bullets, code blocks, headers, links - Makes agent replies more scannable with proper structure - Files: src/components/sections/DashboardWorkspace.jsx, src/components/sections/DashboardWorkspace.module.css Co-authored-by: Venkat SF --- .../sections/DashboardWorkspace.jsx | 10 ++- .../sections/DashboardWorkspace.module.css | 67 ++++++++++++++++--- src/pages/Home.jsx | 2 +- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/components/sections/DashboardWorkspace.jsx b/src/components/sections/DashboardWorkspace.jsx index 404b137..4409890 100644 --- a/src/components/sections/DashboardWorkspace.jsx +++ b/src/components/sections/DashboardWorkspace.jsx @@ -1,5 +1,7 @@ import { useState, useEffect, useRef, useCallback, lazy, Suspense } from 'react' import { LineChart, ArrowUp, ChevronLeft, Sparkles, Check, Loader2, Brain, PencilRuler, Pencil, ClipboardCheck, TrendingUp, Users, PieChart, Layers, Code2, X, Copy, Undo2, Play, Database, History, RotateCcw, Eye, RefreshCw, ChevronDown, BellRing, Trash2 } from 'lucide-react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' import DashboardArtifact from '@/components/DashboardArtifact' import ShareMenu from './ShareMenu' import { savedDashboardsAPI } from '@/lib/api/client' @@ -506,7 +508,7 @@ export default function DashboardWorkspace({ connectionId, dashboard, onClose })
/ @@ -556,7 +558,11 @@ export default function DashboardWorkspace({ connectionId, dashboard, onClose })
{!intro && messages.map((msg, i) => (
- {msg.text} + {msg.role === 'agent' && !msg.error ? ( +
+ {msg.text} +
+ ) : msg.text}
))} {!intro && thinking && ( diff --git a/src/components/sections/DashboardWorkspace.module.css b/src/components/sections/DashboardWorkspace.module.css index 11eeaa5..3fba05c 100644 --- a/src/components/sections/DashboardWorkspace.module.css +++ b/src/components/sections/DashboardWorkspace.module.css @@ -38,15 +38,12 @@ } .logoBtn:active { transform: scale(0.92); } -.logoMark { - width: 26px; - height: 26px; - border-radius: 7px; - background: #534AB7; - display: flex; - align-items: center; - justify-content: center; +.logoIcon { + color: #555; + flex-shrink: 0; + transition: color 120ms ease-out; } +.logoBtn:hover .logoIcon { color: #111; } .crumbLink { border: none; @@ -306,6 +303,60 @@ animation: bubbleIn 260ms cubic-bezier(0.2, 0.8, 0.2, 1); } +.agentMarkdown { + font-size: 13px; + line-height: 1.6; + color: #26262a; +} +.agentMarkdown p { margin: 0 0 8px; } +.agentMarkdown p:last-child { margin-bottom: 0; } +.agentMarkdown ul, .agentMarkdown ol { + margin: 6px 0; + padding-left: 18px; +} +.agentMarkdown li { + margin: 3px 0; +} +.agentMarkdown li::marker { + color: #9a97ad; +} +.agentMarkdown strong { + font-weight: 600; + color: #111; +} +.agentMarkdown code { + font-family: ui-monospace, "SF Mono", Menlo, monospace; + font-size: 12px; + background: #f5f5f7; + padding: 1px 5px; + border-radius: 4px; +} +.agentMarkdown pre { + background: #f5f5f7; + border-radius: 6px; + padding: 10px 12px; + overflow-x: auto; + margin: 8px 0; +} +.agentMarkdown pre code { + background: none; + padding: 0; +} +.agentMarkdown h1, .agentMarkdown h2, .agentMarkdown h3 { + font-size: 13px; + font-weight: 600; + margin: 10px 0 6px; + color: #111; +} +.agentMarkdown h1:first-child, .agentMarkdown h2:first-child, .agentMarkdown h3:first-child { + margin-top: 0; +} +.agentMarkdown a { + color: #534AB7; + text-decoration: none; +} +.agentMarkdown a:hover { text-decoration: underline; } + .bubbleUser { align-self: flex-end; max-width: 92%; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index a091279..1bf8841 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -79,7 +79,7 @@ export default function Home() { {/* Main content — lazy-mount sections on first visit, then keep alive */}
- {canSwitchProfile && } + {canSwitchProfile && !immersive && }
{visibleSections.map(([key, Section]) => { if (!mounted.has(key)) return null From 6c3d500ed8bfcd3773136e8fb04f65bcbd5288db Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 23 Sep 2026 13:18:57 +0000 Subject: [PATCH 2/4] fix: add brevity style rules to dashboard agent prompts Update DashboardAgentService.java with explicit instructions for concise replies: 1. buildChatTask() - Chat-only replies (greetings, questions): - Added: lead with the answer, no filler phrases 2. buildTask() dashboard-note instructions - Build completion notes: - Keep to 1-3 short lines - Use markdown bullets when listing multiple items - Lead with what changed, skip filler phrases - Only allow longer replies for errors or limitations - Skip phrases like 'I've created...', 'As requested...' - Never open with 'Done' or 'Great' The frontend ReactMarkdown rendering (previous commit) now has properly-structured content to display. Co-authored-by: Venkat SF --- .../service/DashboardAgentService.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java b/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java index 027431b..8f2439c 100644 --- a/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java +++ b/backend/src/main/java/com/dbaagent/service/DashboardAgentService.java @@ -220,7 +220,9 @@ private String buildChatTask(String prompt) { + "It does not read as a request to build or change a chart/dashboard — it looks like a " + "greeting, small talk, or a question about what you can do. Reply briefly and naturally " + "in plain text (no HTML, no code block, no tool calls, no grounding, no SQL). If it's a " - + "greeting, greet back and invite them to describe a dashboard. Keep it to 1-2 sentences."; + + "greeting, greet back and invite them to describe a dashboard.\n\n" + + "STYLE: Keep it to 1-2 short sentences. Lead with the answer. No filler phrases like " + + "\"Great question!\" or restating what they asked. Just answer directly."; } // ── the task the agent runs ──────────────────────────────────────────── @@ -335,17 +337,25 @@ Output your FINAL message as ONLY fenced blocks (one ```dashboard-shell``` first outside a fence, no tool calls after the last one. Do NOT wrap the whole thing in a single ```html block — the shell and each widget are SEPARATE fences. - 7. END with a ```dashboard-note``` fence: 1-3 sentences to the person who asked, in the same plain + 7. END with a ```dashboard-note``` fence: a SHORT reply to the person who asked, in the same plain business language as the two hard rules above (a note naming a table, a column, SQL, or the connection id breaks the same security requirement the dashboard itself is bound by). - Say what THIS turn actually changed — not that a dashboard exists. Then, in the same note: - - State anything you could NOT do, could not verify, or chose to skip, and why. A build that - partly worked must say so. Never claim a number is correct because a query returned it. + + REPLY STYLE — brevity is mandatory: + - Lead with what changed or what you built. No filler, no restating their request. + - Keep it to 1-3 short lines. Use markdown bullets when listing multiple items (requirements + met, status updates, or next steps). Never write long paragraphs. + - Skip phrases like "I've created...", "Here's what I did...", "As requested...". Just state + the facts: "Added revenue chart. Date range defaults to last 30 days." + - Only write more than 3 lines when explaining an error, a limitation you hit, or a breaking + change — and even then stay concise. + + Content requirements (still apply): + - State anything you could NOT do, could not verify, or chose to skip, and why. - If the user was correcting or disputing something (a wrong figure, a chart that didn't load), - say plainly whether it is now fixed, and what the value/behaviour is now versus what they - reported. If you could not reproduce or resolve their complaint, say THAT — do not answer a - correction with a description of what you built. - Write it as you would to a colleague: specific and short. Never open with "Done"."""); + say plainly whether it is now fixed. If you could not resolve it, say THAT. + - Never claim a number is correct just because a query returned it. + - Never open with "Done" or "Great"."""); return sb.toString(); } From 360a7bdd0dbf77c94b8686ab4b5320eaf2b13647 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 23 Sep 2026 16:50:01 +0000 Subject: [PATCH 3/4] refactor: relocate View as control from header to sidebar Move ProfileSwitch from the top header (Home.jsx) to the left sidebar (AppSidebar.jsx) where it lives permanently above the Connections section. This addresses product feedback that the top-row View as control was distracting across all pages. Changes: - Remove ProfileSwitch from Home.jsx entirely - Add ProfileSwitch to AppSidebar.jsx bottom section - Update ProfileSwitch.jsx with sidebar-compatible styling (collapsed prop) - Add comprehensive sidebar CSS variants to ProfileSwitch.module.css The View as dropdown now appears for admins in the sidebar, integrated with the connection switcher and user profile area. Co-authored-by: Venkat SF --- src/components/layout/AppSidebar.jsx | 4 +- src/components/layout/ProfileSwitch.jsx | 139 ++++++++----- .../layout/ProfileSwitch.module.css | 194 ++++++++++++++++++ src/pages/Home.jsx | 8 +- 4 files changed, 282 insertions(+), 63 deletions(-) diff --git a/src/components/layout/AppSidebar.jsx b/src/components/layout/AppSidebar.jsx index 809b935..f8caaa0 100644 --- a/src/components/layout/AppSidebar.jsx +++ b/src/components/layout/AppSidebar.jsx @@ -7,6 +7,7 @@ import { AGENTS_ENABLED, canAccessHomeSection, getConnectionAccessBadge, getConn import { PERMISSIONS } from '@/lib/permissions' import ManageConnectionsModal from '@/components/ManageConnectionsModal' import SettingsModal from '@/components/SettingsModal' +import ProfileSwitch from './ProfileSwitch' import { useAuth } from '@/hooks/useAuth' import styles from './AppSidebar.module.css' @@ -30,7 +31,7 @@ export default function AppSidebar() { const [showConnectionDropdown, setShowConnectionDropdown] = useState(false) const userMenuRef = useRef(null) const connectionDropdownRef = useRef(null) - const { logout, role, username, impersonating, permissions, hasPermission } = useAuth() + const { logout, role, username, impersonating, permissions, hasPermission, canSwitchProfile } = useAuth() // Connections (add/edit/delete a database) is administrative: the backend already // refuses it to Developer and Data Engineer with 403, so hiding the button stops the // UI offering a door that only leads to an error. Settings is likewise administrative @@ -137,6 +138,7 @@ export default function AppSidebar() { {/* Bottom: connections + profile */}
+ {canSwitchProfile && }
- -
- {open && ( - - )} -
- ) - } - - return ( -
+
- {open && ( + {open && !collapsed && ( +
+
+ Viewing as + {username} + {role} +
+ {impersonatorUsername && ( +
Signed in as {impersonatorUsername}
+ )} +
+ {error &&
{error}
} + + + {open && ( + + )} +
+ )} +
+ ) + } + + // Normal state: show "View as" trigger + return ( +
+ + {open && !collapsed && ( )}
) } -function CandidateMenu({ candidates, loading, switching, error, activeUsername, onSelect }) { +function CandidateMenu({ candidates, loading, switching, error, activeUsername, onSelect, isSidebar }) { return ( -
+
Switch into a user
{loading &&
Loading users…
} {!loading && error &&
{error}
} diff --git a/src/components/layout/ProfileSwitch.module.css b/src/components/layout/ProfileSwitch.module.css index e03cc49..ffee625 100644 --- a/src/components/layout/ProfileSwitch.module.css +++ b/src/components/layout/ProfileSwitch.module.css @@ -227,3 +227,197 @@ background: #374151; color: #e5e7eb; } + +/* ── Sidebar variant ── */ +.sidebarWrap { + position: relative; + width: 100%; +} + +.sidebarTrigger, +.sidebarTriggerActive { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 10px; + padding: 8px 10px; + border-radius: 8px; + border: none; + background: transparent; + color: #6b7280; + font-size: 14px; + font-weight: 400; + font-family: var(--font-family-sans); + cursor: pointer; + width: 100%; + text-align: left; + transition: background 0.1s, color 0.1s; + white-space: nowrap; + overflow: hidden; +} + +.sidebarTrigger:hover:not(:disabled) { + background: #f3f4f6; + color: #111827; +} + +.sidebarTrigger:disabled { + opacity: 0.6; + cursor: default; +} + +.sidebarTriggerActive { + background: #fef3c7; + color: #92400e; +} + +.sidebarTriggerActive:hover:not(:disabled) { + background: #fde68a; +} + +.sidebarIcon { + flex-shrink: 0; + opacity: 0.6; +} + +.sidebarTriggerActive .sidebarIcon { + opacity: 0.9; +} + +.sidebarLabel { + overflow: hidden; + opacity: 1; + transition: opacity 0.12s ease; + flex: 1; + min-width: 0; + text-overflow: ellipsis; +} + +.sidebarLabelHidden { + opacity: 0; + width: 0; + pointer-events: none; +} + +.chevronIcon { + opacity: 0.6; + flex-shrink: 0; +} + +.sidebarDropdown { + position: absolute; + bottom: calc(100% + 6px); + left: 0; + right: 0; + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 10px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + z-index: 220; + overflow: hidden; +} + +.sidebarDropdownInfo { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + flex-wrap: wrap; +} + +.sidebarDropdownLabel { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #9ca3af; +} + +.sidebarDropdownName { + font-size: 13px; + font-weight: 600; + color: #111827; +} + +.sidebarDropdownMeta { + padding: 0 12px 8px; + font-size: 11px; + color: #6b7280; +} + +.sidebarDropdownDivider { + height: 1px; + background: #f3f4f6; +} + +.sidebarDropdownError { + padding: 8px 12px; + font-size: 12px; + color: #b91c1c; +} + +.sidebarDropdownAction { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 9px 12px; + background: transparent; + border: none; + font-size: 13px; + color: #374151; + cursor: pointer; + text-align: left; + transition: background 0.1s; +} + +.sidebarDropdownAction:hover:not(:disabled) { + background: #f9fafb; + color: #111827; +} + +.sidebarDropdownAction:disabled { + opacity: 0.6; + cursor: default; +} + +.sidebarDropdownActionPrimary { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + width: calc(100% - 16px); + margin: 8px; + padding: 8px 12px; + background: #111827; + border: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + color: #fff; + cursor: pointer; + transition: background 0.1s; +} + +.sidebarDropdownActionPrimary:hover:not(:disabled) { + background: #1f2937; +} + +.sidebarDropdownActionPrimary:disabled { + opacity: 0.6; + cursor: default; +} + +.sidebarMenu { + position: absolute; + bottom: calc(100% + 6px); + left: 0; + right: 0; + max-height: 320px; + overflow: auto; + background: #fff; + border: 1px solid #e5e7eb; + border-radius: 10px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + z-index: 220; +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 1bf8841..f9e692c 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,6 +1,5 @@ import { useState, useEffect, useMemo, useRef } from 'react' import AppSidebar from '@/components/layout/AppSidebar' -import ProfileSwitch from '@/components/layout/ProfileSwitch' import AgentView from '@/components/Agent/AgentView' import AgentChatSection from '@/components/sections/AgentChatSection' import DigestFeedSection from '@/components/sections/DigestSection' @@ -27,7 +26,7 @@ const SECTION_MAP = { } export default function Home() { - const { role, canSwitchProfile, permissions } = useAuth() + const { role, permissions } = useAuth() const { selectedConnection } = useConnectionManager() const activeSection = useActiveSection() const setActiveSection = useSetActiveSection() @@ -78,9 +77,7 @@ export default function Home() { {!immersive && } {/* Main content — lazy-mount sections on first visit, then keep alive */} -
- {canSwitchProfile && !immersive && } -
+
{visibleSections.map(([key, Section]) => { if (!mounted.has(key)) return null return ( @@ -98,7 +95,6 @@ export default function Home() { ) })}
-
) } From 62e4a504e901e5a39605f83d50395203cd470cc7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 23 Sep 2026 17:01:31 +0000 Subject: [PATCH 4/4] refactor: consolidate connection switcher and Connections button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge the connection dropdown and the separate Connections button into a single unified control. The connection dropdown now includes a 'Manage connections...' action at the bottom (for users with MANAGE_CONNECTIONS permission), eliminating the redundant separate button that cluttered the sidebar. Changes: - Move 'Manage connections...' action inside the connection dropdown - Remove separate Connections button from sidebar bottom section - Add dropdownDivider and dropdownManage CSS styles - Allow dropdown to open even with no connections (for admins to add) Sidebar bottom now has: View as → Connection dropdown → User profile (instead of: View as → Connection dropdown → Connections button → User) Co-authored-by: Venkat SF --- src/components/layout/AppSidebar.jsx | 99 +++++++++++---------- src/components/layout/AppSidebar.module.css | 28 ++++++ 2 files changed, 81 insertions(+), 46 deletions(-) diff --git a/src/components/layout/AppSidebar.jsx b/src/components/layout/AppSidebar.jsx index f8caaa0..c94c0c6 100644 --- a/src/components/layout/AppSidebar.jsx +++ b/src/components/layout/AppSidebar.jsx @@ -144,7 +144,7 @@ export default function AppSidebar() { className={styles.bottomItem} onClick={() => setShowConnectionDropdown((v) => !v)} title={collapsed ? connectionLabel : undefined} - disabled={isLoading || connections.length === 0} + disabled={isLoading || (connections.length === 0 && !canManageConnections)} > @@ -156,62 +156,69 @@ export default function AppSidebar() { {!collapsed && } - {showConnectionDropdown && !collapsed && connections.length > 0 && ( + {showConnectionDropdown && !collapsed && (
-
Connections
- {connections.map((conn) => ( -
+ {connections.length > 0 && ( + <> +
Connections
+ {connections.map((conn) => ( +
+ + +
+ ))} + + )} + {canManageConnections && ( + <> + {connections.length > 0 &&
} - -
- ))} + + )}
)}
- {canManageConnections && ( - - )}