@stacksjs/defaults 0.75.328 → 0.75.330

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/ai/skills/stacks-dashboard/SKILL.md +2 -2
  2. package/ai/skills/stacks-native/CATALOG.md +2 -2
  3. package/ide/vscode/package.json +1 -1
  4. package/package.json +4 -4
  5. package/resources/components/Dashboard/Auth/ForgotPassword.stx +1 -1
  6. package/resources/components/Dashboard/Ci/CiDashboard.stx +7 -7
  7. package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +3 -3
  8. package/resources/components/Dashboard/Commerce/CommerceCategoriesTable.stx +1 -1
  9. package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +5 -5
  10. package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +2 -2
  11. package/resources/components/Dashboard/Commerce/CommerceCategoryHierarchy.stx +2 -2
  12. package/resources/components/Dashboard/Commerce/CommerceCouponBreakdown.stx +1 -1
  13. package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +9 -9
  14. package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +1 -1
  15. package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +5 -5
  16. package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +2 -2
  17. package/resources/components/Dashboard/Commerce/CommerceCustomersTable.stx +1 -1
  18. package/resources/components/Dashboard/Commerce/CommerceGiftCardBreakdown.stx +1 -1
  19. package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +11 -11
  20. package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +13 -13
  21. package/resources/components/Dashboard/Commerce/CommercePaymentsBreakdown.stx +2 -2
  22. package/resources/components/Dashboard/Commerce/CommercePosCart.stx +1 -1
  23. package/resources/components/Dashboard/Commerce/CommercePosCatalog.stx +1 -1
  24. package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +1 -1
  25. package/resources/components/Dashboard/Commerce/CommerceProductDetailDashboard.stx +8 -8
  26. package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +1 -1
  27. package/resources/components/Dashboard/Commerce/CommerceProductsTable.stx +2 -2
  28. package/resources/components/Dashboard/Commerce/CommerceReviewsTable.stx +1 -1
  29. package/resources/components/Dashboard/Commerce/CommerceVariantsTable.stx +1 -1
  30. package/resources/components/Dashboard/Commerce/Delivery/DeliveryOverviewDashboard.stx +1 -1
  31. package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +2 -2
  32. package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +3 -3
  33. package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +6 -6
  34. package/resources/components/Dashboard/Commerce/PrintLogBreakdown.stx +1 -1
  35. package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +2 -2
  36. package/resources/components/Dashboard/Commerce/PrintLogsTable.stx +3 -3
  37. package/resources/components/Dashboard/Content/AuthorsDashboard.stx +1 -1
  38. package/resources/components/Dashboard/Content/CommentsDashboard.stx +1 -1
  39. package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +1 -1
  40. package/resources/components/Dashboard/Content/FileManagerDashboard.stx +53 -61
  41. package/resources/components/Dashboard/Content/FileStoragePanel.stx +99 -0
  42. package/resources/components/Dashboard/Content/FileUploadDialog.stx +1 -1
  43. package/resources/components/Dashboard/Content/PagesDashboard.stx +1 -1
  44. package/resources/components/Dashboard/Content/PostEditorDialog.stx +2 -2
  45. package/resources/components/Dashboard/Content/PostsDashboard.stx +1 -1
  46. package/resources/components/Dashboard/Content/SeoEditorDialog.stx +1 -1
  47. package/resources/components/Dashboard/Deployments/DeploymentPreviewDialog.stx +1 -1
  48. package/resources/components/Dashboard/Email/CapturedMailDetail.stx +1 -1
  49. package/resources/components/Dashboard/Feedback/FeedbackForm.stx +4 -4
  50. package/resources/components/Dashboard/FileViewer/MediaCard.stx +1 -1
  51. package/resources/components/Dashboard/FileViewer/MediaInfoPanel.stx +4 -4
  52. package/resources/components/Dashboard/GlobalSearch.stx +4 -4
  53. package/resources/components/Dashboard/Infrastructure/CloudDashboard.stx +1 -1
  54. package/resources/components/Dashboard/Infrastructure/DnsDashboard.stx +2 -2
  55. package/resources/components/Dashboard/Infrastructure/LogsDashboard.stx +2 -2
  56. package/resources/components/Dashboard/Infrastructure/MailboxesDashboard.stx +3 -3
  57. package/resources/components/Dashboard/Infrastructure/ServerlessDashboard.stx +2 -2
  58. package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -5
  59. package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +1 -1
  60. package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +2 -2
  61. package/resources/components/Dashboard/Kanban/KanbanCardDialog.stx +8 -8
  62. package/resources/components/Dashboard/Marketing/AbandonedCartsDashboard.stx +5 -5
  63. package/resources/components/Dashboard/Marketing/CampaignsDashboard.stx +5 -5
  64. package/resources/components/Dashboard/Marketing/MarketingListsDashboard.stx +4 -4
  65. package/resources/components/Dashboard/Marketing/SocialPostsDashboard.stx +4 -4
  66. package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
  67. package/resources/components/Dashboard/Navbar.stx +2 -2
  68. package/resources/components/Dashboard/Operations/AuditDashboard.stx +1 -1
  69. package/resources/components/Dashboard/Operations/OperationHistory.stx +1 -1
  70. package/resources/components/Dashboard/Operations/RecoveryJobList.stx +1 -1
  71. package/resources/components/Dashboard/Remote/RemoteConsole.stx +1 -1
  72. package/resources/components/Dashboard/Settings/SettingsDashboard.stx +1 -1
  73. package/resources/components/Dashboard/UI/Checkbox.stx +1 -1
  74. package/resources/components/Dashboard/UI/MetricCards.stx +1 -1
  75. package/resources/components/Dashboard/UI/Pagination.stx +1 -1
  76. package/resources/components/Dashboard/UI/Tabs.stx +7 -4
  77. package/resources/components/Dashboard/UI/Textarea.stx +2 -2
  78. package/resources/components/Dashboard/UI/Toggle.stx +1 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: stacks-dashboard
3
- description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 413 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
3
+ description: Use when building or customizing the Stacks admin dashboard, including dashboard pages, model management views, analytics widgets, commerce dashboards, content management, settings panels, deployment monitoring, job/queue management, or the 414 built-in dashboard components. Covers the dashboard system at storage/framework/defaults/.
4
4
  license: MIT
5
5
  compatibility: Bun >= 1.3.0, TypeScript
