feat: ui refinements and settings reactivity

- Replace status pills with icons in Audio Detail view
- Make settings changes (summary profiles) reactive
- Update karaoke highlight to use text accent color
- Remove transcription card styling on mobile
- Remove Scriberr logo from chat panel
This commit is contained in:
rishikanthc
2025-12-12 15:21:00 -08:00
committed by Rishikanth Chandrasekaran
parent c33197752b
commit 789a9be6de
12 changed files with 291 additions and 20 deletions

View File

@@ -0,0 +1,65 @@
---
trigger: always_on
---
The Fluid UI Style Guide
1. Deference & Negative Space
Principle: The UI must recede; content must dominate.
Action: Maximize white space. Avoid heavy borders or boxes to separate content. Use translucency (blur) for background layers to maintain context without visual noise.
2. Hierarchy via Typography
Principle: Group information using font weight and size, not lines.
Action: Establish a strict scale (e.g., Large Bold for titles, Medium for headers, Regular for body). Use 100% black for primary text and grey/opacity (e.g., 60%) for secondary text to guide the eye.
3. The 44pt Touch Standard
Principle: Accuracy allows for speed.
Action: All interactive tap targets must be at least 44x44 points, regardless of the visual icon size. Place primary navigation in the bottom "thumb zone."
4. Physics-Based Motion
Principle: Objects have mass and friction; nothing moves linearly.
Action: Use Spring Animations (configure mass, stiffness, damping) instead of ease-in/out. Ensure all animations are interruptible (if a user grabs a moving object, it stops instantly).
5. Color as Function
Principle: Color indicates interactivity, not decoration.
Action: Select one "Tint Color" (Accent) for buttons, links, and active states. Keep the structural UI monochrome (whites, greys, blacks).
6. Direct Manipulation (Gestures)
Principle: Users should manipulate the object, not a proxy for the object.
Action: Prioritize swipes (to delete/go back) and pinches over tap-based buttons. Ensure the animation tracks 1:1 with the user's finger during the gesture.
7. Depth & The Z-Axis
Principle: Interfaces are stacked layers, not flat planes.
Action: Use shadows and dimming to indicate elevation. When a modal or sheet appears, the background layer should scale down slightly or darken to push it "back" in Z-space.
8. Instant Multisensory Feedback
Principle: Every interaction requires acknowledgment.
Action: Response latency must be <100ms. Pair visual state changes (highlights/press states) with subtle haptic feedback (tactile bumps) for confirmation.
9. Zero Dead Ends (Empty States)
Principle: An empty screen is a broken experience.
Action: Never leave a container blank. Design illustrative "Empty States" that explain what belongs there and provide a direct button to create that content.
10. Functional Consistency
Principle: Predictability reduces cognitive load.
Action: Reuse system paradigms. If it looks like a switch, it must toggle. If it looks like a search bar, it must filter. Do not create custom controls if a standard system control exists.

25
.agent/rules/go-rules.md Normal file
View File

@@ -0,0 +1,25 @@
---
trigger: always_on
---
The "10 Commandments" of Go Backend Development
Accept Interfaces, Return Structs. Keep functions flexible by accepting behaviors (interfaces) and robust by returning data (structs). Let the consumer define the interface.
Propagate Context Everywhere. Pass context.Context as the first argument to every I/O function. It is essential for handling cancellation, timeouts, and tracing in distributed systems.
Wrap, Don't Hide, Errors. Never ignore errors. Wrap them with fmt.Errorf("%w", err) to add context (traceability) while allowing the caller to unwrap and inspect the root cause.
Package by Feature. Avoid models and controllers folders. Organize code by business domain (e.g., billing, auth) to ensure high cohesion and avoid circular dependencies.
Limit Concurrency. Never spawn unlimited goroutines per request. Use Worker Pools or semaphores to cap concurrency and prevent memory exhaustion under load.
Pre-allocate Memory. Use make([]T, 0, cap) when the size is known. Avoiding repeated array resizing reduces CPU overhead and garbage collection pressure.
Avoid Global State. Ban global variables and init() side effects. Use explicit Dependency Injection to make your server testable, modular, and thread-safe.
Pool Hot Objects. Use sync.Pool for frequently allocated short-lived objects (buffers, contexts). This drastically reduces Garbage Collection pauses in high-throughput systems.
Log Structurally. Abandon text logs. Use log/slog to emit JSON logs with key-value pairs (order_id=123), making your production logs queryable and actionable.
Write Table-Driven Tests. Utilize Gos idiom of table-driven testing to cover multiple edge cases cleanly and efficiently without duplicating test logic.

