mirror of
https://github.com/rishikanthc/Scriberr.git
synced 2026-03-03 00:27:01 +00:00
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:
committed by
Rishikanth Chandrasekaran
parent
c33197752b
commit
789a9be6de
65
.agent/rules/fluid-ui-style-guide.md
Normal file
65
.agent/rules/fluid-ui-style-guide.md
Normal 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
25
.agent/rules/go-rules.md
Normal 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 Go’s idiom of table-driven testing to cover multiple edge cases cleanly and efficiently without duplicating test logic.
|
||||
33
.agent/rules/react-rules.md
Normal file
33
.agent/rules/react-rules.md
Normal 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.
|
||||
100
.agent/rules/scriberr-design-system.md
Normal file
100
.agent/rules/scriberr-design-system.md
Normal 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.
|
||||
5
.agent/workflows/check-build.md
Normal file
5
.agent/workflows/check-build.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
description:
|
||||
---
|
||||
|
||||
Run build.sh in the project root and verify it builds successfully without errors
|
||||
7
.agent/workflows/commit-changes.md
Normal file
7
.agent/workflows/commit-changes.md
Normal 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
*/}
|
||||
|
||||
Reference in New Issue
Block a user