6
6
  allowed-tools: Read Edit Write Bash Grep Glob
@@ -8,7 +8,7 @@ allowed-tools: Read Edit Write Bash Grep Glob
8
8
 
9
9
  # Stacks Dashboard
10
10
 
11
- The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 413 components, and a multi-section layout.
11
+ The Stacks admin dashboard provides a full-featured admin panel with 100+ route views, 414 components, and a multi-section layout.
12
12
 
13
13
  ## Key Paths
14
14
  - Dashboard components: `storage/framework/defaults/resources/components/Dashboard/`
@@ -21,7 +21,7 @@ Browser runtime bindings are documented separately in `stacks-auto-imports`.
21
21
  | `@stacksjs/auth` | [`stacks-auth`](../stacks-auth/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/auth/src/index.ts) | 120 |
22
22
  | `@stacksjs/browser` | [`stacks-browser`](../stacks-browser/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser/src/index.ts) | 8 |
23
23
  | `@stacksjs/browser-extension` | [`stacks-browser-extension`](../stacks-browser-extension/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/browser-extension/src/index.ts) | 7 |
24
- | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 166 |
24
+ | `@stacksjs/buddy` | [`stacks-buddy`](../stacks-buddy/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/index.ts) | 167 |
25
25
  | `@stacksjs/build` | [`stacks-build`](../stacks-build/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/build/src/index.ts) | 3 |
26
26
  | `@stacksjs/cache` | [`stacks-cache`](../stacks-cache/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/cache/src/index.ts) | 14 |
27
27
  | `@stacksjs/calendar-api` | [`stacks-calendar`](../stacks-calendar/SKILL.md) | [entrypoint](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/calendar-api/src/index.ts) | 2 |
@@ -217,7 +217,7 @@ Read the entrypoint and package export map for re-exported or type-only surfaces
217
217
 
218
218
  Local source modules: [src/commands/index.ts](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/src/commands/index.ts).
219
219
 