View File

@@ -0,0 +1,33 @@
---
trigger: always_on
---
1. **Separate Server State from Client State.**
Never store API data in Redux or Context. Use **TanStack Query** (or SWR) for caching and fetching, reserving **Zustand** or Context strictly for UI state (e.g., themes, modals).
2. **Organize by Feature, Not File Type.**
Abandon `/components` and `/hooks` folders. **Colocate** related code (api, hooks, components) into feature-specific directories (e.g., `features/user-profile`) to ensure maintainability as the codebase grows.
3. **Derive State, Do Not Sync It.**
Never use `useEffect` to update a state variable based on another state variable. Calculate the derived value directly in the render body to eliminate "glitches" and extra render cycles.
4. **Preserve Referential Equality.**
Wrap objects and functions passed to memoized children in **`useMemo`** and **`useCallback`**. Stable references prevent expensive, unnecessary re-renders of child components.
5. **Eliminate Render-Fetch Waterfalls.**
Do not chain data fetching (Parent fetches -> Renders -> Child fetches). Use route-level loaders or parallel queries to fetch all required data immediately.
6. **Prefer Composition Over Prop Drilling.**
Avoid passing data through five layers of components. Use **Component Composition** (passing components as `children` or props) to make the hierarchy flat and efficient.
7. **Virtualize Long Lists.**
Never render large datasets directly to the DOM. Use **TanStack Virtual** or `react-window` to render only the visible items, ensuring the browser remains responsive.
8. **Lazy Load Routes.**
Implement **Code Splitting** using `React.lazy` and `Suspense` for route-level components. This ensures users only download the JavaScript required for the current page.
9. **Enforce Strict TypeScript.**
Treat `any` as a compile error. Define explicit interfaces for all props and API responses to guarantee safe refactoring and self-documenting code.
10. **Isolate Logic in Custom Hooks.**
Keep UI components pure (JSX only). Abstract complex state logic, side effects, and listeners into **Custom Hooks** to ensure logic is testable, reusable, and readable.

View File

