Skip to content

Commit dae23be

Browse files
committed
release: cut v0.0.9
1 parent 7db403b commit dae23be

34 files changed

+2816
-1654
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,29 @@ All notable changes are documented here. The project uses [Semantic Versioning](
1212
- **0.0.6** - UI foundation and layout redesign release
1313
- **0.0.7** - Projects-first workflow and operations tooling release
1414
- **0.0.8** - Navigation, admin backup, UX polish, and hotfix stabilization release
15+
- **0.0.9** - Security hardening and quote-workflow refactor release
1516

1617
---
1718

19+
## [0.0.9] - 2026-03-26
20+
21+
### Added
22+
- **Quote workflow service modules** - Added `server/lib/quoteActivity.js`, `server/services/itemStatsService.js`, and `server/services/quoteService.js` so activity logging, item analytics bookkeeping, quote send, duplication, and status transitions are reusable outside the route layer.
23+
- **Quote UI extraction primitives** - Added `client/src/lib/quoteTotals.js`, `client/src/hooks/useQuoteDetail.js`, extracted quote modals (`ImagePicker`, `QuoteFilePicker`, `QuoteSendModal`), and dedicated `quote-builder/` panel components.
24+
- **Release notes for 0.0.9** - Added a dedicated `RELEASE_NOTES_0.0.9.md` summary for packaging/tagging and downstream release publishing.
25+
26+
### Changed
27+
- **Quote detail architecture** - `QuoteDetailPage.jsx` has been reduced substantially by moving shared state and modal/file-picker concerns into dedicated modules.
28+
- **Quote builder architecture** - `QuoteBuilder.jsx` now delegates line-item editing, adjustment management, and inventory browsing to smaller focused panels.
29+
- **Shared pricing logic** - Public and operator quote totals now derive from the same shared helper instead of duplicating pricing/adjustment math in two pages.
30+
- **Backend route structure** - `server/routes/quotes.js` now routes through extracted quote services/helpers instead of hosting the core orchestration inline.
31+
32+
### Fixed
33+
- **JWT and extension-auth fallback behavior** - Auth middleware no longer leaves broad route access coupled to a generic extension token path.
34+
- **Public file and quote exposure** - File serving and public quote payloads now use tighter access checks and smaller response surfaces.
35+
- **Upload and attachment safety** - File uploads are validated from actual file signatures, and outbound quote mail only attaches files already linked to the quote.
36+
- **Public quote state mutation guardrails** - Public approval/signing-related flows now have stronger state checks and safer backend handling.
37+
1838
## [0.0.8] - 2026-03-24
1939

2040
### Added

README.md

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# BadShuffle v0.0.8
1+
# BadShuffle v0.0.9
22

3-
![Release](https://img.shields.io/badge/release-0.0.8-0a7ea4)
3+
![Release](https://img.shields.io/badge/release-0.0.9-0a7ea4)
44
![Status](https://img.shields.io/badge/status-pre--release-c79200)
55
![Stack](https://img.shields.io/badge/stack-React%20%7C%20Express%20%7C%20SQLite-1f6feb)
66
![Deploy](https://img.shields.io/badge/deploy-Docker%20%7C%20Windows%20EXE-2ea44f)
@@ -21,18 +21,16 @@ BadShuffle is a self-hosted event rental software platform for project-centric q
2121
- **Domain complexity** — Availability conflicts, per-line pricing overrides, reusable rental/payment policies, and public quote signing target actual event-rental workflows.
2222
- **Deployment pragmatism** — Run it locally, on a LAN, in Docker, or as packaged Windows executables.
2323

24-
## What’s New In v0.0.8
24+
## What’s New In v0.0.9
2525

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.
26+
`v0.0.9` is the security hardening and quote-workflow refactor release. It tightens public/auth/file handling on the backend, breaks the largest quote surfaces into smaller maintainable modules, and standardizes shared pricing logic so the operator and public quote experiences stay in sync as the product grows.
2727

28-
- **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.
29-
- **Admin continuity tooling** — Admin now includes SQLite backup export/import so operators can migrate or restore data without leaving the app.
30-
- **New workspace surfaces** — Added a Directory hub for Leads/Vendors plus dedicated Inventory Settings and Message Settings screens for operational preferences.
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.
28+
- **Security hardening pass** — JWT and extension-token handling is narrowed, file-serving auth is stricter, public quote payloads are least-privilege, upload MIME detection is content-based, and quote email attachments are scoped to the active project.
29+
- **Backend quote-service extraction** — Quote send, duplicate, activity logging, status transitions, and item stats bookkeeping now live in dedicated service/lib modules instead of a single oversized route file.
30+
- **Quote detail decomposition** — QuoteDetailPage now relies on extracted helpers/components and a shared controller hook so editing, files, sending, and totals logic are easier to evolve independently.
31+
- **Quote builder decomposition** — QuoteBuilder has been split into focused line-items, adjustments, and inventory-picker panels, reducing rerender pressure and making future UI changes less risky.
32+
- **Shared totals utility** — Quote total/adjustment logic now comes from one shared utility instead of drifting between QuoteDetail and PublicQuote.
33+
- **Release documentation refresh** — README, changelog, status notes, package versions, and release notes have all been aligned to `0.0.9`.
3634

3735
## Core Features
3836

@@ -54,10 +52,10 @@ BadShuffle is a self-hosted event rental software platform for project-centric q
5452

5553
## Near-Term Roadmap
5654

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.
60-
- **Operations depth** — Send preview, pull sheets, and richer warehouse workflows.
55+
- **Frontend refactor completion** — Finish landing the QuoteDetail/QuoteBuilder extraction work and smoke-test the refactored flows across desktop and mobile.
56+
- **Cross-theme QA + responsive pass** — Close the remaining theme/mobile gaps across quote editing, messages, and modal-heavy views.
57+
- **Workflow safety** — Extend unsaved-change protection and better destructive-action confirmation patterns to other high-risk forms.
58+
- **Operations depth** — Send preview, pull sheets, richer warehouse workflows, and migration/versioning cleanup for the database bootstrap.
6159

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

@@ -491,4 +489,3 @@ Client helpers in `client/src/api.js`: `getVendors`, `getConflicts`, `getQuoteAv
491489
## License
492490

493491
MIT. See [LICENSE](LICENSE).
494-

RELEASE_NOTES_0.0.9.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# BadShuffle 0.0.9 Release Notes
2+
3+
Release date: 2026-03-26
4+
5+
## Summary
6+
7+
`0.0.9` is the security hardening and quote-workflow refactor release. It tightens auth/public/file behavior on the backend, extracts the largest quote flows into reusable services and helpers, and continues the QuoteDetail/QuoteBuilder decomposition so future work can land with less coupling and less drift.
8+
9+
## Highlights
10+
11+
- Stricter auth and public-surface handling around JWT, extension-token access, file serving, and public quote payloads
12+
- Upload validation now uses detected file signatures instead of trusting browser MIME values
13+
- Quote email attachments are restricted to files already linked to the active quote
14+
- `server/routes/quotes.js` now delegates orchestration to:
15+
- `server/lib/quoteActivity.js`
16+
- `server/services/itemStatsService.js`
17+
- `server/services/quoteService.js`
18+
- Shared quote pricing/totals logic moved to `client/src/lib/quoteTotals.js`
19+
- QuoteDetail and QuoteBuilder have been broken into smaller modules, hooks, and focused panels for easier iteration
20+
21+
## Notes
22+
23+
- This remains a `0.x` pre-release line
24+
- The backend extraction work is complete in this release
25+
- Frontend refactor follow-through and broader responsive/theme QA remain active follow-up work

ai/STATUS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# STATUS
22

3+
**Released v0.0.9** (2026-03-26): Security hardening + quote workflow refactor release. Highlights: stricter auth/file/public quote handling; extracted backend quote services (`quoteActivity`, `itemStatsService`, `quoteService`); shared quote totals utility; QuoteDetail and QuoteBuilder decomposition into focused hooks/components/panels; and release/versioning docs aligned to `0.0.9`.
4+
35
**Released v0.0.8** (2026-03-24): Navigation + UX polish release. Highlights: grouped collapsible sidebar with hover flyouts, unread/pending badges, and live team presence; new Directory landing page; new Inventory Settings and Message Settings pages; Admin database export/import; ErrorBoundary fallback; skeleton loaders; contextual empty-search states; lazy-loaded inventory thumbnails; toast `aria-live`; PublicQuotePage `document.title`; theme-token cleanup and layout max-width polish.
46

57
**Released v0.0.7** (2026-03-24): PRD batch 2 — files list view + bulk delete, billing search/sort/export, single-screen new project creation. PRD batch 1 — rename Quotes→Projects, fix inventory hover, file auth, search bars.
@@ -495,4 +497,3 @@ Help: `node server/cli.js --help`
495497

496498
## Next Steps
497499
- Optional: add more target fields to lead import (e.g. guest count, delivery address) if sheet columns expand.
498-

client/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "badshuffle-client",
3-
"version": "0.0.8",
3+
"version": "0.0.9",
44
"private": true,
55
"type": "module",
66
"scripts": {

client/src/api.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ export const api = {
258258
const qs = new URLSearchParams(Object.entries(params || {}).filter(function(e) { return e[1] !== undefined && e[1] !== ''; }));
259259
return request('/messages?' + qs);
260260
},
261+
sendQuoteMessage: (quoteId, body) => request('/messages', { method: 'POST', body: { quote_id: quoteId, ...body } }),
261262
getUnreadCount: () => request('/messages/unread-count'),
262263
markMessageRead: (id) => request('/messages/' + id + '/read', { method: 'PUT' }),
263264
deleteMessage: (id) => request('/messages/' + id, { method: 'DELETE' }),
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { api } from '../api.js';
3+
4+
export default function ImagePicker({ onSelect, classNames = {} }) {
5+
const [open, setOpen] = useState(false);
6+
const [fileImages, setFileImages] = useState([]);
7+
const [invImages, setInvImages] = useState([]);
8+
const [loading, setLoading] = useState(false);
9+
10+
useEffect(() => {
11+
if (!open) return;
12+
setLoading(true);
13+
Promise.all([api.getFiles().catch(() => ({ files: [] })), api.getItems({ hidden: '0' }).catch(() => ({ items: [] }))])
14+
.then(([filesData, itemsData]) => {
15+
setFileImages((filesData.files || []).filter((f) => f.mime_type && f.mime_type.startsWith('image/')));
16+
setInvImages((itemsData.items || []).filter((i) => i.photo_url));
17+
})
18+
.finally(() => setLoading(false));
19+
}, [open]);
20+
21+
if (!open) {
22+
return (
23+
<button type="button" className="btn btn-ghost btn-sm" onClick={() => setOpen(true)}>
24+
Pick image from library
25+
</button>
26+
);
27+
}
28+
29+
return (
30+
<div className={classNames.imagePicker}>
31+
<div className={classNames.imagePickerHeader}>
32+
<span style={{ fontSize: 13, fontWeight: 600 }}>Pick an image</span>
33+
<button type="button" className="btn btn-ghost btn-sm" onClick={() => setOpen(false)}>
34+
Close
35+
</button>
36+
</div>
37+
{loading ? (
38+
<div style={{ padding: 20, textAlign: 'center' }}>
39+
<div className="spinner" />
40+
</div>
41+
) : (
42+
<div className={classNames.imagePickerGrid}>
43+
{fileImages.map((f) => (
44+
<button
45+
key={'f-' + f.id}
46+
type="button"
47+
className={classNames.imagePickerThumb}
48+
onClick={() => {
49+
onSelect(api.fileServeUrl(f.id), null);
50+
setOpen(false);
51+
}}
52+
title={f.original_name}
53+
>
54+
<img
55+
src={api.fileServeUrl(f.id)}
56+
alt={f.original_name}
57+
onError={(e) => {
58+
e.target.style.display = 'none';
59+
}}
60+
/>
61+
</button>
62+
))}
63+
{invImages.map((i) => (
64+
<button
65+
key={'i-' + i.id}
66+
type="button"
67+
className={classNames.imagePickerThumb}
68+
onClick={() => {
69+
onSelect(api.proxyImageUrl(i.photo_url), i.unit_price || null);
70+
setOpen(false);
71+
}}
72+
title={i.title}
73+
>
74+
<img
75+
src={api.proxyImageUrl(i.photo_url)}
76+
alt={i.title}
77+
onError={(e) => {
78+
e.target.style.display = 'none';
79+
}}
80+
/>
81+
</button>
82+
))}
83+
{fileImages.length === 0 && invImages.length === 0 && (
84+
<p style={{ fontSize: 13, color: 'var(--color-text-muted)', padding: 12 }}>
85+
No images found. Upload images to the Files page first.
86+
</p>
87+
)}
88+
</div>
89+
)}
90+
</div>
91+
);
92+
}

client/src/components/Layout.module.css

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,9 @@
108108
overflow-x: hidden;
109109
-webkit-overflow-scrolling: touch;
110110
background: var(--color-surface);
111-
padding: 24px 32px;
111+
padding: 20px 24px;
112112
}
113113

114114
.mainInner {
115115
width: 100%;
116-
max-width: 1440px;
117-
margin: 0 auto;
118116
}

0 commit comments

Comments
 (0)