220
- Evidence directory: [166 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
220
+ Evidence directory: [167 retained tests](https://github.com/stacksjs/stacks/blob/main/storage/framework/core/buddy/tests). These files are evidence pointers, not a claim that all tests were run for this catalog.
221
221
 
222
222
  ### @stacksjs/build
223
223
 
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.75.328",
5
+ "version": "0.75.330",
6
6
  "description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.328",
5
+ "version": "0.75.330",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/stacksjs/stacks.git",
@@ -45,9 +45,9 @@
45
45
  "dependencies": {
46
46
  "@iconify-json/f7": "^1.2.2",
47
47
  "@iconify-json/hugeicons": "^1.2.35",
48
- "@stacksjs/mobile": "0.75.328",
49
- "@stacksjs/sanitizer": "0.2.719",
50
- "@stacksjs/stx": "0.2.719",
48
+ "@stacksjs/mobile": "0.75.330",
49
+ "@stacksjs/sanitizer": "0.2.720",
50
+ "@stacksjs/stx": "0.2.720",
51
51
  "@xterm/addon-fit": "^0.11.0",
52
52
  "@xterm/xterm": "^6.0.0",
53
53
  "ts-qr-codes": "^0.1.8"
@@ -35,7 +35,7 @@ function retry(): void {
35
35
  <p class="mb-6 text-neutral-500 text-sm dark:text-neutral-400">
36
36
  We've sent a password reset link to <strong class="text-neutral-700 dark:text-neutral-200">{{ email() }}</strong>
37
37
  </p>
38
- <p class="text-neutral-400 text-xs dark:text-neutral-500">
38
+ <p class="text-neutral-500 text-xs dark:text-neutral-500">
39
39
  Didn't receive the email? Check your spam folder or
40
40
  <Button
41
41
  type="button"
@@ -168,7 +168,7 @@
168
168
  <div :if="loading && !disabled" class="flex items-center justify-center" style="min-height: 50vh">
169
169
  <div class="flex flex-col gap-4 items-center">
170
170
  <span aria-hidden="true" class="h-7 w-7 text-violet-500 animate-spin i-hugeicons-loading-03"></span>
171
- <span class="text-sm text-zinc-400">Fetching CI status…</span>
171
+ <span class="text-sm text-zinc-500 dark:text-zinc-400">Fetching CI status…</span>
172
172
  </div>
173
173
  </div>
174
174
 
@@ -206,7 +206,7 @@
206
206
  <div class="flex flex-wrap gap-x-4 gap-y-1 items-center mb-2 text-sm">
207
207
  <span class="font-medium text-emerald-600 dark:text-emerald-400" :text="passingText"></span>
208
208
  <span class="font-medium text-red-500 dark:text-red-400" :text="failingText"></span>
209
- <span class="text-zinc-400" :text="totalText"></span>
209
+ <span class="text-zinc-500 dark:text-zinc-400" :text="totalText"></span>
210
210
  <span class="text-zinc-300 dark:text-zinc-700">·</span>
211
211
  <span class="text-zinc-500 dark:text-zinc-400" :text="runnersText"></span>
212
212
  </div>
@@ -215,7 +215,7 @@
215
215
  on `ci.alerts.enabled`). Drawn inline as a single SVG path
216
216
  so we don't pull a chart library for 20 datapoints. -->
217
217
  <div :if="hasHistory()" class="flex gap-2 items-center mb-5">
218
- <span class="text-[10px] text-zinc-400 tracking-wide uppercase">Queue depth (last hour)</span>
218
+ <span class="text-[10px] text-zinc-500 tracking-wide uppercase dark:text-zinc-400">Queue depth (last hour)</span>
219
219
  <svg width="200" height="24" viewBox="0 0 200 24" class="text-violet-400">
220
220
  <path :d="sparklinePath()" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
221
221
  </svg>
@@ -251,8 +251,8 @@
251
251
  in a new tab" rather than triggering the drilldown
252
252
  drawer on the parent card. -->
253
253
  <a :href="repo.url" target="_blank" @click.stop class="font-medium no-underline text-sm text-zinc-900 dark:text-zinc-100 hover:underline" :text="repo.name"></a>
254
- <span :if="repo.commitSha" class="font-mono text-xs text-zinc-400" :text="repo.commitSha"></span>
255
- <span :if="repo.updatedAt" class="text-xs text-zinc-400" :text="timeAgo(repo.updatedAt)"></span>
254
+ <span :if="repo.commitSha" class="font-mono text-xs text-zinc-500 dark:text-zinc-400" :text="repo.commitSha"></span>
255
+ <span :if="repo.updatedAt" class="text-xs text-zinc-500 dark:text-zinc-400" :text="timeAgo(repo.updatedAt)"></span>
256
256
  </div>
257
257
 
258
258
  <div :if="repo.commitMessage" class="mt-1">
@@ -260,11 +260,11 @@
260
260
  </div>
261
261
 
262
262
  <div :if="repo.commitAuthor" class="mt-0.5">
263
- <span class="text-xs text-zinc-400" :text="repo.commitAuthor"></span>
263
+ <span class="text-xs text-zinc-500 dark:text-zinc-400" :text="repo.commitAuthor"></span>
264
264
  </div>
265
265
 
266
266
  <div :if="repo.buddyBotPRs > 0 || repo.actionsPRs > 0" class="mt-2 pt-2 border-t border-zinc-200/60 dark:border-zinc-800">
267
- <div class="mb-1 text-xs text-zinc-400">Pull Requests</div>
267
+ <div class="mb-1 text-xs text-zinc-500 dark:text-zinc-400">Pull Requests</div>
268
268
  <div class="flex flex-wrap gap-x-3 gap-y-0.5">
269
269
  <a :if="repo.buddyBotPRs > 0" :href="repo.buddyBotPRsUrl" target="_blank" rel="noreferrer" @click.stop class="no-underline text-xs text-zinc-500 dark:text-zinc-400 hover:underline">
270
270
  buddy-bot: <span :text="repo.buddyBotPRs"></span>
@@ -88,7 +88,7 @@ function failedJobs(runId: number) {
88
88
  <span class="font-medium text-sm text-zinc-800 truncate dark:text-zinc-200" :text="run.name"></span>
89
89
  </div>
90
90
  <p :if="run.commitMessage" class="mt-1 text-xs text-zinc-500 truncate" :text="truncate(run.commitMessage, 80)"></p>
91
- <div class="flex gap-2 items-center mt-1 text-[10px] text-zinc-400">
91
+ <div class="flex gap-2 items-center mt-1 text-[10px] text-zinc-500 dark:text-zinc-400">
92
92
  <span class="font-mono" :text="run.headShaShort"></span>
93
93
  <span :if="run.headBranch" :text="run.headBranch"></span>
94
94
  <span :if="run.commitAuthor">·</span>
@@ -105,13 +105,13 @@ function failedJobs(runId: number) {
105
105
 
106
106
  <div :if="expandedRunId() === run.id" class="px-3 py-2 bg-zinc-50 dark:bg-zinc-900/50 border-t border-zinc-100 dark:border-zinc-900">
107
107
  <div :if="loadingJobsRunId() === run.id" class="py-2 text-xs text-zinc-500">Loading jobs...</div>
108
- <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length === 0" class="py-1 italic text-xs text-zinc-400">No job detail.</div>
108
+ <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length === 0" class="py-1 italic text-xs text-zinc-500 dark:text-zinc-400">No job detail.</div>
109
109
  <div :if="loadingJobsRunId() !== run.id && failedJobs(run.id).length > 0" class="space-y-1.5">
110
110
  <p class="text-[10px] text-zinc-500 tracking-wide uppercase">Failed jobs</p>
111
111
  <a :for="job in failedJobs(run.id)" :href="job.url" target="_blank" rel="noreferrer" @click.stop class="flex gap-1.5 items-center no-underline text-red-700 text-xs dark:text-red-400 hover:underline">
112
112
  <span aria-hidden="true" class="h-2.5 w-2.5 i-hugeicons-cancel-circle"></span>
113
113
  <span :text="job.name"></span>
114
- <span :if="job.durationMs" class="text-zinc-400" :text="'(' + formatDuration(job.durationMs) + ')'"></span>
114
+ <span :if="job.durationMs" class="text-zinc-500 dark:text-zinc-400" :text="'(' + formatDuration(job.durationMs) + ')'"></span>
115
115
  </a>
116
116
  </div>
117
117
  <div :if="loadingJobsRunId() !== run.id && jobsForRun(run.id).length > 0 && failedJobs(run.id).length === 0" class="py-1 text-xs text-zinc-500">
@@ -79,7 +79,7 @@ function handleRowAction(payload: { action: string, row: CommerceCategoryRecord
79
79
  <span class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md" x-text="row.initial"></span>
80
80
  <div class="min-w-0">
81
81
  <p class="font-medium text-gray-900 text-sm dark:text-white" x-text="row.name"></p>
82
- <p class="mt-1 max-w-96 text-gray-400 text-xs truncate dark:text-neutral-500" x-text="row.summary"></p>
82
+ <p class="mt-1 max-w-96 text-gray-500 text-xs truncate dark:text-neutral-500" x-text="row.summary"></p>
83
83
  </div>
84
84
  </div>
85
85
  </template>
@@ -47,11 +47,11 @@ function deleteRecord(): void {
47
47
  </template>
48
48
  <p class="text-gray-600 text-sm whitespace-pre-wrap dark:text-neutral-300">{{ record()?.description || 'No description recorded.' }}</p>
49
49
  <dl class="grid gap-4 grid-cols-2 mt-6">
50
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Parent</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ parentName() }}</dd></div>
51
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Display order</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.displayOrder || 0 }}</dd></div>
52
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Direct children</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ children().length }}</dd></div>
53
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
54
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Image URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.imageUrl || 'Not configured' }}</dd></div>
50
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Parent</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ parentName() }}</dd></div>
51
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Display order</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.displayOrder || 0 }}</dd></div>
52
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Direct children</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ children().length }}</dd></div>
53
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
54
+ <div class="col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Image URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.imageUrl || 'Not configured' }}</dd></div>
55
55
  </dl>
56
56
  <template #footer>
57
57
  <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit category</Button></div></div>
