From d6aa540472c3f49fe8539a6c667f9f0b6a4aa932 Mon Sep 17 00:00:00 2001 From: Ocean82 <203952330+Ocean82@users.noreply.github.com> Date: Sat, 25 Jul 2026 08:55:31 +0000 Subject: [PATCH 1/4] Improve spreadsheet-first workspace and docked chat UX Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- src/components/ChatPanel.tsx | 4 +-- src/components/panels/DockPanel.tsx | 53 +++++++++++++++++++++------- src/components/panels/panelTypes.ts | 26 -------------- src/components/panels/panelTypes.tsx | 29 +++++++++++++++ src/index.css | 36 +++++++++++++++++++ 5 files changed, 107 insertions(+), 41 deletions(-) delete mode 100644 src/components/panels/panelTypes.ts create mode 100644 src/components/panels/panelTypes.tsx diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx index df5b0c7..86b6349 100644 --- a/src/components/ChatPanel.tsx +++ b/src/components/ChatPanel.tsx @@ -186,7 +186,7 @@ export function ChatPanel({ isMobileOpen, onCloseMobile, embedded }: { isMobileO

smartsh!t assistant

-

Describe what you need โ€” I handle the rest

+

Ask about this sheet or make a change

@@ -231,7 +231,7 @@ export function ChatPanel({ isMobileOpen, onCloseMobile, embedded }: { isMobileO onClick={onCloseMobile} className="md:hidden p-1.5 rounded-lg text-white hover:bg-white/20" > - โœ• +
diff --git a/src/components/panels/DockPanel.tsx b/src/components/panels/DockPanel.tsx index 1ccd22a..aa16535 100644 --- a/src/components/panels/DockPanel.tsx +++ b/src/components/panels/DockPanel.tsx @@ -31,48 +31,75 @@ export function DockPanel({ panelId, children, title, headerActions }: DockPanel const handleClose = () => setActivePanel(null) - const handleResizeStart = useCallback((e: React.MouseEvent) => { + const resizeBy = useCallback((delta: number) => { + setPanelWidth(panelId, Math.min(def.maxWidth, Math.max(def.minWidth, width + delta))) + }, [width, panelId, def.maxWidth, def.minWidth, setPanelWidth]) + + const handleResizeStart = useCallback((e: React.PointerEvent) => { e.preventDefault() + e.currentTarget.setPointerCapture(e.pointerId) resizingRef.current = true const startX = e.clientX const startWidth = width document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' - const onMove = (ev: MouseEvent) => { + const onMove = (ev: PointerEvent) => { if (!resizingRef.current) return - // Dragging left edge โ†’ moving left means bigger panel + // The handle is on the left edge: moving left makes the panel wider. const delta = startX - ev.clientX - const newWidth = Math.min(def.maxWidth, Math.max(def.minWidth, startWidth + delta)) - setPanelWidth(panelId, newWidth) + setPanelWidth(panelId, Math.min(def.maxWidth, Math.max(def.minWidth, startWidth + delta))) } const onUp = () => { resizingRef.current = false document.body.style.cursor = '' document.body.style.userSelect = '' - document.removeEventListener('mousemove', onMove) - document.removeEventListener('mouseup', onUp) + document.removeEventListener('pointermove', onMove) + document.removeEventListener('pointerup', onUp) + document.removeEventListener('pointercancel', onUp) } - document.addEventListener('mousemove', onMove) - document.addEventListener('mouseup', onUp) + document.addEventListener('pointermove', onMove) + document.addEventListener('pointerup', onUp) + document.addEventListener('pointercancel', onUp) }, [width, panelId, def.maxWidth, def.minWidth, setPanelWidth]) + const handleResizeKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'ArrowLeft') { + e.preventDefault() + resizeBy(20) + } else if (e.key === 'ArrowRight') { + e.preventDefault() + resizeBy(-20) + } else if (e.key === 'Home') { + e.preventDefault() + setPanelWidth(panelId, def.minWidth) + } else if (e.key === 'End') { + e.preventDefault() + setPanelWidth(panelId, def.maxWidth) + } + } + if (!isOpen) return null return (
{/* Resize handle (left edge) */}
diff --git a/src/components/panels/panelTypes.ts b/src/components/panels/panelTypes.ts deleted file mode 100644 index 096802a..0000000 --- a/src/components/panels/panelTypes.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Panel system type definitions. - * Each panel has an ID, icon, label, and default width. - */ - -export type PanelId = 'chat' | 'insights' | 'auditor' | 'inspector' - -export interface PanelDef { - id: PanelId - icon: string - label: string - defaultWidth: number - minWidth: number - maxWidth: number -} - -export const PANELS: PanelDef[] = [ - { id: 'chat', icon: '๐Ÿ’ฌ', label: 'Chat', defaultWidth: 360, minWidth: 280, maxWidth: 500 }, - { id: 'insights', icon: '๐Ÿ“Š', label: 'Insights', defaultWidth: 320, minWidth: 260, maxWidth: 480 }, - { id: 'auditor', icon: '๐Ÿ›ก๏ธ', label: 'Auditor', defaultWidth: 300, minWidth: 260, maxWidth: 440 }, - { id: 'inspector', icon: '๐Ÿ”ฌ', label: 'Inspector', defaultWidth: 300, minWidth: 260, maxWidth: 440 }, -] - -export function getPanelDef(id: PanelId): PanelDef { - return PANELS.find((p) => p.id === id)! -} diff --git a/src/components/panels/panelTypes.tsx b/src/components/panels/panelTypes.tsx new file mode 100644 index 0000000..ab81cad --- /dev/null +++ b/src/components/panels/panelTypes.tsx @@ -0,0 +1,29 @@ +/** + * Panel system type definitions. + * Each panel has an ID, icon, label, and default width. + */ + +import type { ReactNode } from 'react' +import { BarChart3, Microscope, MessageSquare, Shield } from 'lucide-react' + +export type PanelId = 'chat' | 'insights' | 'auditor' | 'inspector' + +export interface PanelDef { + id: PanelId + icon: ReactNode + label: string + defaultWidth: number + minWidth: number + maxWidth: number +} + +export const PANELS: PanelDef[] = [ + { id: 'chat', icon: , label: 'Chat', defaultWidth: 360, minWidth: 280, maxWidth: 500 }, + { id: 'insights', icon: , label: 'Insights', defaultWidth: 320, minWidth: 260, maxWidth: 480 }, + { id: 'auditor', icon: , label: 'Auditor', defaultWidth: 300, minWidth: 260, maxWidth: 440 }, + { id: 'inspector', icon: , label: 'Inspector', defaultWidth: 300, minWidth: 260, maxWidth: 440 }, +] + +export function getPanelDef(id: PanelId): PanelDef { + return PANELS.find((p) => p.id === id)! +} diff --git a/src/index.css b/src/index.css index 1ca8fac..9cbb307 100644 --- a/src/index.css +++ b/src/index.css @@ -215,3 +215,39 @@ body { transform: scale(0.90); } } + +/* Spreadsheet-first workspace: docked tools never float over the grid. */ +#spreadsheet-main { + min-width: 320px; + background: var(--surface-panel); +} + +/* Give the dock resize affordance a predictable hit area without adding visual noise. */ +[role="separator"][aria-orientation="vertical"] { + touch-action: none; +} + +@media (max-width: 900px) and (min-width: 769px) { + #spreadsheet-main { + min-width: 240px; + } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + } +} + +/* On phones the active tool becomes a deliberate full-screen workspace, rather + than squeezing the sheet into an unusable column. The close button returns + users to the sheet. */ +@media (max-width: 767px) { + .dock-panel { + width: 100% !important; + min-width: 0 !important; + max-width: none !important; + } +} From 7c545e9fa720377b3d723edaf641b5544b0961d1 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:09:22 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`are?= =?UTF-8?q?na/019f987a-smartshit`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @Ocean82. * https://github.com/Ocean82/smartshit/pull/13#issuecomment-5077825911 The following files were modified: * `src/components/ChatPanel.tsx` * `src/components/panels/DockPanel.tsx` * `src/components/panels/panelTypes.tsx` --- src/components/ChatPanel.tsx | 8 ++++++++ src/components/panels/DockPanel.tsx | 8 ++++++++ src/components/panels/panelTypes.tsx | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx index 86b6349..4f7fb77 100644 --- a/src/components/ChatPanel.tsx +++ b/src/components/ChatPanel.tsx @@ -22,6 +22,14 @@ function healthFooterMessage(health: ServerHealth | null): string { return 'Instant analysis active ยท Skills work without AI' } +/** + * Renders the assistant chat panel with messaging, spreadsheet actions, attachments, feedback, and usage controls. + * + * @param isMobileOpen - Whether the standalone panel is open on mobile. + * @param onCloseMobile - Callback invoked when the mobile panel is closed. + * @param embedded - Whether to render the panel without standalone header and visibility controls. + * @returns The assistant chat panel. + */ export function ChatPanel({ isMobileOpen, onCloseMobile, embedded }: { isMobileOpen?: boolean; onCloseMobile?: () => void; embedded?: boolean }) { const { messages, diff --git a/src/components/panels/DockPanel.tsx b/src/components/panels/DockPanel.tsx index aa16535..b21adc8 100644 --- a/src/components/panels/DockPanel.tsx +++ b/src/components/panels/DockPanel.tsx @@ -17,6 +17,14 @@ interface DockPanelProps { headerActions?: React.ReactNode } +/** + * Renders an active dock panel with resizable content and header controls. + * + * @param panelId - Identifier of the panel to render and resize + * @param title - Optional title displayed in the panel header + * @param headerActions - Optional controls displayed before the close button + * @returns The dock panel when active, or `null` otherwise + */ export function DockPanel({ panelId, children, title, headerActions }: DockPanelProps) { const activePanel = useStore((s) => s.activePanel) const setActivePanel = useStore((s) => s.setActivePanel) diff --git a/src/components/panels/panelTypes.tsx b/src/components/panels/panelTypes.tsx index ab81cad..4f29ea0 100644 --- a/src/components/panels/panelTypes.tsx +++ b/src/components/panels/panelTypes.tsx @@ -24,6 +24,12 @@ export const PANELS: PanelDef[] = [ { id: 'inspector', icon: , label: 'Inspector', defaultWidth: 300, minWidth: 260, maxWidth: 440 }, ] +/** + * Retrieves the definition for a panel identifier. + * + * @param id - The identifier of the panel to retrieve + * @returns The matching panel definition + */ export function getPanelDef(id: PanelId): PanelDef { return PANELS.find((p) => p.id === id)! } From c41bf4cfd1f9275131c27dc217d48beda27fb10e Mon Sep 17 00:00:00 2001 From: Ocean82 <203952330+Ocean82@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:17:22 +0000 Subject: [PATCH 3/4] Address dock resize accessibility review Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- src/components/ChatPanel.tsx | 1 + src/components/panels/DockPanel.tsx | 80 +++++++++++++++++------------ src/index.css | 6 +-- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/components/ChatPanel.tsx b/src/components/ChatPanel.tsx index 86b6349..adfe865 100644 --- a/src/components/ChatPanel.tsx +++ b/src/components/ChatPanel.tsx @@ -229,6 +229,7 @@ export function ChatPanel({ isMobileOpen, onCloseMobile, embedded }: { isMobileO