Skip to content

Commit f7d656a

Browse files
committed
release: v0.0.5
1 parent 42fbc78 commit f7d656a

25 files changed

+745
-52
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ All notable changes are documented here. The project uses [Semantic Versioning](
88
- **0.0.2** - Quote flow, public messaging, and theming release
99
- **0.0.3** - Pricing controls, reusable policies, and UX polish release
1010
- **0.0.4** - Startup stability hotfix release
11+
- **0.0.5** - In-app updater and extension fallback import release
12+
13+
---
14+
15+
## [0.0.5] - 2026-03-23
16+
17+
### Added
18+
- **Packaged in-app updater endpoints** - New authenticated routes `GET /api/updates`, `GET /api/updates/releases`, and `POST /api/updates/apply` provide current-version status, GitHub release listing, and package-aware release installation.
19+
- **Settings update console** - Settings now includes an Updates card with version status, release picker, "What's New" body preview, install trigger, and restart health polling.
20+
- **Extension JSON fallback import** - Import page adds an **Extension JSON** tab so scraped extension payloads can be pasted and imported directly.
21+
- **Bulk item ingestion route** - `POST /api/items/bulk-upsert` now supports extension fallback import with create/update counts in one request.
22+
23+
### Changed
24+
- **Extension connectivity model** - Extension popup now stores a configurable server URL, and extension sync requests use that stored target instead of a hardcoded localhost API endpoint.
25+
- **Extension recovery behavior** - Background sync now always saves the last scraped payload so the popup can export JSON even when network sync fails.
26+
- **Extension download URL resolution** - ExtensionPage download action now respects `VITE_API_BASE` instead of relying on a hardcoded absolute localhost URL.
1127

1228
---
1329

README.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# BadShuffle v0.0.4
1+
# BadShuffle v0.0.5
22

3-
![Release](https://img.shields.io/badge/release-0.0.4-0a7ea4)
3+
![Release](https://img.shields.io/badge/release-0.0.5-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,10 +21,12 @@ BadShuffle is a self-hosted event rental software platform for quoting, inventor
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.4
24+
## What’s New In v0.0.5
2525

26-
- **Fresh-start inventory stability** — Fixed a blank-page crash when no items exist by removing an undefined empty-state reference in `ItemGrid`.
27-
- **Startup fetch resilience**`InventoryPage` now safely handles transient `getItems` failures during sql.js initialization and falls back to an empty list.
26+
- **In-app updater (packaged builds)** — Settings now has an Updates panel that checks GitHub releases, shows release notes, and installs a selected version with restart detection.
27+
- **Authenticated update API** — Added `/api/updates`, `/api/updates/releases`, and `/api/updates/apply` for release status/list/install flows.
28+
- **Extension recovery workflow** — Chrome extension now supports custom server URL configuration, keeps the last scraped items, and can export them as JSON if direct sync fails.
29+
- **Manual extension JSON import** — Import page adds an Extension JSON tab, backed by `POST /api/items/bulk-upsert` for fast create/update ingestion.
2830

2931
## Core Features
3032

@@ -33,7 +35,7 @@ BadShuffle is a self-hosted event rental software platform for quoting, inventor
3335
- **Availability awareness** — Quote conflict checks, oversold detection, subrental needs, and inventory-aware quote building.
3436
- **Public-facing surfaces** — Client quote page, live quote messaging, SEO catalog pages, `robots.txt`, sitemap generation, and JSON-LD metadata.
3537
- **Comms and files** — SMTP send, IMAP reply capture, media library uploads, and quote-linked attachments.
36-
- **Import and sync** — Google Sheets import plus a Chrome extension for syncing items from Goodshuffle Pro.
38+
- **Import and sync** — Google Sheets import plus a Chrome extension for syncing items from Goodshuffle Pro, with manual JSON fallback import.
3739
- **Optional AI** — Per-feature provider settings for OpenAI, Anthropic, and Gemini without making AI a hard dependency.
3840

3941
## What This Demonstrates
@@ -61,7 +63,7 @@ badshuffle/
6163
├── server/ Express API + sql.js SQLite (port 3001)
6264
│ ├── index.js
6365
│ ├── db.js sql.js shim that mirrors better-sqlite3's API
64-
│ ├── routes/ items, quotes, sheets, stats, ai, files, messages, settings, vendors, availability, publicCatalog
66+
│ ├── routes/ items, quotes, sheets, stats, ai, files, messages, settings, vendors, availability, updates, extension, publicCatalog
6567
│ ├── services/ singleInstance, updateCheck, emailPoller (IMAP)
6668
│ └── lib/ authMiddleware, crypto, imageProxy
6769
├── client/ React + Vite SPA (port 5173 in dev)
@@ -312,6 +314,7 @@ All endpoints are prefixed with `/api`. Protected endpoints require `Authorizati
312314
| GET | `/items` | List items (`?search=`, `?hidden=`, `?category=`) |
313315
| POST | `/items` | Create item |
314316
| POST | `/items/upsert` | Create or update by title (used by extension) |
317+
| POST | `/items/bulk-upsert` | Bulk create/update by title (used by Extension JSON import) |
315318
| PUT | `/items/:id` | Update item |
316319
| DELETE | `/items/:id` | Delete item |
317320
| GET | `/items/:id/associations` | Get related items |
@@ -401,6 +404,14 @@ All endpoints are prefixed with `/api`. Protected endpoints require `Authorizati
401404
| PUT | `/vendors/:id` | Update vendor |
402405
| DELETE | `/vendors/:id` | Delete vendor |
403406

407+
### Updates
408+
409+
| Method | Path | Description |
410+
|---|---|---|
411+
| GET | `/updates` | Current version + cached update check status |
412+
| GET | `/updates/releases` | Fetch GitHub releases and flag newer tags |
413+
| POST | `/updates/apply` | Download selected release assets and restart (packaged `.exe` mode only) |
414+
404415
### Public Catalog (no auth)
405416

406417
| Method | Path | Description |
@@ -430,7 +441,7 @@ All endpoints are prefixed with `/api`. Protected endpoints require `Authorizati
430441
| GET | `/v1/docs` | Swagger UI for the versioned API |
431442
| GET | `/v1/openapi.json` | Raw OpenAPI document |
432443

433-
Client helpers in `client/src/api.js`: `getVendors`, `getConflicts`, `getQuoteAvailabilityItems`, `reorderQuoteItems`, `getPaymentPolicies`, `getRentalTerms`, `getItemAccessories`, `getPublicMessages`, `sendPublicMessage`.
444+
Client helpers in `client/src/api.js`: `getVendors`, `getConflicts`, `getQuoteAvailabilityItems`, `reorderQuoteItems`, `getPaymentPolicies`, `getRentalTerms`, `getItemAccessories`, `bulkUpsertItems`, `getUpdateStatus`, `getUpdateReleases`, `applyUpdate`, `getPublicMessages`, `sendPublicMessage`.
434445

435446
---
436447

ai/ARCHITECTURE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
- **Monolith:** Single Node.js server (Express) and single React SPA. No microservices.
66
- **Database:** SQLite via **sql.js** (WASM). Synchronous API; DB persisted to `badshuffle.db` after every write (see `db.js` `_save()`). Path is pkg-aware (next to exe when packaged).
77
- **Auth:** JWT (Bearer). Role stored in DB and fetched via `GET /api/auth/me`; not embedded in JWT so role changes take effect on next page load.
8-
- **Optional services:** IMAP polling (emailPoller) for inbound replies; startup update check (GitHub releases API). Both are optional and fail gracefully.
8+
- **Optional services:** IMAP polling (emailPoller) for inbound replies; startup update check (GitHub releases API). In packaged builds, authenticated update-install routes are also available. All update behavior is designed to fail gracefully.
99

1010
## Folder Structure
1111

@@ -21,9 +21,10 @@ badshuffle/
2121
│ ├── routes/
2222
│ │ ├── auth.js # Login, logout, setup, forgot, reset, /me, extension-token, test-mail
2323
│ │ ├── quotes.js # Quote CRUD, send/approve/revert, contract, files, payments, activity, custom items, filtered quote list
24-
│ │ ├── items.js # Items CRUD, categories, associations (bundles), is_subrental, vendor_id, contract_description
24+
│ │ ├── items.js # Items CRUD, categories, associations (bundles), is_subrental, vendor_id, contract_description, bulk-upsert
2525
│ │ ├── availability.js # Conflicts, subrental-needs, quote conflict check, picker stock endpoint
2626
│ │ ├── vendors.js # Vendors CRUD
27+
│ │ ├── updates.js # Release status/list/apply flow for packaged updater
2728
│ │ ├── leads.js # Leads CRUD, preview/import (CSV/XLSX/Sheets), events
2829
│ │ ├── templates.js # Email + contract templates
2930
│ │ ├── files.js # Upload, list, delete; stored in uploads/

ai/CURSOR_BRIEFING.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
# Cursor Briefing — BadShuffle (as of 2026-03-19)
1+
# Cursor Briefing — BadShuffle (as of 2026-03-23)
22

33
## What this project is
44

55
BadShuffle is a self-hosted inventory and quoting tool for event rental businesses. It runs as two local Windows executables (server + client), or via Docker. Stack: Node/Express + sql.js SQLite on the back end, React + Vite on the front end.
66

7-
## Current state: v0.0.4 (latest release)
7+
## Current state: v0.0.5 (latest release)
88

99
Latest release:
1010

1111
```
12-
release: v0.0.4startup stability hotfix for fresh installs and slow-device initialization paths
12+
release: v0.0.5in-app updater for packaged builds + extension sync fallback import workflow
1313
```
1414

15+
### What shipped in v0.0.5
16+
- **In-app packaged updater** — Added authenticated update routes (`/api/updates`, `/api/updates/releases`, `/api/updates/apply`) and a Settings UI to check releases, inspect notes, install a chosen tag, and auto-reload after restart.
17+
- **Extension sync resilience** — Extension now supports configurable server URL, stores the last scraped payload in local storage, and exposes a one-click JSON export fallback in the popup.
18+
- **Manual fallback import path** — Import page now has an Extension JSON tab that parses extension payloads and bulk upserts via `POST /api/items/bulk-upsert`.
19+
1520
### What shipped in v0.0.4
1621
- **Inventory empty-state crash fix** — Removed an undefined `search` reference in `ItemGrid` that could throw when the inventory list was empty.
1722
- **Startup rejection handling**`InventoryPage` now catches failed `getItems` calls during sql.js startup and degrades gracefully.
@@ -102,8 +107,8 @@ Dockerfile — Multi-stage build
102107
## Git state
103108

104109
- Runtime lockfiles, uploads, and local editor config are ignored.
105-
- Canonical release line is `v0.0.1` through `v0.0.4`.
106-
- Current canonical version is `v0.0.4`.
110+
- Canonical release line is `v0.0.1` through `v0.0.5`.
111+
- Current canonical version is `v0.0.5`.
107112

108113
## Known stubs / incomplete items
109114

ai/FEATURES.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ All features below are implemented unless marked as stub or partial.
1616
## Quote Item Management
1717

1818
- Add/update/remove line items (inventory items) on a quote: quantity, label, sort_order. PATCH for hidden_from_quote.
19+
- **Bulk upsert ingestion:** `POST /api/items/bulk-upsert` accepts `{ items: [...] }` and create/updates by case-insensitive title (used by Extension JSON fallback import).
1920
- **Hide from quote:** `quote_items.hidden_from_quote` — item stays on quote for internal/operations but excluded from client-facing totals and public view. Toggle in QuoteBuilder.
2021
- **Zero-quantity removal behavior:** Updating a quote item or custom item with `quantity = 0` removes the line and logs activity.
2122
- **Logic location:** `server/routes/quotes.js` (POST/PUT/DELETE quote items and custom items), `client/src/components/QuoteBuilder.jsx`.
@@ -108,6 +109,14 @@ All features below are implemented unless marked as stub or partial.
108109
- **Public message thread:** GET/POST `/api/quotes/public/:token/messages` for client-visible conversation history and message posting from the public quote page.
109110
- **Logic location:** `server/index.js` and `server/api/v1.js` (public route), `client/src/pages/PublicQuotePage.jsx`.
110111

112+
## In-App Updates (Packaged Builds)
113+
114+
- **Status/list/apply API:** Authenticated routes `GET /api/updates`, `GET /api/updates/releases`, and `POST /api/updates/apply` expose current version, GitHub release data, and packaged install workflow.
115+
- **Asset swap/restart flow:** Applying an update downloads release assets (`badshuffle-server.exe`, `badshuffle-client.exe`, `www.zip`), writes `_update.bat`, exits current process, and relaunches server after swap/extract.
116+
- **Dev-mode guardrail:** Update install route returns an explicit error in non-packaged/dev mode.
117+
- **Settings UI:** Settings page includes update status badge, release picker, release notes preview, install action, and health polling until restart completes.
118+
- **Logic location:** `server/routes/updates.js`, `server/index.js`, `client/src/pages/SettingsPage.jsx`, `client/src/api.js`.
119+
111120
## Pull Sheets
112121

113122
- **Not implemented.** No pull_sheets table, no routes, no UI. Operations workflow (order → pull sheet → warehouse → load → delivery) is not in the codebase.
@@ -162,10 +171,10 @@ All features below are implemented unless marked as stub or partial.
162171
- **Dashboard:** GET `/api/quotes/summary` (byStatus, revenueByStatus, upcoming, byMonth). DashboardPage. Also Conflicts and Subrental Needs panels (see Availability & Conflict Detection).
163172
- **Presence:** PUT/GET `/api/presence` — in-memory “who’s online” and path. Client reports path on route change; Sidebar can show team online.
164173
- **AI suggest:** POST `/api/ai/suggest` (OpenAI) for item suggestions; AISuggestModal on quote.
165-
- **Extension:** Download extension ZIP (public); extension tokens for API (admin). ExtensionPage.
166-
- **Import:** Inventory from CSV/XLSX/Sheets (sheets.js); leads from CSV/XLSX/Sheets with column mapping (leads preview/import). ImportPage.
174+
- **Extension:** Download extension ZIP (public); extension tokens for API (admin); configurable server URL in popup; JSON export fallback from cached scraped payload when sync fails.
175+
- **Import:** Inventory from CSV/XLSX/Sheets (sheets.js); leads from CSV/XLSX/Sheets with column mapping (leads preview/import); Extension JSON import tab for pasted payloads.
167176
- **Stats:** Item usage (times quoted, etc.); StatsPage, ItemDetailPage.
168-
- **Settings:** Company, tax, currency, SMTP/IMAP; `count_oos_oversold`; AI provider keys (Claude, OpenAI, Gemini) and per-feature enable/model settings; `ui_theme`, `google_places_api_key`, `map_default_style`; `ui_scale` (75–150%, applied as root font-size). SettingsPage (operator).
177+
- **Settings:** Company, tax, currency, SMTP/IMAP; `count_oos_oversold`; AI provider keys (Claude, OpenAI, Gemini) and per-feature enable/model settings; `ui_theme`, `google_places_api_key`, `map_default_style`; `ui_scale` (75–150%, applied as root font-size); updates panel for packaged install flow. SettingsPage (operator).
169178
- **UI Scale:** Range slider 75–150% (step 5) in SettingsPage. Applies immediately via `document.documentElement.style.fontSize = (scale/100)*14 + 'px'`. Persisted to `localStorage` (`bs_ui_scale`) and loaded by `main.jsx` before first render.
170179
- **Quote tile borders:** `QuoteCard.jsx` shows colored left border by status — draft=yellow, sent=blue, approved/confirmed/closed=green, conflict or unsigned changes=red.
171180
- **Conflict stop sign:** SVG uses `<line>` + `<circle>` for a visible white ! on the red octagon. Present in QuoteCard, QuoteBuilder, QuotePage.

ai/PROJECT_OVERVIEW.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ There is **no pull-sheet or warehouse-fulfillment module** in the codebase; oper
3030
| **Files** | Upload/list/delete files; attach to quotes; serve with auth or signed URL |
3131
| **Messages** | Outbound emails logged; inbound replies via IMAP poll; thread view per quote |
3232
| **Templates** | Email templates (CRUD, default); contract templates (reusable contract body) |
33-
| **Settings** | Company, tax, currency, SMTP/IMAP, count_oos_oversold (operator-only) |
33+
| **Settings** | Company, tax, currency, SMTP/IMAP, count_oos_oversold, packaged update controls (operator-only) |
3434
| **Availability** | Conflicts (reserved > stock), subrental-needs, per-quote conflict check; dashboard panels; quote builder conflict icons |
3535
| **Vendors** | CRUD vendors; items can have is_subrental and vendor_id; Vendors page |
36+
| **Updates** | Authenticated packaged updater routes for release status/list/install (`/api/updates*`) |
3637
| **Admin** | Users, roles, approval, system settings (autokill, update check) (admin-only) |
3738
| **Presence** | In-memory “who’s online” and current path (PUT/GET /api/presence) |
3839

ai/STATUS.md

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

3+
**Released v0.0.5** (2026-03-23): In-app update flow shipped for packaged builds (`/api/updates` status/list/apply + Settings updates console), plus extension sync hardening (configurable server URL, persisted scraped payload export) and Import-page Extension JSON fallback backed by `POST /api/items/bulk-upsert`.
4+
35
**Released v0.0.4** (2026-03-19): Startup stability hotfix shipped. Fixed inventory empty-state crash caused by an undefined `search` reference and added graceful `getItems` error handling during sql.js initialization.
46

57
**Released v0.0.3** (2026-03-18): Post-v0.0.2 enhancement batch shipped. Highlights: quote line-item discounts, quote expiration, reusable payment/rental terms, item accessories, UI scale, quote card polish, public quote parity fixes, and GitHub/docs cleanup.

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.4",
3+
"version": "0.0.5",
44
"private": true,
55
"type": "module",
66
"scripts": {

client/src/api.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const api = {
8181
getItem: (id) => request(`/items/${id}`),
8282
createItem: (body) => request('/items', { method: 'POST', body }),
8383
upsertItem: (body) => request('/items/upsert', { method: 'POST', body }),
84+
bulkUpsertItems: (items) => request('/items/bulk-upsert', { method: 'POST', body: { items } }),
8485
updateItem: (id, body) => request(`/items/${id}`, { method: 'PUT', body }),
8586
deleteItem: (id) => request(`/items/${id}`, { method: 'DELETE' }),
8687
getCategories: () => request('/items/categories'),
@@ -262,6 +263,11 @@ export const api = {
262263
getConflicts: () => request('/availability/conflicts'),
263264
getSubrentals: () => request('/availability/subrentals'),
264265

266+
// Updates
267+
getUpdateStatus: () => request('/updates'),
268+
getUpdateReleases: () => request('/updates/releases'),
269+
applyUpdate: (tag) => request('/updates/apply', { method: 'POST', body: { tag } }),
270+
265271
// Public catalog (no auth)
266272
catalog: {
267273
getMeta: () => publicRequest('/public/catalog-meta'),

0 commit comments

Comments
 (0)