@@ -124,10 +124,10 @@ function errorMessages(): string[] {
124
124
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span><input x-model="name" type="text" maxlength="50" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
125
125
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Slug</span><input x-model="slug" type="text" required spellcheck="false" class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" @input="markSlugTouched" @blur="normalizeSlug" /></label>
126
126
  <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Description</span><textarea x-model="description" rows="3" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"></textarea></label>
127
- <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Image URL</span><input x-model="imageUrl" type="text" placeholder="Optional persisted asset URL" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /><span class="block mt-1 text-gray-400 text-xs dark:text-neutral-500">Use a stored asset URL. File uploads belong to the native storage workflow.</span></label>
127
+ <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Image URL</span><input x-model="imageUrl" type="text" placeholder="Optional persisted asset URL" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /><span class="block mt-1 text-gray-500 text-xs dark:text-neutral-500">Use a stored asset URL. File uploads belong to the native storage workflow.</span></label>
128
128
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Parent category</span><select x-model="parentCategoryId" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"><option value="">Root category</option><template :for="option in parentOptions()" :key="option.id"><option :value="option.id">{{ option.name }}</option></template></select></label>
129
129
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Display order</span><input x-model="displayOrder" type="number" min="0" step="1" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
130
- <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Active in the storefront</span><span class="block mt-0.5 text-gray-400 text-xs dark:text-neutral-500">Availability is independent from the hierarchy.</span></span></label>
130
+ <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Active in the storefront</span><span class="block mt-0.5 text-gray-500 text-xs dark:text-neutral-500">Availability is independent from the hierarchy.</span></span></label>
131
131
  </div>
132
132
  </form>
133
133
  <template #footer>
@@ -21,14 +21,14 @@ function childLabel(id: string): string {
21
21
 
22
22
  <section aria-label="Category hierarchy" class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
23
23
  <div class="flex gap-4 items-center justify-between">
24
- <div><h2 class="font-semibold text-gray-900 text-sm dark:text-white">Root hierarchy</h2><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">Ordered by storefront display order.</p></div>
24
+ <div><h2 class="font-semibold text-gray-900 text-sm dark:text-white">Root hierarchy</h2><p class="mt-1 text-gray-500 text-xs dark:text-neutral-500">Ordered by storefront display order.</p></div>
25
25
  <span class="text-gray-500 text-xs dark:text-neutral-400">{{ roots().length }} roots</span>
26
26
  </div>
27
27
  <div :if="roots().length === 0" class="mt-4 text-gray-500 text-sm dark:text-neutral-400">No root categories recorded.</div>
28
28
  <div :if="roots().length > 0" class="grid gap-2 grid-cols-1 sm:grid-cols-2 xl:grid-cols-5 mt-4">
29
29
  <template :for="record in roots().slice(0, 10)" :key="record.id">
30
30
  <article class="p-3 border border-gray-100 rounded-lg dark:border-neutral-700">
31
- <div class="flex gap-3 items-center"><span class="flex items-center justify-center shrink-0 h-8 w-8 font-semibold text-blue-700 text-xs dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md">{{ record.name.slice(0, 1).toUpperCase() }}</span><div class="min-w-0"><p class="font-medium text-gray-900 text-sm truncate dark:text-white">{{ record.name }}</p><p class="text-gray-400 text-xs dark:text-neutral-500">{{ childLabel(record.id) }} · order {{ record.displayOrder }}</p></div></div>
31
+ <div class="flex gap-3 items-center"><span class="flex items-center justify-center shrink-0 h-8 w-8 font-semibold text-blue-700 text-xs dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md">{{ record.name.slice(0, 1).toUpperCase() }}</span><div class="min-w-0"><p class="font-medium text-gray-900 text-sm truncate dark:text-white">{{ record.name }}</p><p class="text-gray-500 text-xs dark:text-neutral-500">{{ childLabel(record.id) }} · order {{ record.displayOrder }}</p></div></div>
32
32
  </article>
33
33
  </template>
34
34
  </div>
@@ -27,7 +27,7 @@ function mostUsed(): CouponRecord[] {
27
27
  }
28
28
  </script>
29
29
 
30
- <section aria-label="Coupon breakdowns" class="grid gap-3 lg:grid-cols-3">
30
+ <section aria-label="Coupon breakdowns" class="grid gap-3 grid-cols-1 lg:grid-cols-3">
31
31
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 text-sm dark:text-white">Status distribution</h2><div class="mt-4 space-y-3"><template :for="item in statuses()" :key="item.label"><div><div class="flex justify-between text-xs"><span class="text-gray-500 dark:text-neutral-400">{{ item.label }}</span><span class="font-mono text-gray-700 dark:text-neutral-200">{{ item.count }}</span></div><div class="mt-1 h-1.5 bg-gray-100 dark:bg-neutral-700 rounded-full"><div :class="'h-full rounded-full ' + item.color" :style="'width: ' + width(item.count)"></div></div></div></template></div></article>
32
32
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 text-sm dark:text-white">Discount types</h2><dl class="mt-3 divide-gray-100 divide-y dark:divide-neutral-700"><template :for="item in discountTypes()" :key="item.label"><div class="flex items-center justify-between py-3"><dt class="text-gray-500 text-sm dark:text-neutral-400">{{ item.label }}</dt><dd class="font-mono text-gray-900 text-sm dark:text-white">{{ item.count }}</dd></div></template></dl></article>
33
33
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 text-sm dark:text-white">Most redeemed</h2><p :if="mostUsed().length === 0" class="mt-4 text-gray-500 text-sm dark:text-neutral-400">No coupon usage recorded.</p><ol :else class="mt-3 divide-gray-100 divide-y dark:divide-neutral-700"><template :for="record in mostUsed()" :key="record.id"><li class="flex items-center justify-between py-2"><span class="font-mono text-gray-600 text-sm truncate dark:text-neutral-300">{{ record.code }}</span><span class="ml-3 font-mono text-gray-900 text-sm dark:text-white">{{ record.usageCount.toLocaleString() }}</span></li></template></ol></article>
@@ -46,15 +46,15 @@ function deleteRecord(): void {
46
46
  <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span>
47
47
  </template>
48
48
  <dl class="grid gap-4 grid-cols-2">
49
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ discount(record()) }}</dd></div>
50
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
51
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Usage</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.usageCount || 0 }} / {{ record()?.usageLimit || 'No limit' }}</dd></div>
52
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Minimum order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.minOrderAmount ? money(record()?.minOrderAmount || 0) : 'No minimum' }}</dd></div>
53
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Maximum discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.maxDiscountAmount ? money(record()?.maxDiscountAmount || 0) : 'No maximum' }}</dd></div>
54
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Product relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.productId || 'Not linked' }}</dd></div>
55
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Free product reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.freeProductId || 'Not configured' }}</dd></div>
56
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Start date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.startDate || 'Not recorded' }}</dd></div>
57
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">End date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.endDate || 'Not recorded' }}</dd></div>
49
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ discount(record()) }}</dd></div>
50
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
51
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Usage</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.usageCount || 0 }} / {{ record()?.usageLimit || 'No limit' }}</dd></div>
52
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Minimum order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.minOrderAmount ? money(record()?.minOrderAmount || 0) : 'No minimum' }}</dd></div>
53
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Maximum discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.maxDiscountAmount ? money(record()?.maxDiscountAmount || 0) : 'No maximum' }}</dd></div>
54
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Product relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.productId || 'Not linked' }}</dd></div>
55
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Free product reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.freeProductId || 'Not configured' }}</dd></div>
56
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Start date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.startDate || 'Not recorded' }}</dd></div>
57
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">End date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.endDate || 'Not recorded' }}</dd></div>
58
58
  </dl>