@@ -0,0 +1,100 @@
---
trigger: always_on
---
**Scriberr Premium Design System**.
This is a comprehensive set of design guidelines for the **Scriberr Design System**. These rules are tailored for your designer to ensure the "Raised/Floating" aesthetic works perfectly with a **Pure White** background and your specific **Orange Gradient** branding.
---
### **Scriberr Design System Guidelines**
#### **1. Depth & Elevation (The "Floating" Strategy)**
Since the main background is **Pure White (`#FFFFFF`)**, we cannot rely on background contrast to separate cards. We must rely on **Physics-Based Shadowing** and **Micro-Borders**.
* **The "Resting" State (Default Cards)**
* **Concept:** Objects should not look like they are flying; they should look like thick cardstock resting on a desk.
* **Guideline:** Use a **Dual-Shadow approach**:
1. *Ambient Shadow:* A very tight, low-blur shadow to ground the object (e.g., `0px 2px 4px`).
2. *Key Light Shadow:* A larger, very soft shadow to suggest depth (e.g., `0px 10px 20px`).
* **The "Micro-Border":** Every floating white card **must** have a `1px` solid border in a very pale grey (`rgba(0,0,0,0.06)`). This prevents the "ghosting" effect where white cards blend invisibly into the white background on low-quality monitors.
* **The "Active" State (Hover/Lift)**
* **Guideline:** When a user hovers over a card (like a list item), do not change the background color. Instead, **lift** the card.
* **Action:** Increase the shadow spread and slightly nudge the element up (`translateY(-2px)`). This mimics tactile feedback.
#### **2. Color Usage (The 60-30-10 Rule)**
Your **Orange Gradient** (`#FFAB40``#FF3D00`) is high-energy. It must be the "jewel" of the interface, not the wallpaper.
* **60% Neutral (The Canvas):**
* **Light Mode:** Strictly Pure White (`#FFFFFF`).
* **Dark Mode:** Deep Carbon (`#0A0A0A`).
* **30% Structure (Text & Lines):**
* Use shades of **Neutral Grey** (e.g., `#171717` to `#737373`).
* *Constraint:* Never use pure black (`#000000`) for text. It creates a harsh "strobe" effect against white backgrounds.
* **10% Accent (The Logo Colors):**
* **Primary Action:** Use the **Orange Gradient** for the single most important action on a screen (e.g., "New Recording," "Save").
* **Secondary Action:** Use the **Solid Middle Orange** (`#FF6D20`) for links or active icons.
* **Tertiary/Backgrounds:** Use a "Washed Orange" (`rgba(255, 171, 64, 0.08)`) for active states in menus or selected items to tie them to the brand without overwhelming the eye.
#### **3. Typography & Hierarchy**
To maintain the "Sleek/Premium" feel, hierarchy is established through **Weight**, not just Size.
* **Headings:** Use a geometric sans-serif (like Inter, DM Sans, or Plus Jakarta).
* *Style:* Bold or ExtraBold.
* *Color:* Darkest Grey (`#171717`).
* **Body Text:**
* *Style:* Regular.
* *Color:* Medium Grey (`#525252`).
* *Constraint:* Keep line-height generous (approx 1.5 to 1.6) to maintain the "airiness" of the white background.
* **Metadata (Dates, file sizes):**
* *Style:* Medium Weight, Smaller Size.
* *Color:* Light Grey (`#A3A3A3`).
* *Transformation:* Use Uppercase + Wide Letter Spacing (Tracking) occasionally for labels (e.g., "AUDIO FILES") to add elegance.
#### **4. Iconography**
Icons must bridge the gap between your detailed logo and the minimal UI.
* **Style:** Use **Rounded** or **Soft-Edge** strokes (2px width). Avoid sharp, jagged icons.
* **Coloring Strategy:**
* *Inactive Icons:* Slate Grey (`#A3A3A3`).
* *Active Icons:* Solid Orange (`#FF6D20`).
* *Feature Icons:* If you need a large icon (e.g., empty state placeholder), apply the **Brand Gradient** to the icon stroke for a premium touch.
#### **5. Dark Mode Specifics (The "Achromatic" Rule)**
Since you requested **no blues**, the dark mode must rely on "Surface Lightness."
* **The "Rim Light" Technique:**
* In the absence of shadows, every card in Dark Mode must have a `1px` border of `rgba(255, 255, 255, 0.08)`. This mimics light catching the edge of the plastic/metal.
* **Elevation Mapping:**
* *Background:* `#0A0A0A` (Deepest)
* *Card Level 1:* `#141414` (Slightly Lighter)
* *Card Level 2 (Modals/Dropdowns):* `#1F1F1F` (Lighter still)
* **Text Contrast:**
* Do not use pure white (`#FFFFFF`) text on dark backgrounds; it causes "halation" (blurring). Use `#EDEDED` (93% White).
#### **6. Interactive Components**
* **Buttons:**
* *Primary:* Full Gradient background. Text is White.
* *Secondary:* White background, Grey border, Dark text. Hover border changes to Orange.
* *Ghost:* Transparent background, Orange text.
* **Inputs (Text Fields):**
* *Default:* Light Grey background (`#F9FAFB`) with no border. This feels softer than a harsh outlined box.
* *Focus:* White background + Orange Border + Orange "Glow" shadow.
#### **7. Status Colors (Harmonization)**
Standard "Traffic Light" colors often clash with custom branding. Use this adjusted palette to harmonize with your Orange logo:
* **Success:** **Teal/Emerald** (`#10B981`). (Standard Green fights with Orange; Teal complements it).
* **Error:** **Warm Red** (`#EF4444`). (Standard bright red is too aggressive; Warm red matches the "heat" of the orange).
* **Warning:** **Amber** (`#F59E0B`). (Naturally fits the palette).
#### **7. The CSS Theme Block**
The custom theme has been added to the index.css. Use these specifiers to style all UI components. All style setting values should be defined centrally in index.css
Colors should never be hardcoded.
A set of carefully designed components are there in @/components/ui folder. try to reuse them as much as you can for consistency. If what you need is not there then feel free to build a custom one.

