diff --git a/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs b/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs
index 2d4f022..a0c527a 100644
--- a/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs
+++ b/src/WidgetWorks.Infrastructure/Seeding/DbSeeder.cs
@@ -29,11 +29,31 @@ public sealed class DbSeeder(IDbConnectionFactory factory, IPasswordHasher hashe
{
private static readonly (string Sku, string Name, string Description, decimal Price, int OnHand)[] DemoWidgets =
[
- ("WW-001", "Standard Widget", "The dependable everyday widget.", 9.99m, 250),
- ("WW-002", "Deluxe Widget", "Premium finish with a reinforced housing.", 24.99m, 120),
- ("WW-003", "Mega Widget", "Oversized widget for heavy-duty jobs.", 49.99m, 60),
- ("WW-004", "Mini Widget", "Compact widget for tight spaces.", 4.99m, 500),
- ("WW-005", "Widget Pro Kit", "Bundle of assorted widgets and accessories.", 79.99m, 40),
+ ("WW-001", "Standard Widget Block", "The dependable everyday widget.", 9.99m, 250),
+ ("WW-002", "Standard Widget Rotary", "Dial-adjustable everyday widget.", 11.49m, 220),
+ ("WW-003", "Standard Widget Valve", "Inline everyday widget with twin ports.", 12.99m, 180),
+ ("WW-004", "Standard Widget Hub", "Four-port everyday widget.", 13.99m, 160),
+ ("WW-005", "Standard Widget Turbine", "Ventilated everyday widget.", 14.99m, 140),
+ ("WW-006", "Deluxe Widget Block", "Premium finish with a reinforced housing.", 24.99m, 120),
+ ("WW-007", "Deluxe Widget Rotary", "Premium dial with a gold-plated pointer.", 27.49m, 110),
+ ("WW-008", "Deluxe Widget Valve", "Premium inline widget, machined ports.", 29.99m, 90),
+ ("WW-009", "Deluxe Widget Hub", "Premium four-port widget, gold contacts.", 32.99m, 80),
+ ("WW-010", "Deluxe Widget Turbine", "Premium ventilated widget, balanced rotor.", 34.99m, 70),
+ ("WW-011", "Mega Widget Block", "Oversized widget for heavy-duty jobs.", 49.99m, 60),
+ ("WW-012", "Mega Widget Rotary", "Oversized dial widget for heavy-duty jobs.", 54.49m, 55),
+ ("WW-013", "Mega Widget Valve", "Oversized inline widget, high-flow ports.", 59.99m, 45),
+ ("WW-014", "Mega Widget Hub", "Oversized four-port widget for busy lines.", 64.99m, 40),
+ ("WW-015", "Mega Widget Turbine", "Oversized ventilated widget, high throughput.", 69.99m, 35),
+ ("WW-016", "Mini Widget Block", "Compact widget for tight spaces.", 4.99m, 500),
+ ("WW-017", "Mini Widget Rotary", "Compact dial widget for tight spaces.", 5.99m, 460),
+ ("WW-018", "Mini Widget Valve", "Compact inline widget, low-flow ports.", 6.49m, 420),
+ ("WW-019", "Mini Widget Hub", "Compact four-port widget for tight spaces.", 7.49m, 380),
+ ("WW-020", "Mini Widget Turbine", "Compact ventilated widget, quiet running.", 8.49m, 340),
+ ("WW-021", "Widget Pro Kit", "Bundle of assorted widgets and accessories.", 79.99m, 40),
+ ("WW-022", "Widget Starter Kit", "Open-tray bundle for a first build.", 39.99m, 90),
+ ("WW-023", "Widget Builder Kit", "Three-drawer cabinet of sorted widgets.", 99.99m, 30),
+ ("WW-024", "Widget Travel Kit", "Soft-sided bundle for work away from the bench.", 49.99m, 60),
+ ("WW-025", "Widget Master Kit", "Two-case bundle covering the full range.", 149.99m, 20),
];
public async Task SeedAsync(SeedOptions options, CancellationToken ct)
diff --git a/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs b/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs
index 81d5846..c22b744 100644
--- a/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs
+++ b/tests/WidgetWorks.IntegrationTests/SeederAndMigrationTests.cs
@@ -107,13 +107,15 @@ public async Task Seeding_stocks_the_demo_catalog_and_repeats_safely()
await Seeder.SeedAsync(Options(Guid.NewGuid().ToString("N")[..8]), CancellationToken.None);
var standard = await widgets.GetBySkuAsync("WW-001", CancellationToken.None);
- Assert.Equal("Standard Widget", standard!.Name);
+ Assert.Equal("Standard Widget Block", standard!.Name);
Assert.Equal(9.99m, standard.Price);
await Seeder.SeedAsync(Options(Guid.NewGuid().ToString("N")[..8]), CancellationToken.None);
- // Still one row per SKU after a second run.
- Assert.Equal(1, await widgets.CountAsync(new WidgetQuery("Standard Widget", true, 1, 50), CancellationToken.None));
+ // Still one row per SKU after a second run. The search has to name a whole
+ // product: each department seeds five widgets, so "Standard Widget" on its own
+ // now matches all five of them.
+ Assert.Equal(1, await widgets.CountAsync(new WidgetQuery("Standard Widget Block", true, 1, 50), CancellationToken.None));
}
[Fact]
diff --git a/web/public/products/widget.svg b/web/public/products/widget.svg
index 83aed71..dfaf001 100644
--- a/web/public/products/widget.svg
+++ b/web/public/products/widget.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-001.svg b/web/public/products/ww-001.svg
index fb98253..d20f347 100644
--- a/web/public/products/ww-001.svg
+++ b/web/public/products/ww-001.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-002.svg b/web/public/products/ww-002.svg
index 8e4c29d..de7640a 100644
--- a/web/public/products/ww-002.svg
+++ b/web/public/products/ww-002.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-003.svg b/web/public/products/ww-003.svg
index 1fc1560..14ed72c 100644
--- a/web/public/products/ww-003.svg
+++ b/web/public/products/ww-003.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-004.svg b/web/public/products/ww-004.svg
index 10693cb..880a1d9 100644
--- a/web/public/products/ww-004.svg
+++ b/web/public/products/ww-004.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-005.svg b/web/public/products/ww-005.svg
index acd6690..1b87fa5 100644
--- a/web/public/products/ww-005.svg
+++ b/web/public/products/ww-005.svg
@@ -1 +1 @@
-
+
diff --git a/web/public/products/ww-006.svg b/web/public/products/ww-006.svg
new file mode 100644
index 0000000..c661575
--- /dev/null
+++ b/web/public/products/ww-006.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-007.svg b/web/public/products/ww-007.svg
new file mode 100644
index 0000000..e27ef45
--- /dev/null
+++ b/web/public/products/ww-007.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-008.svg b/web/public/products/ww-008.svg
new file mode 100644
index 0000000..5b42ef6
--- /dev/null
+++ b/web/public/products/ww-008.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-009.svg b/web/public/products/ww-009.svg
new file mode 100644
index 0000000..c58cb7c
--- /dev/null
+++ b/web/public/products/ww-009.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-010.svg b/web/public/products/ww-010.svg
new file mode 100644
index 0000000..0ff15d2
--- /dev/null
+++ b/web/public/products/ww-010.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-011.svg b/web/public/products/ww-011.svg
new file mode 100644
index 0000000..4ab3ade
--- /dev/null
+++ b/web/public/products/ww-011.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-012.svg b/web/public/products/ww-012.svg
new file mode 100644
index 0000000..ce49a0c
--- /dev/null
+++ b/web/public/products/ww-012.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-013.svg b/web/public/products/ww-013.svg
new file mode 100644
index 0000000..fdad27b
--- /dev/null
+++ b/web/public/products/ww-013.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-014.svg b/web/public/products/ww-014.svg
new file mode 100644
index 0000000..bd9d188
--- /dev/null
+++ b/web/public/products/ww-014.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-015.svg b/web/public/products/ww-015.svg
new file mode 100644
index 0000000..37283b9
--- /dev/null
+++ b/web/public/products/ww-015.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-016.svg b/web/public/products/ww-016.svg
new file mode 100644
index 0000000..7f6758c
--- /dev/null
+++ b/web/public/products/ww-016.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-017.svg b/web/public/products/ww-017.svg
new file mode 100644
index 0000000..078bde6
--- /dev/null
+++ b/web/public/products/ww-017.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-018.svg b/web/public/products/ww-018.svg
new file mode 100644
index 0000000..0691471
--- /dev/null
+++ b/web/public/products/ww-018.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-019.svg b/web/public/products/ww-019.svg
new file mode 100644
index 0000000..9cfc4e7
--- /dev/null
+++ b/web/public/products/ww-019.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-020.svg b/web/public/products/ww-020.svg
new file mode 100644
index 0000000..d03752b
--- /dev/null
+++ b/web/public/products/ww-020.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-021.svg b/web/public/products/ww-021.svg
new file mode 100644
index 0000000..bc1f3f6
--- /dev/null
+++ b/web/public/products/ww-021.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-022.svg b/web/public/products/ww-022.svg
new file mode 100644
index 0000000..dd27f3d
--- /dev/null
+++ b/web/public/products/ww-022.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-023.svg b/web/public/products/ww-023.svg
new file mode 100644
index 0000000..6e44611
--- /dev/null
+++ b/web/public/products/ww-023.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-024.svg b/web/public/products/ww-024.svg
new file mode 100644
index 0000000..5d1d4ba
--- /dev/null
+++ b/web/public/products/ww-024.svg
@@ -0,0 +1 @@
+
diff --git a/web/public/products/ww-025.svg b/web/public/products/ww-025.svg
new file mode 100644
index 0000000..b5e1b45
--- /dev/null
+++ b/web/public/products/ww-025.svg
@@ -0,0 +1 @@
+
diff --git a/web/src/lib/img.ts b/web/src/lib/img.ts
index e934a79..e896ce8 100644
--- a/web/src/lib/img.ts
+++ b/web/src/lib/img.ts
@@ -3,12 +3,15 @@
// SKU (web/public/products) so the grid needs no external image service and
// renders offline. Real product images set on the widget (admin) take
// precedence; SKUs without bespoke art share a generic widget illustration.
-const ILLUSTRATED_SKUS = new Set(['ww-001', 'ww-002', 'ww-003', 'ww-004', 'ww-005'])
+const ILLUSTRATED = /^ww-(\d{3})$/
+const ILLUSTRATED_COUNT = 25
export function productImage(w: { sku: string; imageUrl?: string | null }): string {
if (w.imageUrl && w.imageUrl.trim()) return w.imageUrl
const sku = (w.sku || '').toLowerCase()
- return ILLUSTRATED_SKUS.has(sku) ? `/products/${sku}.svg` : '/products/widget.svg'
+ const match = ILLUSTRATED.exec(sku)
+ const n = match ? Number(match[1]) : 0
+ return n >= 1 && n <= ILLUSTRATED_COUNT ? `/products/${sku}.svg` : '/products/widget.svg'
}
// Deterministic, cosmetic star rating derived from the SKU. Placeholder visuals