Skip to content

Commit 7db403b

Browse files
committed
hotfix: stabilize v0.0.8 UI and performance
1 parent 38b7e6b commit 7db403b

18 files changed

Lines changed: 372 additions & 113 deletions

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ All notable changes are documented here. The project uses [Semantic Versioning](
1111
- **0.0.5** - In-app updater and extension fallback import release
1212
- **0.0.6** - UI foundation and layout redesign release
1313
- **0.0.7** - Projects-first workflow and operations tooling release
14-
- **0.0.8** - Navigation, admin backup, and UX polish release
14+
- **0.0.8** - Navigation, admin backup, UX polish, and hotfix stabilization release
1515

1616
---
1717

@@ -22,18 +22,25 @@ All notable changes are documented here. The project uses [Semantic Versioning](
2222
- **Directory workspace** - Added a dedicated Directory page that groups Leads and Vendors under one navigation entry.
2323
- **Dedicated settings subpages** - Added Inventory Settings and Message Settings pages with persisted settings scaffolding for inventory display and outbound messaging preferences.
2424
- **Global crash fallback** - Added a top-level React `ErrorBoundary` with a user-facing reload recovery screen.
25+
- **Verbose error diagnostics toggle** - Added the `verbose_errors` setting so operators can temporarily expose detailed server and crash-screen error messages while debugging.
2526

2627
### Changed
2728
- **Sidebar information architecture** - Reworked navigation into grouped sections with collapsible mode, hover flyouts, unread/pending badges, and live team presence context.
2829
- **Inventory and leads loading/search UX** - Replaced spinner-only states with skeleton loaders, added contextual empty-search messages with reset actions, and lazy-loaded inventory thumbnails.
2930
- **Quote builder polish** - Added add-to-project flash feedback and refined line-item spacing/thumbnail treatment.
3031
- **Theme consistency pass** - Replaced remaining hardcoded accent/focus colors with theme tokens, fixed the Files page elevated-surface token mismatch, and added a max-width constraint to the main layout.
3132
- **Dashboard polish** - Updated KPI accent styling to use theme variables and improved loading presentation during data fetches.
33+
- **Route-level bundle loading** - Converted heavy operator and public screens to lazy-loaded routes so the initial client payload is smaller and secondary pages only load when visited.
34+
- **Quote detail editing safety** - Quote detail now tracks dirty form state, blocks in-app navigation when edits are unsaved, and warns on browser reload/close.
35+
- **Messages mobile layout** - The Messages workspace now swaps to a focused single-pane detail flow on smaller screens instead of forcing the desktop split-pane layout.
3236

3337
### Fixed
3438
- **Public quote browser tab title** - Public quote pages now set `document.title` from the active quote name.
3539
- **Toast accessibility** - Toast notifications now announce through `role="status"` and `aria-live`.
3640
- **Inventory card action labels** - Icon-only item card actions now expose accessible names for assistive technologies.
41+
- **Spreadsheet import title validation** - Sheets import now rejects numeric-only item titles, preventing Excel date serials and bare numeric IDs from being imported as inventory names.
42+
- **Item API metadata parity** - Item create/update/bulk upsert flows now preserve `contract_description` consistently while also rejecting numeric-only titles.
43+
- **Quote creation failure handling** - Quote creation now returns a structured API error instead of bubbling an unhandled server failure.
3744

3845
---
3946

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ BadShuffle is a self-hosted event rental software platform for project-centric q
2323

2424
## What’s New In v0.0.8
2525

26-
`v0.0.8` is the navigation, admin continuity, and UX polish release. It reorganizes the operator experience around workflow-based navigation, adds in-app database backup/restore, introduces dedicated directory/settings surfaces, and tightens resilience through better loading, accessibility, and error handling.
26+
`v0.0.8` is the navigation, admin continuity, UX polish, and Windows host packaging release, now followed by a hotfix pass that focuses on performance, mobile behavior, editing safety, and import resilience. It reorganizes the operator experience around workflow-based navigation, adds in-app database backup/restore, ships packaged Windows `.exe` artifacts for self-hosted deployments, and tightens daily operator workflows with better loading, error handling, and guardrails.
2727

2828
- **Navigation architecture refresh** — The sidebar is now grouped around workflows (Projects, Inventory, Messages, Directory, Settings), supports collapse + flyout behavior, and surfaces unread-message plus pending-admin counts alongside live team presence.
2929
- **Admin continuity tooling** — Admin now includes SQLite backup export/import so operators can migrate or restore data without leaving the app.
3030
- **New workspace surfaces** — Added a Directory hub for Leads/Vendors plus dedicated Inventory Settings and Message Settings screens for operational preferences.
31-
- **UX resilience pass** — Added skeleton loading states, contextual empty-search feedback, a global React error boundary, toast screen-reader announcements, and lazy-loaded inventory imagery.
32-
- **Theme and builder polish** — Replaced more hardcoded UI colors with theme tokens, fixed remaining surface-token mismatches, tightened quote-builder item presentation, and constrained the main content width for better wide-screen balance.
31+
- **Windows host packaging** — The project is packaged for Windows hosts with separate server, client, and updater `.exe` artifacts so operators can run BadShuffle without a manual Node deployment.
32+
- **Performance hotfixes** — Heavy operator and public routes now lazy-load on first navigation, reducing the initial client bundle cost for day-to-day use.
33+
- **Workflow safety hotfixes** — Quote detail editing now warns before reload or navigation when there are unsaved changes, making large project edits harder to lose accidentally.
34+
- **Responsive messaging polish** — Messages now behaves as a focused single-pane experience on smaller screens, with cleaner thread/detail transitions during mobile use.
35+
- **Debugging and import hardening** — Settings can enable verbose error output for debugging, quote creation failures now return cleaner API errors, and sheet/item imports reject numeric-only titles that commonly come from spreadsheet date serials or malformed source data.
3336

3437
## Core Features
3538

@@ -51,9 +54,9 @@ BadShuffle is a self-hosted event rental software platform for project-centric q
5154

5255
## Near-Term Roadmap
5356

54-
- **Cross-theme QA + responsive pass** — Finish theme verification and close remaining mobile layout gaps across quote editing, messages, and modal-heavy views.
55-
- **Performance follow-up**Lazy-load heavy pages and finish image lazy loading on the remaining public/files surfaces.
56-
- **Workflow safety**Add unsaved-changes warnings plus better inline confirmation patterns for destructive actions.
57+
- **Cross-theme QA + responsive pass** — Finish theme verification and close the remaining mobile layout gaps across quote editing, messages, and modal-heavy views.
58+
- **Performance follow-up**Finish image lazy loading on the remaining public/files surfaces and review post-split route chunk sizes.
59+
- **Workflow safety**Extend unsaved-change protection to other high-risk forms and replace destructive browser confirms with better inline confirmation patterns.
5760
- **Operations depth** — Send preview, pull sheets, and richer warehouse workflows.
5861

5962
More context lives in [ai/KNOWN_GAPS.md](ai/KNOWN_GAPS.md) and [ai/TODO.md](ai/TODO.md).

client/src/App.jsx

Lines changed: 85 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,48 @@
1-
import React, { useEffect, useState, useRef } from 'react';
1+
import React, { useEffect, useState, useRef, lazy, Suspense } from 'react';
22
import { BrowserRouter, Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
33
import Layout from './components/Layout.jsx';
4-
import InventoryPage from './pages/InventoryPage.jsx';
5-
import ImportPage from './pages/ImportPage.jsx';
6-
import QuotePage from './pages/QuotePage.jsx';
7-
import QuoteDetailPage from './pages/QuoteDetailPage.jsx';
8-
import StatsPage from './pages/StatsPage.jsx';
9-
import ExtensionPage from './pages/ExtensionPage.jsx';
10-
import ItemDetailPage from './pages/ItemDetailPage.jsx';
11-
import SettingsPage from './pages/SettingsPage.jsx';
12-
import LeadsPage from './pages/LeadsPage.jsx';
13-
import AdminPage from './pages/AdminPage.jsx';
14-
import LoginPage from './pages/LoginPage.jsx';
15-
import SetupPage from './pages/SetupPage.jsx';
16-
import ForgotPage from './pages/ForgotPage.jsx';
17-
import ResetPage from './pages/ResetPage.jsx';
18-
import PublicQuotePage from './pages/PublicQuotePage.jsx';
19-
import PublicCatalogPage from './pages/PublicCatalogPage.jsx';
20-
import PublicItemPage from './pages/PublicItemPage.jsx';
21-
import TemplatesPage from './pages/TemplatesPage.jsx';
22-
import DashboardPage from './pages/DashboardPage.jsx';
23-
import FilesPage from './pages/FilesPage.jsx';
24-
import MessagesPage from './pages/MessagesPage.jsx';
25-
import BillingPage from './pages/BillingPage.jsx';
26-
import VendorsPage from './pages/VendorsPage.jsx';
27-
import DirectoryPage from './pages/DirectoryPage.jsx';
28-
import InventorySettingsPage from './pages/InventorySettingsPage.jsx';
29-
import MessageSettingsPage from './pages/MessageSettingsPage.jsx';
304
import { ToastProvider } from './components/Toast.jsx';
315
import { api, getToken, setToken, clearToken } from './api';
326

7+
// ── Auth-path pages — eager (shown before JS bundle is fully parsed) ──────────
8+
import LoginPage from './pages/LoginPage.jsx';
9+
import SetupPage from './pages/SetupPage.jsx';
10+
import ForgotPage from './pages/ForgotPage.jsx';
11+
import ResetPage from './pages/ResetPage.jsx';
12+
13+
// ── App pages — lazy (only parsed when first navigated to) ────────────────────
14+
const DashboardPage = lazy(() => import('./pages/DashboardPage.jsx'));
15+
const InventoryPage = lazy(() => import('./pages/InventoryPage.jsx'));
16+
const ItemDetailPage = lazy(() => import('./pages/ItemDetailPage.jsx'));
17+
const ImportPage = lazy(() => import('./pages/ImportPage.jsx'));
18+
const QuotePage = lazy(() => import('./pages/QuotePage.jsx'));
19+
const QuoteDetailPage = lazy(() => import('./pages/QuoteDetailPage.jsx'));
20+
const BillingPage = lazy(() => import('./pages/BillingPage.jsx'));
21+
const StatsPage = lazy(() => import('./pages/StatsPage.jsx'));
22+
const ExtensionPage = lazy(() => import('./pages/ExtensionPage.jsx'));
23+
const LeadsPage = lazy(() => import('./pages/LeadsPage.jsx'));
24+
const FilesPage = lazy(() => import('./pages/FilesPage.jsx'));
25+
const MessagesPage = lazy(() => import('./pages/MessagesPage.jsx'));
26+
const AdminPage = lazy(() => import('./pages/AdminPage.jsx'));
27+
const TemplatesPage = lazy(() => import('./pages/TemplatesPage.jsx'));
28+
const VendorsPage = lazy(() => import('./pages/VendorsPage.jsx'));
29+
const SettingsPage = lazy(() => import('./pages/SettingsPage.jsx'));
30+
const DirectoryPage = lazy(() => import('./pages/DirectoryPage.jsx'));
31+
const InventorySettingsPage = lazy(() => import('./pages/InventorySettingsPage.jsx'));
32+
const MessageSettingsPage = lazy(() => import('./pages/MessageSettingsPage.jsx'));
33+
// Public pages — lazy (most users never visit these from inside the app)
34+
const PublicQuotePage = lazy(() => import('./pages/PublicQuotePage.jsx'));
35+
const PublicCatalogPage = lazy(() => import('./pages/PublicCatalogPage.jsx'));
36+
const PublicItemPage = lazy(() => import('./pages/PublicItemPage.jsx'));
37+
38+
function PageSpinner() {
39+
return (
40+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 200 }}>
41+
<div className="spinner" />
42+
</div>
43+
);
44+
}
45+
3346
function ProtectedRoute({ children }) {
3447
if (!getToken()) return <Navigate to="/login" replace />;
3548
return children;
@@ -45,14 +58,11 @@ function AuthGate({ children, setRole }) {
4558
let cancelled = false;
4659

4760
const doAuth = async () => {
48-
// Dev mode: auto-login with hardcoded admin account, no setup/login required
4961
if (import.meta.env.DEV && !getToken()) {
5062
try {
5163
const data = await api.auth.devLogin();
5264
if (data.token) setToken(data.token);
53-
} catch (e) {
54-
// server not ready yet or non-dev build; fall through to normal flow
55-
}
65+
} catch (e) {}
5666
}
5767

5868
let setup = true;
@@ -85,7 +95,6 @@ function AuthGate({ children, setRole }) {
8595
if (cancelled) return;
8696
clearToken();
8797
setRole('');
88-
// If already at /login, try dev auto-login immediately (stale token case after DB clear)
8998
if (import.meta.env.DEV && location.pathname === '/login') {
9099
try {
91100
const data = await api.auth.devLogin();
@@ -94,7 +103,7 @@ function AuthGate({ children, setRole }) {
94103
const me = await api.auth.me();
95104
if (!cancelled) { setRole(me.role); setState('authed'); }
96105
}
97-
} catch { /* fall through to show login page */ }
106+
} catch {}
98107
if (!cancelled) setState('unauthed');
99108
return;
100109
}
@@ -110,7 +119,6 @@ function AuthGate({ children, setRole }) {
110119
return () => { cancelled = true; };
111120
}, [navigate, setRole, location.pathname]);
112121

113-
// When pathname changes to /login (e.g. after logout), allow redirect again in future
114122
useEffect(() => {
115123
if (location.pathname === '/login') hasRedirectedToLogin.current = false;
116124
}, [location.pathname]);
@@ -125,47 +133,49 @@ export default function App() {
125133
return (
126134
<ToastProvider>
127135
<BrowserRouter>
128-
<Routes>
129-
{/* Public routes — no auth required */}
130-
<Route path="/quote/public/:token" element={<PublicQuotePage />} />
131-
<Route path="/catalog" element={<PublicCatalogPage />} />
132-
<Route path="/catalog/item/:id" element={<PublicItemPage />} />
133-
134-
{/* App routes (auth required except login/setup) */}
135-
<Route path="*" element={
136-
<AuthGate setRole={setRole}>
137-
<Routes>
138-
<Route path="/login" element={<LoginPage />} />
139-
<Route path="/setup" element={<SetupPage />} />
140-
<Route path="/forgot" element={<ForgotPage />} />
141-
<Route path="/reset" element={<ResetPage />} />
142-
143-
<Route path="/" element={<ProtectedRoute><Layout role={role} /></ProtectedRoute>}>
144-
<Route index element={<Navigate to="/dashboard" replace />} />
145-
<Route path="dashboard" element={<DashboardPage />} />
146-
<Route path="inventory" element={<InventoryPage />} />
147-
<Route path="inventory/:id" element={<ItemDetailPage />} />
148-
<Route path="import" element={<ImportPage />} />
149-
<Route path="quotes" element={<QuotePage />} />
150-
<Route path="quotes/:id" element={<QuoteDetailPage />} />
151-
<Route path="billing" element={<BillingPage />} />
152-
<Route path="stats" element={<StatsPage />} />
153-
<Route path="extension" element={<ExtensionPage />} />
154-
<Route path="leads" element={<LeadsPage />} />
155-
<Route path="files" element={<FilesPage />} />
156-
<Route path="messages" element={<MessagesPage />} />
157-
<Route path="admin" element={<AdminPage />} />
158-
<Route path="templates" element={<TemplatesPage />} />
159-
<Route path="vendors" element={<VendorsPage />} />
160-
<Route path="settings" element={<SettingsPage />} />
161-
<Route path="directory" element={<DirectoryPage />} />
162-
<Route path="inventory-settings" element={<InventorySettingsPage />} />
163-
<Route path="message-settings" element={<MessageSettingsPage />} />
164-
</Route>
165-
</Routes>
166-
</AuthGate>
167-
} />
168-
</Routes>
136+
<Suspense fallback={<PageSpinner />}>
137+
<Routes>
138+
{/* Public routes */}
139+
<Route path="/quote/public/:token" element={<PublicQuotePage />} />
140+
<Route path="/catalog" element={<PublicCatalogPage />} />
141+
<Route path="/catalog/item/:id" element={<PublicItemPage />} />
142+
143+
{/* App routes */}
144+
<Route path="*" element={
145+
<AuthGate setRole={setRole}>
146+
<Routes>
147+
<Route path="/login" element={<LoginPage />} />
148+
<Route path="/setup" element={<SetupPage />} />
149+
<Route path="/forgot" element={<ForgotPage />} />
150+
<Route path="/reset" element={<ResetPage />} />
151+
152+
<Route path="/" element={<ProtectedRoute><Layout role={role} /></ProtectedRoute>}>
153+
<Route index element={<Navigate to="/dashboard" replace />} />
154+
<Route path="dashboard" element={<DashboardPage />} />
155+
<Route path="inventory" element={<InventoryPage />} />
156+
<Route path="inventory/:id" element={<ItemDetailPage />} />
157+
<Route path="import" element={<ImportPage />} />
158+
<Route path="quotes" element={<QuotePage />} />
159+
<Route path="quotes/:id" element={<QuoteDetailPage />} />
160+
<Route path="billing" element={<BillingPage />} />
161+
<Route path="stats" element={<StatsPage />} />
162+
<Route path="extension" element={<ExtensionPage />} />
163+
<Route path="leads" element={<LeadsPage />} />
164+
<Route path="files" element={<FilesPage />} />
165+
<Route path="messages" element={<MessagesPage />} />
166+
<Route path="admin" element={<AdminPage />} />
167+
<Route path="templates" element={<TemplatesPage />} />
168+
<Route path="vendors" element={<VendorsPage />} />
169+
<Route path="settings" element={<SettingsPage />} />
170+
<Route path="directory" element={<DirectoryPage />} />
171+
<Route path="inventory-settings" element={<InventorySettingsPage />} />
172+
<Route path="message-settings" element={<MessageSettingsPage />} />
173+
</Route>
174+
</Routes>
175+
</AuthGate>
176+
} />
177+
</Routes>
178+
</Suspense>
169179
</BrowserRouter>
170180
</ToastProvider>
171181
);

client/src/components/AISuggestModal.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
align-items: center;
77
justify-content: center;
88
z-index: 900;
9+
padding: 16px;
910
}
1011

1112
.modal {

0 commit comments

Comments
 (0)