View File

@@ -0,0 +1,5 @@
---
description:
---
Run build.sh in the project root and verify it builds successfully without errors

View File

@@ -0,0 +1,7 @@
---
description: commit changes
---
Update .gitignore if needed
Stage all required files
Commit all changes with a good description that answers what or why

BIN
main Executable file

Binary file not shown.

View File

@@ -294,12 +294,11 @@ export const TranscriptView = forwardRef<HTMLDivElement, TranscriptViewProps>(({
{/* CSS for the Highlight API - Global for both views */}
<style>{`
::highlight(karaoke-word) {
background-color: var(--brand-solid);
color: white !important;
border-radius: 3px;
padding: 0 1px;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
background-color: transparent;
color: var(--brand-solid) !important;
font-weight: 600;
text-decoration: underline decoration-dotted var(--brand-solid);
text-underline-offset: 4px;
}
`}</style>
</div>

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { MainLayout } from "@/components/layout/MainLayout";
import { User, Settings as SettingsIcon, Key, Bot, FileText, Plus, Terminal } from "lucide-react";
import {
@@ -20,6 +21,7 @@ import { useAuth } from "@/features/auth/hooks/useAuth";
export function Settings() {
const [activeTab, setActiveTab] = useState("transcription");
const { getAuthHeaders } = useAuth();
const queryClient = useQueryClient();
const [summaryDialogOpen, setSummaryDialogOpen] = useState(false);
const [editingSummary, setEditingSummary] = useState<SummaryTemplate | null>(null);
const [summaryRefresh, setSummaryRefresh] = useState(0);
@@ -181,6 +183,9 @@ export function Settings() {
await fetch('/api/v1/summaries', { method: 'POST', headers, body: JSON.stringify({ name: tpl.name, description: tpl.description, model: tpl.model, prompt: tpl.prompt }) });
}
} finally {
// Invalidate cache to propagate changes
queryClient.invalidateQueries({ queryKey: ["summaryTemplates"] });
// keep user on Summary tab and refresh the list without a full reload
setSummaryDialogOpen(false);
setEditingSummary(null);

View File

@@ -1,10 +1,11 @@
import { useRef, useState, useEffect, useCallback } from "react";
import { createPortal } from "react-dom";
import { useParams, useNavigate } from "react-router-dom";
import { MoreVertical, Edit2, Activity, FileText, Bot, Check, Loader2, List, AlignLeft, ArrowDownCircle, StickyNote, MessageCircle, FileImage, FileJson } from "lucide-react";
import { MoreVertical, Edit2, Activity, FileText, Bot, Check, Loader2, List, AlignLeft, ArrowDownCircle, StickyNote, MessageCircle, FileImage, FileJson, Clock, AlertCircle } from "lucide-react";
import { Header } from "@/components/Header";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { EmberPlayer, type EmberPlayerRef } from "@/components/audio/EmberPlayer";
@@ -218,16 +219,49 @@ export const AudioDetailView = function AudioDetailView({ audioId: propAudioId }
<div className="flex items-center gap-3 text-xs font-medium uppercase tracking-wider text-[var(--text-tertiary)]">
<span>{formattedDate}</span>
<span className="w-1 h-1 rounded-full bg-[var(--text-tertiary)] opacity-50"></span>
<div className={cn(
"flex items-center gap-1.5 px-2 py-0.5 rounded-full border",
audioFile.status === 'completed' && "border-[var(--success-solid)]/20 text-[var(--success-solid)] bg-[var(--success-translucent)]",
audioFile.status === 'processing' && "border-[var(--brand-solid)]/20 text-[var(--brand-solid)] bg-[var(--brand-light)]",
audioFile.status === 'failed' && "border-[var(--error)]/20 text-[var(--error)] bg-[var(--error)]/10",
)}>
{audioFile.status === 'completed' && <Check className="h-3 w-3" />}
{audioFile.status === 'processing' && <Loader2 className="h-3 w-3 animate-spin" />}
{audioFile.status === 'failed' && <Activity className="h-3 w-3" />}
<span>{audioFile.status}</span>
{/* Status Icon */}
<div>
{audioFile.status === 'completed' && (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help text-emerald-500">
<Check className="h-5 w-5" strokeWidth={2.5} />
</div>
</TooltipTrigger>
<TooltipContent>Completed</TooltipContent>
</Tooltip>
)}
{audioFile.status === 'processing' && (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help text-amber-500">
<Loader2 className="h-5 w-5 animate-spin" strokeWidth={2.5} />
</div>
</TooltipTrigger>
<TooltipContent>Processing</TooltipContent>
</Tooltip>
)}
{audioFile.status === 'failed' && (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help text-red-500">
<AlertCircle className="h-5 w-5" strokeWidth={2.5} />
</div>
</TooltipTrigger>
<TooltipContent>Failed</TooltipContent>
</Tooltip>
)}
{audioFile.status === 'pending' && (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help text-gray-400">
<Clock className="h-5 w-5" strokeWidth={2.5} />
</div>
</TooltipTrigger>
<TooltipContent>Queued</TooltipContent>
</Tooltip>
)}
</div>
</div>
</div>

View File

@@ -1,6 +1,5 @@
import { useState, useEffect } from "react";
import { ChatSessionsSidebar } from "@/components/ChatSessionsSidebar";
import { ScriberrIcon } from "@/components/ScriberrLogo";
import { ChatInterface } from "@/components/ChatInterface";
import { Button } from "@/components/ui/button";
import { X, ArrowLeft } from "lucide-react";
@@ -58,7 +57,6 @@ export function ChatSidePanel({ transcriptionId, isOpen, onClose, isMobile }: Ch
</Button>
)}
<div className="flex items-center gap-2 font-bold text-[var(--text-primary)]">
<ScriberrIcon className="h-4 w-4" />
<span>{view === 'chat' ? 'Chat' : 'Sessions'}</span>
</div>
</div>

View File

@@ -158,7 +158,7 @@ export function TranscriptSection({
if (!transcript) return null;
return (
<div className="glass-card rounded-[var(--radius-card)] p-4 md:p-6 min-h-[500px] border-[var(--border-subtle)] shadow-[var(--shadow-card)] transition-shadow hover:shadow-[var(--shadow-float)]">
<div className="md:glass-card md:rounded-[var(--radius-card)] md:border-[var(--border-subtle)] md:shadow-[var(--shadow-card)] md:hover:shadow-[var(--shadow-float)] p-4 md:p-6 min-h-[500px] transition-shadow">
{/*
TOOLBAR REMOVED -> Moved to Context Menu
*/}