59
59
  <template #footer>
60
60
  <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit coupon</Button></div></div>
@@ -132,7 +132,7 @@ function errorMessages(): string[] {
132
132
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Free product ID</span><input x-model="freeProductId" type="text" placeholder="Optional" class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
133
133
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Start date</span><input x-model="startDate" type="date" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
134
134
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">End date</span><input x-model="endDate" type="date" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
135
- <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Enabled for redemption</span><span class="block mt-0.5 text-gray-400 text-xs dark:text-neutral-500">Availability is stored separately from lifecycle status.</span></span></label>
135
+ <label class="flex gap-3 items-center sm:col-span-2 p-3 border border-gray-200 rounded-lg dark:border-neutral-700"><input x-model="isActive" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded" /><span><span class="block font-medium text-gray-700 text-sm dark:text-neutral-200">Enabled for redemption</span><span class="block mt-0.5 text-gray-500 text-xs dark:text-neutral-500">Availability is stored separately from lifecycle status.</span></span></label>
136
136
  </div>
137
137
  </form>
138
138
  <template #footer>
@@ -56,11 +56,11 @@ function deleteRecord(): void {
56
56
  <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Unknown' }}</span>
57
57
  </template>
58
58
  <dl class="grid gap-4 grid-cols-2">
59
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Phone</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.phone || 'Not recorded' }}</dd></div>
60
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Recorded spend</dt><dd class="mt-1 font-medium text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalSpent || 0) }}</dd></div>
61
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Last order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.lastOrder || '') }}</dd></div>
62
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
63
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Avatar URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.avatar || 'Not configured' }}</dd></div>
59
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Phone</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.phone || 'Not recorded' }}</dd></div>
60
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Recorded spend</dt><dd class="mt-1 font-medium text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalSpent || 0) }}</dd></div>
61
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Last order</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.lastOrder || '') }}</dd></div>
62
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Record ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.id || 'Not recorded' }}</dd></div>
63
+ <div class="col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Avatar URL</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.avatar || 'Not configured' }}</dd></div>
64
64
  </dl>
65
65
  <template #footer>
66
66
  <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit customer</Button></div></div>
@@ -91,11 +91,11 @@ function errorMessages(): string[] {
91
91
  <div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
92
92
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span><input x-model="name" type="text" minlength="2" maxlength="255" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
93
93
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Email</span><input x-model="email" type="email" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
94
- <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Phone <span class="font-normal text-gray-400 dark:text-neutral-500">(optional)</span></span><input x-model="phone" type="tel" maxlength="50" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
94
+ <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Phone <span class="font-normal text-gray-500 dark:text-neutral-500">(optional)</span></span><input x-model="phone" type="tel" maxlength="50" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
95
95
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Status</span><select x-model="status" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"><option value="Active">Active</option><option value="Inactive">Inactive</option></select></label>
96
96
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Recorded spend ({{ currency() }})</span><input x-model="totalSpent" type="number" min="0" :step="moneyInputStep(currency())" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
97
97
  <label class="block"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Last order</span><input x-model="lastOrder" type="date" class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /></label>
98
- <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Avatar URL</span><input x-model="avatar" type="url" placeholder="https://cdn.example.com/customers/avatar.jpg" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /><span class="block mt-1 text-gray-400 text-xs dark:text-neutral-500">Use a persisted HTTP asset URL. The dashboard uses initials when rendering the table.</span></label>
98
+ <label class="block sm:col-span-2"><span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Avatar URL</span><input x-model="avatar" type="url" placeholder="https://cdn.example.com/customers/avatar.jpg" required class="mt-1 px-3 py-2 w-full text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700" /><span class="block mt-1 text-gray-500 text-xs dark:text-neutral-500">Use a persisted HTTP asset URL. The dashboard uses initials when rendering the table.</span></label>
99
99
  </div>
100
100
  </form>
101
101
  <template #footer>
@@ -76,7 +76,7 @@ function handleRowAction(payload: { action: string, row: CommerceCustomerRecord
76
76
  <span class="flex items-center justify-center shrink-0 h-9 w-9 font-semibold text-blue-700 text-sm dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-md" x-text="row.initial"></span>
77
77
  <div class="min-w-0">
78
78
  <p class="font-medium text-gray-900 text-sm dark:text-white" x-text="row.nameLabel"></p>
79
- <p class="mt-1 font-mono text-gray-400 text-xs dark:text-neutral-500" x-text="row.idLabel"></p>
79
+ <p class="mt-1 font-mono text-gray-500 text-xs dark:text-neutral-500" x-text="row.idLabel"></p>
80
80
  </div>
81
81
  </div>
82
82
  </template>
@@ -26,7 +26,7 @@ function money(value: number, currency: string): string {
26
26
  <ul :if="summary.currencies.length > 0" class="mt-4 space-y-3">
27
27
  <template :for="item in summary.currencies" :key="item.currency">
28
28
  <li class="flex gap-4 justify-between text-sm">
29
- <span class="text-gray-500 dark:text-neutral-400">{{ item.currency }} <span class="text-gray-400 text-xs dark:text-neutral-500">({{ item.cards }})</span></span>
29
+ <span class="text-gray-500 dark:text-neutral-400">{{ item.currency }} <span class="text-gray-500 text-xs dark:text-neutral-500">({{ item.cards }})</span></span>
30
30
  <span class="font-medium text-gray-900 dark:text-white">{{ money(item.currentBalance, item.currency) }}</span>
31
31
  </li>
32
32
  </template>
@@ -45,17 +45,17 @@ function deleteRecord(): void {
45
45
  <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ record()?.status || 'Not recorded' }}</span>
46
46
  </template>
47
47
  <dl class="grid gap-4 grid-cols-1 sm:grid-cols-2">
48
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Current balance</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ money(record()?.currentBalance || 0, record()?.currency || 'USD') }}</dd></div>
49
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Initial balance</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ money(record()?.initialBalance || 0, record()?.currency || 'USD') }}</dd></div>
50
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
51
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Delivery</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isDigital ? 'Digital' : 'Physical' }} · {{ record()?.isReloadable ? 'Reloadable' : 'Single use' }}</dd></div>
52
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Recipient email</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.recipientEmail || 'Not recorded' }}</dd></div>
53
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Purchaser ID</dt><dd class="mt-1 break-all font-mono text-gray-900 text-sm dark:text-white">{{ record()?.purchaserId || 'Not recorded' }}</dd></div>
54
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Customer ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.customerId || 'Not linked' }}</dd></div>
55
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Template ID</dt><dd class="mt-1 break-all font-mono text-gray-900 text-sm dark:text-white">{{ record()?.templateId || 'Not configured' }}</dd></div>
56
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Expiry date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ dateTime(record()?.expiryDate || '') }}</dd></div>
57
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Last used date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ dateTime(record()?.lastUsedDate || '') }}</dd></div>
58
- <div class="sm:col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Personal message</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.personalMessage || 'No message recorded.' }}</dd></div>
48
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Current balance</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ money(record()?.currentBalance || 0, record()?.currency || 'USD') }}</dd></div>
49
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Initial balance</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ money(record()?.initialBalance || 0, record()?.currency || 'USD') }}</dd></div>
50
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Availability</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isActive ? 'Enabled' : 'Disabled' }}</dd></div>
51
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Delivery</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.isDigital ? 'Digital' : 'Physical' }} · {{ record()?.isReloadable ? 'Reloadable' : 'Single use' }}</dd></div>
52
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Recipient email</dt><dd class="mt-1 break-all text-gray-900 text-sm dark:text-white">{{ record()?.recipientEmail || 'Not recorded' }}</dd></div>
53
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Purchaser ID</dt><dd class="mt-1 break-all font-mono text-gray-900 text-sm dark:text-white">{{ record()?.purchaserId || 'Not recorded' }}</dd></div>
54
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Customer ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.customerId || 'Not linked' }}</dd></div>
55
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Template ID</dt><dd class="mt-1 break-all font-mono text-gray-900 text-sm dark:text-white">{{ record()?.templateId || 'Not configured' }}</dd></div>
56
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Expiry date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ dateTime(record()?.expiryDate || '') }}</dd></div>
57
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Last used date</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ dateTime(record()?.lastUsedDate || '') }}</dd></div>
58
+ <div class="sm:col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Personal message</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.personalMessage || 'No message recorded.' }}</dd></div>
59
59
  </dl>
