Skip to content

Commit 6c6a600

Browse files
committed
release: v0.4.5
1 parent 374c077 commit 6c6a600

39 files changed

Lines changed: 1336 additions & 267 deletions

.claude/settings.local.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

.gitignore

-49 Bytes
Binary file not shown.

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,26 @@ All notable changes are documented here. The project uses [Semantic Versioning](
1313

1414
---
1515

16+
## [0.4.5] - 2026-03-18
17+
18+
### Added
19+
- **Per-item quote pricing controls** — Quote lines now support discount metadata (`discount_type`, `discount_amount`) alongside drag-to-reorder for cleaner quote composition.
20+
- **Reusable quote policy content** — New payment policy and rental terms templates can be managed centrally and attached to individual quotes.
21+
- **Item accessory relationships** — Inventory items can now store accessory links for future quote-builder automation.
22+
- **Repo metadata polish** — Added a real `LICENSE` file and refreshed the GitHub-facing README for clearer discovery and evaluation.
23+
24+
### Changed
25+
- **Public quote experience** — Quote expiration is now surfaced consistently, discounted pricing is reflected in public totals, and public approvals/signatures respect expiration state.
26+
- **Operator UX** — Added UI scale controls, direct quote navigation from Messages, clearer quote status borders, and more polished quote-builder pricing affordances.
27+
- **Quickstart and discoverability** — README now leads with product value, deployment options, badges, keywords, and a cleaner getting-started flow.
28+
29+
### Fixed
30+
- **Legacy public quote API parity**`/api/quotes/public/:token` now returns expiration status, payment policy data, rental terms, and discount fields so the public React page matches the new backend feature set.
31+
- **Accessory messaging accuracy** — InventoryPage no longer claims permanent accessories auto-add to quotes before that behavior exists.
32+
- **Local runtime noise**`.gitignore` now correctly excludes local lockfiles, runtime uploads, and editor-specific config so release diffs stay focused.
33+
34+
---
35+
1636
## [0.4.4] - 2026-03-18
1737

1838
### Added

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 248Tech
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 114 additions & 140 deletions
Large diffs are not rendered by default.

ai/API_DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**All development going forward must consider the API.**
44

5-
This document is part of the AI coordination layer. When implementing features, Cursor and Claude must treat the API as a first-class surface.
5+
This document is part of the AI coordination layer. When implementing features, all editor/agent workflows must treat the API as a first-class surface.
66

77
## Rule
88

ai/CURSOR_BRIEFING.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
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.4.4 (latest release)
7+
## Current state: v0.4.5 (latest release)
88

99
Latest release:
1010

1111
```
12-
release: v0.4.4quote filtering + 2-step creation wizard, public quote live messaging, picker-level availability, theme/map settings, extension extraction hardening, contract_description support
12+
release: v0.4.5line-item discounts, quote expiration, payment policies, rental terms, item accessories, UI scale, public quote parity fixes, and repo/readme polish
1313
```
1414

15+
### What shipped in v0.4.5
16+
- **Quote pricing + layout polish** — Drag-to-reorder line items, per-item discounts, status-colored quote cards, and stronger conflict visibility.
17+
- **Public quote parity** — Public quote route and page now respect quote expiration, show rental/payment policy content, and calculate totals using discounted pricing.
18+
- **Inventory + operator UX** — Item accessories can be linked in Inventory, Messages can jump straight to the related quote, and UI scale can be adjusted globally from Settings.
19+
- **Repo hygiene** — README, quickstart, license, gitignore, and AI coordination docs were refreshed for a cleaner release surface.
20+
1521
### What shipped in v0.4.4
1622
- **Quote list + create flow** — Quotes page now supports search/status/date/venue/balance filters, and a 2-step quote creation wizard with optional Google Places address autocomplete.
1723
- **Public quote messaging** — New no-auth thread routes (`GET/POST /api/quotes/public/:token/messages`) and live message UI on the public quote page.
@@ -132,10 +138,10 @@ docker-entrypoint.sh — Creates /data/uploads, execs CMD
132138

133139
## Git state
134140

135-
- `badshuffle.lock` deleted (runtime lock file, ignored going forward)
136-
- v0.4.3 adds public catalog pages, Docker files, dev-login flow, and AI settings support on the main release line
141+
- Runtime lockfiles, uploads, and local editor config are now ignored
142+
- Canonical release line is `v0.4.5`
137143

138-
Canonical version is v0.4.4 (0.x pre-release until 1.0).
144+
Canonical version is v0.4.5 (0.x pre-release until 1.0).
139145

140146
## Known stubs / incomplete items
141147

ai/FEATURES.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ All features below are implemented unless marked as stub or partial.
2323
## Price Overrides
2424

2525
- **Per-line price override:** `quote_items.unit_price_override` (REAL, nullable). NULL = use inventory unit_price. Set inline in QuoteBuilder (click price → input → Enter/Esc to confirm/cancel). Override shown in purple with ✕ reset button.
26-
- **Effective price:** `effectivePrice(it) = it.unit_price_override ?? it.unit_price` — used in all total computations (QuoteDetailPage, public quote, QuoteExport).
27-
- **Logic location:** `server/routes/quotes.js` (PUT /:id/items/:qitem_id accepts `unit_price_override`; GET /:id returns it), QuoteBuilder (inline edit UI), QuoteDetailPage (`effectivePrice`, `computeTotals`).
26+
- **Per-item discount:** `quote_items.discount_type` (`none` | `percent` | `fixed`) + `quote_items.discount_amount` (REAL). Inline edit in QuoteBuilder (discount badge click → popover). Applied in `effectivePrice()` in QuoteDetailPage and PublicQuotePage.
27+
- **Effective price:** `effectivePrice(it)` — applies `unit_price_override` then discount. Used in all total computations (QuoteDetailPage, PublicQuotePage, QuoteExport).
28+
- **Logic location:** `server/routes/quotes.js` (PUT /:id/items/:qitem_id accepts `unit_price_override`, `discount_type`, `discount_amount`; GET /:id returns them), QuoteBuilder (inline edit UI), QuoteDetailPage (`effectivePrice`, `computeTotals`).
2829

2930
## Quote Adjustments (Discounts & Surcharges)
3031

@@ -52,6 +53,26 @@ All features below are implemented unless marked as stub or partial.
5253
- **Change logs:** contract_logs table stores each body change (user, old/new body). Shown in Contract tab.
5354
- **Logic location:** `server/routes/quotes.js` (contract, contract/logs), `server/index.js` (public POST contract/sign), `client/src/pages/QuoteDetailPage.jsx` (Contract tab), `client/src/pages/PublicQuotePage.jsx` (sign).
5455

56+
## Quote Expiration
57+
58+
- **Fields:** `quotes.expires_at` (TEXT date, YYYY-MM-DD) + `quotes.expiration_message` (TEXT). Set in QuoteDetailPage edit form.
59+
- **Public quote behavior:** If `expires_at < today`, `is_expired = true` is returned by the public quote API. PublicQuotePage shows a customizable expiration banner and hides the contract signature block (replaced with an "expired" placeholder). If the contract was already signed, it remains visible.
60+
- **Logic location:** `server/api/v1.js` (expiration check, injects `is_expired`), `server/routes/quotes.js` (`PUT /:id` saves `expires_at`, `expiration_message`), `client/src/pages/QuoteDetailPage.jsx` (form fields), `client/src/pages/PublicQuotePage.jsx` + `PublicQuotePage.module.css` (banner + expired state).
61+
62+
## Payment Policies
63+
64+
- **Table:** `payment_policies` (id, name, body_text, is_default, created_at).
65+
- **Routes:** `GET/POST/PUT/DELETE /api/templates/payment-policies` (operator+).
66+
- **Quote linkage:** `quotes.payment_policy_id` (nullable FK). Selectable in QuoteDetailPage edit form. Shown as a "Payment Policy" section on the public quote page when set.
67+
- **Logic location:** `server/routes/templates.js`, `server/api/v1.js` (fetches linked policy for public quote), `client/src/pages/TemplatesPage.jsx` (CRUD section), `client/src/pages/QuoteDetailPage.jsx` (selector), `client/src/pages/PublicQuotePage.jsx` (display).
68+
69+
## Rental Terms
70+
71+
- **Table:** `rental_terms` (id, name, body_text, is_default, created_at).
72+
- **Routes:** `GET/POST/PUT/DELETE /api/templates/rental-terms` (operator+).
73+
- **Quote linkage:** `quotes.rental_terms_id` (nullable FK). Selectable in QuoteDetailPage edit form. Shown as a "Rental Terms" section on the public quote page when set.
74+
- **Logic location:** `server/routes/templates.js`, `server/api/v1.js` (fetches linked terms for public quote), `client/src/pages/TemplatesPage.jsx` (CRUD section), `client/src/pages/QuoteDetailPage.jsx` (selector), `client/src/pages/PublicQuotePage.jsx` (display).
75+
5576
## Billing
5677

5778
- **Payments:** Add/remove payments on a quote (amount, method, reference, note, paid_at). Stored in quote_payments; billing_history records payment_received, payment_removed, refunded.
@@ -144,7 +165,12 @@ All features below are implemented unless marked as stub or partial.
144165
- **Extension:** Download extension ZIP (public); extension tokens for API (admin). ExtensionPage.
145166
- **Import:** Inventory from CSV/XLSX/Sheets (sheets.js); leads from CSV/XLSX/Sheets with column mapping (leads preview/import). ImportPage.
146167
- **Stats:** Item usage (times quoted, etc.); StatsPage, ItemDetailPage.
147-
- **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`. SettingsPage (operator).
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).
169+
- **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.
170+
- **Quote tile borders:** `QuoteCard.jsx` shows colored left border by status — draft=yellow, sent=blue, approved/confirmed/closed=green, conflict or unsigned changes=red.
171+
- **Conflict stop sign:** SVG uses `<line>` + `<circle>` for a visible white ! on the red octagon. Present in QuoteCard, QuoteBuilder, QuotePage.
172+
- **Drag-to-reorder quote items:** HTML5 drag handles (⠿) per line item in QuoteBuilder. On drop calls `PUT /api/quotes/:id/items/reorder` with ordered IDs; server updates `sort_order` in a single DB transaction.
173+
- **Item accessories (permanent):** `item_accessories` table (item_id → accessory_id, UNIQUE, ON DELETE CASCADE). CRUD at `GET/POST/DELETE /api/items/:id/accessories`. InventoryPage edit form shows current accessories list and search-to-add. These permanent accessories are data-only — the quote builder does not yet auto-add them when the parent item is added (future enhancement).
148174
- **Admin:** Users, approve/reject, roles, system settings (autokill, update check). AdminPage (admin).
149175

150176
Where a feature is only partially implemented or has known gaps, see **KNOWN_GAPS.md**.

ai/KNOWN_GAPS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Areas that are incomplete, stubbed, or represent technical debt. Use this to kno
5959
- **Local/trusted use:** CORS allows localhost and extension; JWT_SECRET must be set in production. Public quote and approve-by-token are unauthenticated by design (token is secret link).
6060
- **File storage:** Files on disk in `uploads/`; no S3 or external storage. photo_url can be external URL (proxied) or file id (served via /api/files/:id/serve with signed URL for public).
6161
- **Bundles:** item_associations represent parent/child; UI can show components but current quote builder does not auto-expand bundles into line items (add parent or children manually).
62+
- **Permanent accessories:** `item_accessories` table exists and is managed in InventoryPage. When a product is added to a quote, its permanent accessories are NOT automatically added — that auto-add behavior is not yet implemented. The data is stored and surfaced on item edit; the quote builder must be updated to query and auto-insert accessories when the parent item is added.
6263

6364
---
6465

ai/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ This folder holds **consolidated project documentation** so future AI sessions c
1717

1818
## Relation to `ai/` (lowercase)
1919

20-
The repo also has an **`ai/`** folder (lowercase) with coordination docs: HANDOFF.md, STATUS.md, CURSOR_BRIEFING.md, DECISIONS.md, TODO.md, README.md. Those are for Claude/Cursor workflow and task handoff. This **`AI/`** folder is for **system context**: what the app does, how it’s built, and what’s left to do — so any AI (or human) can onboard quickly.
20+
The repo also has an **`ai/`** folder (lowercase) with coordination docs: HANDOFF.md, STATUS.md, CURSOR_BRIEFING.md, DECISIONS.md, TODO.md, README.md. Those are for agent workflow and task handoff. This **`AI/`** folder is for **system context**: what the app does, how it’s built, and what’s left to do — so any AI (or human) can onboard quickly.
2121

2222
When you change the system (e.g. add pull sheets, new routes, new tables), update the relevant file here (e.g. FEATURES.md, DATA_MODELS.md, ARCHITECTURE.md) so the next session stays in sync.

0 commit comments

Comments
 (0)