Add order color stripe spec
This commit is contained in:
parent
6d03be2ebc
commit
0d508095f0
1 changed files with 117 additions and 0 deletions
117
docs/superpowers/specs/2026-07-09-order-color-stripe-design.md
Normal file
117
docs/superpowers/specs/2026-07-09-order-color-stripe-design.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# Per-order color stripe on Collection Confirm and Invoicing
|
||||
|
||||
**Goal:** Visually separate each order group on the Collection Confirm and Invoicing pages with a colored left-edge stripe, so the list reads as distinct orders instead of one continuous flow.
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- New helper module `src/lib/order-color.ts` exporting:
|
||||
- A `ORDER_COLOR_PALETTE` const — a curated list of 6 Tailwind colour token pairs (`border-{color}-500/40` + `bg-{color}-500/8`) with a matching translucent stripe.
|
||||
- A `colorForOrder(orderId: string)` function that hashes the id and returns one of the palette entries deterministically. Same id → same color, every render, every page.
|
||||
- Collection Confirm: each group section gets a 4px-wide coloured left-edge stripe. The stripe uses `border-l-4 border-{color}-500/40` on the existing `<section>` (or a thin coloured bar).
|
||||
- Invoicing: same treatment on each `<section>` that represents an order group.
|
||||
- Light unit test for `colorForOrder` covering: deterministic, evenly distributed across the palette, returns a known colour for a known id.
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- The header pills, filter chips, or any non-group element.
|
||||
- Tab-level colouring (each tab, not each order).
|
||||
- Per-status colouring (e.g. a different colour for `awaiting` vs `collected`).
|
||||
- Dark-mode-specific colour variants — the chosen palette already has decent contrast on both.
|
||||
- The Collection Pool page or any other list page.
|
||||
|
||||
## Current State
|
||||
|
||||
Both pages already group items by `orderNumber` and render a `<section>` per group. Each section has `rounded-xl border bg-white p-4 shadow-sm` (Collection Confirm) or `rounded-xl border bg-white p-4 shadow-sm` (Invoicing). The two groups are visually identical, so the eye reads them as one continuous list.
|
||||
|
||||
A deterministic hash-to-palette helper gives the right balance: stable across renders and across the two pages, but visually distinct per order.
|
||||
|
||||
## User Confirmed Rule
|
||||
|
||||
- Use a 6-color curated palette, hashed by order id.
|
||||
|
||||
## UX
|
||||
|
||||
### Per-order stripe
|
||||
|
||||
```
|
||||
┌─Collection Confirm──────────────────┐
|
||||
│ PO-2026-07-009 · Supplier · 3 items │
|
||||
└─ Card ──────────────────────────────┐
|
||||
│ ▌ Supplier name │ ← 4px coloured stripe on the left
|
||||
│ ▌ Description │
|
||||
│ ▌ ... │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Stripe is 4px wide, full height of the group card.
|
||||
- Stripe colour comes from `colorForOrder(group.orderNumber)`. Same order on Collection Confirm and Invoicing renders the same colour.
|
||||
- The stripe is decorative — the rest of the card layout is unchanged.
|
||||
|
||||
### Palette
|
||||
|
||||
Six colors, in stable order, all designed to read on a light background:
|
||||
|
||||
| Index | Tailwind tokens |
|
||||
| ----- | --------------------------- |
|
||||
| 0 | `border-emerald-500/40` |
|
||||
| 1 | `border-sky-500/40` |
|
||||
| 2 | `border-amber-500/40` |
|
||||
| 3 | `border-rose-500/40` |
|
||||
| 4 | `border-violet-500/40` |
|
||||
| 5 | `border-teal-500/40` |
|
||||
|
||||
`bg-{color}-500/8` is also exported for callers that want a faint body tint (not used by this change, but available).
|
||||
|
||||
## Behavior
|
||||
|
||||
### Hashing
|
||||
|
||||
`colorForOrder` uses a small FNV-1a hash so the distribution is even and the helper has no deps. The hash is taken mod 6 to pick the palette index. Empty / undefined input falls back to index 0 so the helper is total.
|
||||
|
||||
```ts
|
||||
const PALETTE_SIZE = 6;
|
||||
const FNV_OFFSET = 0x811c9dc5;
|
||||
const FNV_PRIME = 0x01000193;
|
||||
|
||||
export function colorForOrder(orderId: string | undefined | null): OrderColor {
|
||||
if (!orderId) return ORDER_COLOR_PALETTE[0];
|
||||
let hash = FNV_OFFSET;
|
||||
for (let i = 0; i < orderId.length; i++) {
|
||||
hash ^= orderId.charCodeAt(i);
|
||||
hash = Math.imul(hash, FNV_PRIME) >>> 0;
|
||||
}
|
||||
return ORDER_COLOR_PALETTE[hash % PALETTE_SIZE];
|
||||
}
|
||||
```
|
||||
|
||||
The `>>> 0` keeps the value in the uint32 range so `Math.imul` doesn't produce a negative result on overflow.
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit test (`src/lib/order-color.test.ts`)
|
||||
|
||||
1. Returns the same entry for the same id (deterministic).
|
||||
2. Returns the first palette entry for `""`, `null`, and `undefined`.
|
||||
3. For 60 distinct ids, the distribution across the 6 palette entries is at least 1 of each (catches a degenerate hash).
|
||||
4. Known id → known entry (e.g. `"PO-2026-07-009"` → a specific index captured at test-writing time and locked in).
|
||||
|
||||
### Manual smoke
|
||||
|
||||
1. Open Collection Confirm. The first order has a coloured stripe. Open Invoicing, find the same order. The stripe colour matches.
|
||||
2. Two adjacent orders should have visibly different stripes (probability of same index is 1/6).
|
||||
3. No layout regression — the stripe is 4px and the rest of the card is unchanged.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Every order group on Collection Confirm and Invoicing has a 4px left-edge stripe in a colour from the palette.
|
||||
- The same order renders the same colour on both pages.
|
||||
- Adjacent orders have visibly different stripes (≥ 1/6 chance of collision; visual variation is the goal).
|
||||
- The unit tests pass; full suite non-regressive.
|
||||
|
||||
## Risk / Rollback
|
||||
|
||||
- **Visual risk:** low. The stripe is a thin decorative element; no layout shifts beyond 4px of horizontal padding.
|
||||
- **Hash risk:** none. FNV-1a is stable across runtimes and platforms.
|
||||
- **Rollback:** delete `src/lib/order-color.ts`, remove the `border-l-4 border-{color}-500/40` classes from the section elements in both pages, remove the import.
|
||||
Loading…
Reference in a new issue