60
60
  <template #footer>
61
61
  <div class="flex gap-2 justify-between w-full">
@@ -54,19 +54,19 @@ function deleteRecord(): void {
54
54
  <span class="px-2 py-1 font-semibold text-gray-700 text-xs dark:text-neutral-200 bg-gray-100 dark:bg-neutral-700 rounded-full">{{ label(record()?.status || 'UNKNOWN') }}</span>
55
55
  </template>
56
56
  <dl class="grid gap-4 grid-cols-2">
57
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Total</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalAmount || 0, record()?.currency || 'USD') }}</dd></div>
58
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Order type</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ label(record()?.orderType || 'UNKNOWN') }}</dd></div>
59
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Items</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.itemCount || 0 }}</dd></div>
60
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Created</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.createdAt || '') }}</dd></div>
61
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Tax</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.taxAmount || 0, record()?.currency || 'USD') }}</dd></div>
62
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.discountAmount || 0, record()?.currency || 'USD') }}</dd></div>
63
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Delivery fee</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.deliveryFee || 0, record()?.currency || 'USD') }}</dd></div>
64
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Tip</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.tipAmount || 0, record()?.currency || 'USD') }}</dd></div>
65
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Estimated delivery</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.estimatedDeliveryTime || '') }}</dd></div>
66
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Delivery address</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.deliveryAddress || 'Not recorded' }}</dd></div>
67
- <div class="col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Special instructions</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.specialInstructions || 'None recorded' }}</dd></div>
68
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Coupon relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.couponId || 'Not linked' }}</dd></div>
69
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Applied coupon reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.appliedCouponId || 'Not linked' }}</dd></div>
57
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Total</dt><dd class="mt-1 font-semibold text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.totalAmount || 0, record()?.currency || 'USD') }}</dd></div>
58
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Order type</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ label(record()?.orderType || 'UNKNOWN') }}</dd></div>
59
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Items</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ record()?.itemCount || 0 }}</dd></div>
60
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Created</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.createdAt || '') }}</dd></div>
61
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Tax</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.taxAmount || 0, record()?.currency || 'USD') }}</dd></div>
62
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Discount</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.discountAmount || 0, record()?.currency || 'USD') }}</dd></div>
63
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Delivery fee</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.deliveryFee || 0, record()?.currency || 'USD') }}</dd></div>
64
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Tip</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatMoney(record()?.tipAmount || 0, record()?.currency || 'USD') }}</dd></div>
65
+ <div class="col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Estimated delivery</dt><dd class="mt-1 text-gray-900 text-sm dark:text-white">{{ formatDate(record()?.estimatedDeliveryTime || '') }}</dd></div>
66
+ <div class="col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Delivery address</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.deliveryAddress || 'Not recorded' }}</dd></div>
67
+ <div class="col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Special instructions</dt><dd class="mt-1 text-gray-900 text-sm whitespace-pre-wrap dark:text-white">{{ record()?.specialInstructions || 'None recorded' }}</dd></div>
68
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Coupon relation ID</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.couponId || 'Not linked' }}</dd></div>
69
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Applied coupon reference</dt><dd class="mt-1 font-mono text-gray-900 text-sm dark:text-white">{{ record()?.appliedCouponId || 'Not linked' }}</dd></div>
70
70
  </dl>
71
71
  <template #footer>
72
72
  <div class="flex gap-2 justify-between w-full"><Button variant="danger" @click="deleteRecord()">Delete</Button><div class="flex gap-2"><Button variant="secondary" @click="emit('close')">Close</Button><Button @click="editRecord()">Edit order</Button></div></div>
@@ -88,12 +88,12 @@ function money(value: number, currency: string): string {
88
88
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700">
89
89
  <div class="flex items-start justify-between">
90
90
  <h2 class="font-semibold text-gray-900 text-sm dark:text-white">Gross captured by currency</h2>
91
- <span class="text-gray-400 text-xs dark:text-neutral-500">Current view</span>
91
+ <span class="text-gray-500 text-xs dark:text-neutral-500">Current view</span>
92
92
  </div>
93
93
  <div class="mt-4 divide-gray-100 divide-y dark:divide-neutral-700">
94
94
  <template :for="item in currencyRows()" :key="item.currency">
95
95
  <div class="flex items-center justify-between py-2 first:pt-0 last:pb-0">
96
- <div><p class="font-medium text-gray-900 text-sm dark:text-white">{{ item.currency }}</p><p class="text-gray-400 text-xs dark:text-neutral-500">Refunded {{ money(item.refunded, item.currency) }}</p></div>
96
+ <div><p class="font-medium text-gray-900 text-sm dark:text-white">{{ item.currency }}</p><p class="text-gray-500 text-xs dark:text-neutral-500">Refunded {{ money(item.refunded, item.currency) }}</p></div>
97
97
  <p class="font-semibold text-gray-700 text-sm dark:text-neutral-200">{{ money(item.captured, item.currency) }}</p>
98
98
  </div>
99
99
  </template>
@@ -48,7 +48,7 @@ function formatMoney(value: number): string {
48
48
  <Button variant="secondary" size="xs" iconOnly :ariaLabel="'Decrease ' + line.name + ' quantity'" @click="emit('decrease', line.productId)"><span class="block h-3.5 w-3.5 i-hugeicons-minus-sign"></span></Button>
49
49
  <span class="min-w-8 font-semibold text-center text-gray-800 text-sm dark:text-neutral-100">{{ line.quantity }}</span>
50
50
  <Button variant="secondary" size="xs" iconOnly :disabled="line.quantity >= line.inventoryCount" :ariaLabel="'Increase ' + line.name + ' quantity'" @click="emit('increase', line.productId)"><span class="block h-3.5 w-3.5 i-hugeicons-plus-sign"></span></Button>
51
- <span class="ml-auto text-gray-400 text-xs dark:text-neutral-500">{{ line.inventoryCount }} available</span>
51
+ <span class="ml-auto text-gray-500 text-xs dark:text-neutral-500">{{ line.inventoryCount }} available</span>
52
52
  </div>
53
53
  <label class="block mt-3"><span class="sr-only">Instructions for {{ line.name }}</span><input :value="line.specialInstructions" type="text" maxlength="500" placeholder="Optional line instructions" class="px-3 py-1.5 w-full text-gray-900 text-xs dark:text-white bg-gray-50 dark:bg-neutral-900 border border-gray-200 rounded-md dark:border-neutral-700" @input="emit('instructions', { productId: line.productId, event: $event })" /></label>
54
54
  </li>
@@ -71,7 +71,7 @@ function sellable(product: CommercePosProduct): boolean {
71
71
  <div class="flex items-end justify-between mt-auto pt-4">
72
72
  <div>
73
73
  <p class="font-semibold text-gray-900 dark:text-white">{{ formatMoney(product.price) }}</p>
74
- <p :class="'mt-1 text-xs ' + (product.inventoryCount > 0 ? 'text-gray-400 dark:text-neutral-500' : 'text-red-600 dark:text-red-400')">{{ product.inventoryCount.toLocaleString() }} in stock<span :if="product.preparationTime > 0"> · {{ product.preparationTime }} min</span></p>
74
+ <p :class="'mt-1 text-xs ' + (product.inventoryCount > 0 ? 'text-gray-500 dark:text-neutral-500' : 'text-red-600 dark:text-red-400')">{{ product.inventoryCount.toLocaleString() }} in stock<span :if="product.preparationTime > 0"> · {{ product.preparationTime }} min</span></p>
75
75
  </div>
76
76
  <Button size="sm" :disabled="!sellable(product)" :ariaLabel="'Add ' + product.name + ' to cart'" @click="emit('add', product)"><span class="h-4 w-4 i-hugeicons-add-01"></span>Add</Button>
77
77
  </div>
@@ -51,7 +51,7 @@ function formatDate(value: string): string {
51
51
  </div>
52
52
  </template>
53
53
 
54
- <p :if="receipt()?.referenceNumber" class="font-mono text-gray-400 text-xs dark:text-neutral-500">{{ receipt()?.referenceNumber }}</p>
54
+ <p :if="receipt()?.referenceNumber" class="font-mono text-gray-500 text-xs dark:text-neutral-500">{{ receipt()?.referenceNumber }}</p>
55
55
 
56
56
  <ul class="mt-5 divide-gray-100 divide-y dark:divide-neutral-700">
57
57
  <template :for="line in (receipt()?.lines || [])" :key="line.productId"><li class="py-3"><div class="flex gap-3 items-start justify-between"><div><p class="font-medium text-gray-900 text-sm dark:text-white">{{ line.quantity }} × {{ line.name }}</p><p :if="line.specialInstructions" class="mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ line.specialInstructions }}</p></div><p class="font-medium text-gray-700 text-sm whitespace-nowrap dark:text-neutral-200">{{ formatMoney(line.lineTotal) }}</p></div></li></template>
@@ -170,7 +170,7 @@ onMount(() => {
170
170
  <div class="flex gap-3 items-start">
171
171
  <Button variant="secondary" size="md" iconOnly ariaLabel="Back to products" @click="goBack()"><span class="block h-4 w-4 i-hugeicons-arrow-left-02"></span></Button>
172
172
  <div>
173
- <p class="font-medium text-gray-400 text-xs tracking-wide uppercase dark:text-neutral-500">Product {{ productId() }}</p>
173
+ <p class="font-medium text-gray-500 text-xs tracking-wide uppercase dark:text-neutral-500">Product {{ productId() }}</p>
174
174
  <h1 class="mt-1 font-bold text-2xl text-gray-900 dark:text-white">{{ record()?.name || (loading() ? 'Loading product' : 'Product details') }}</h1>
175
175
  <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Persisted Product fields and native commerce relationships.</p>
176
176
  </div>
@@ -191,7 +191,7 @@ onMount(() => {
191
191
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">Inventory</p><p class="mt-2 font-semibold text-gray-900 text-xl dark:text-white">{{ (record()?.inventoryCount || 0).toLocaleString() }}</p></article>
192
192
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">Variants</p><p class="mt-2 font-semibold text-gray-900 text-xl dark:text-white">{{ variants().length }}</p></article>
193
193
  <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">Units</p><p class="mt-2 font-semibold text-gray-900 text-xl dark:text-white">{{ units().length }}</p></article>
194
- <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">Reviews</p><p class="mt-2 font-semibold text-gray-900 text-xl dark:text-white">{{ reviews().total }}</p><p class="mt-1 text-gray-400 text-xs dark:text-neutral-500">{{ reviews().averageRating }} average rating</p></article>
194
+ <article class="p-4 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><p class="text-gray-500 text-xs dark:text-neutral-400">Reviews</p><p class="mt-2 font-semibold text-gray-900 text-xl dark:text-white">{{ reviews().total }}</p><p class="mt-1 text-gray-500 text-xs dark:text-neutral-500">{{ reviews().averageRating }} average rating</p></article>
195
195
  </section>
196
196
 
197
197
  <div class="grid gap-6 grid-cols-1 xl:grid-cols-3">
@@ -204,11 +204,11 @@ onMount(() => {
204
204
  <div class="flex flex-none items-center justify-center h-16 w-16 font-bold text-blue-700 text-lg dark:text-blue-300 bg-blue-50 dark:bg-blue-950/40 rounded-lg"><span class="h-7 w-7 i-hugeicons-package"></span></div>
205
205
  </div>
206
206
  <dl class="grid gap-5 grid-cols-1 sm:grid-cols-2 mt-6 pt-5 border-gray-200 border-t dark:border-neutral-700">
207
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Category</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ record()?.categoryName || 'Unassigned category' }}</dd></div>
208
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Manufacturer</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ record()?.manufacturerName || 'Unassigned manufacturer' }}</dd></div>
209
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Preparation time</dt><dd class="mt-1 text-gray-700 text-sm dark:text-neutral-200">{{ record()?.preparationTime || 0 }} minutes</dd></div>
210
- <div><dt class="text-gray-400 text-xs dark:text-neutral-500">Created</dt><dd class="mt-1 text-gray-700 text-sm dark:text-neutral-200">{{ formatDate(record()?.createdAt || '') }}</dd></div>
211
- <div class="sm:col-span-2"><dt class="text-gray-400 text-xs dark:text-neutral-500">Persisted image URL</dt><dd class="mt-1 break-all font-mono text-gray-600 text-xs dark:text-neutral-300">{{ record()?.imageUrl || 'No image URL recorded.' }}</dd></div>
207
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Category</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ record()?.categoryName || 'Unassigned category' }}</dd></div>
208
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Manufacturer</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ record()?.manufacturerName || 'Unassigned manufacturer' }}</dd></div>
209
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Preparation time</dt><dd class="mt-1 text-gray-700 text-sm dark:text-neutral-200">{{ record()?.preparationTime || 0 }} minutes</dd></div>
210
+ <div><dt class="text-gray-500 text-xs dark:text-neutral-500">Created</dt><dd class="mt-1 text-gray-700 text-sm dark:text-neutral-200">{{ formatDate(record()?.createdAt || '') }}</dd></div>
211
+ <div class="sm:col-span-2"><dt class="text-gray-500 text-xs dark:text-neutral-500">Persisted image URL</dt><dd class="mt-1 break-all font-mono text-gray-600 text-xs dark:text-neutral-300">{{ record()?.imageUrl || 'No image URL recorded.' }}</dd></div>
212
212
  </dl>
213
213
  </section>
214
214
 
@@ -242,7 +242,7 @@ onMount(() => {
242
242
 
243
243
  <div class="grid gap-6 grid-cols-1 xl:grid-cols-2">
244
244
  <section class="p-5 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 dark:text-white">Allergens</h2><div :if="(record()?.allergens || []).length > 0" class="flex flex-wrap gap-2 mt-4"><template :for="allergen in (record()?.allergens || [])" :key="allergen"><span class="px-2.5 py-1 font-medium text-amber-700 text-xs dark:text-amber-300 bg-amber-50 dark:bg-amber-950/40 rounded-full">{{ allergen }}</span></template></div><p :if="(record()?.allergens || []).length === 0" class="mt-4 text-gray-500 text-sm dark:text-neutral-400">No allergens recorded.</p></section>
245
- <section class="p-5 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 dark:text-white">Nutritional information</h2><dl :if="nutritionEntries().length > 0" class="grid gap-3 grid-cols-2 mt-4"><template :for="item in nutritionEntries()" :key="item.label"><div class="p-3 bg-gray-50 dark:bg-neutral-900/50 rounded-md"><dt class="text-gray-400 text-xs dark:text-neutral-500">{{ item.label }}</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ item.value }}</dd></div></template></dl><p :if="nutritionEntries().length === 0" class="mt-4 text-gray-500 text-sm dark:text-neutral-400">No nutritional information recorded.</p></section>
245
+ <section class="p-5 bg-white dark:bg-neutral-800 border border-gray-200 rounded-lg dark:border-neutral-700"><h2 class="font-semibold text-gray-900 dark:text-white">Nutritional information</h2><dl :if="nutritionEntries().length > 0" class="grid gap-3 grid-cols-2 mt-4"><template :for="item in nutritionEntries()" :key="item.label"><div class="p-3 bg-gray-50 dark:bg-neutral-900/50 rounded-md"><dt class="text-gray-500 text-xs dark:text-neutral-500">{{ item.label }}</dt><dd class="mt-1 font-medium text-gray-800 text-sm dark:text-neutral-100">{{ item.value }}</dd></div></template></dl><p :if="nutritionEntries().length === 0" class="mt-4 text-gray-500 text-sm dark:text-neutral-400">No nutritional information recorded.</p></section>
246
246
  </div>
247
247
  </template>
248
248
  </div>