You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -186,6 +186,8 @@ Every module **must** have:
186
186
187
187
Before writing inline HTML in a LiveView template, check `CoreComponents` first. Domain-specific components live in `InvoiceComponents`, `CertificateComponents`, and `SettingsComponents`. Extract shared patterns when they appear 3+ times. For the full component reference, color tokens, DaisyUI usage, layout system, typography, and common page patterns, see `docs/frontend.md`. When generating frontend code, use the `/frontend` skill.
188
188
189
+
For **visual** decisions (what a new surface should look like, empty-state copy, badge tones, layout density), the source of truth is the prototype kit under `docs/design_system/`. Invoke the `/ksef-hub-design` skill or read `DESIGN_SYSTEM.md` before designing any new UI — don't invent tokens, patterns, or copy that aren't already in the kit.
190
+
189
191
## Testing
190
192
191
193
We follow TDD (red-green-refactor). Prefer using ExUnit with `async: true` when safe (avoid for DB or integration tests that share resources), ExMachina for test data factories, and Mox for mocking external services. For test structure, factory patterns, Mox setup, and fixture conventions, see `docs/tests.md`.
Copy file name to clipboardExpand all lines: DESIGN_SYSTEM.md
+70-52Lines changed: 70 additions & 52 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,7 +38,7 @@ Key semantic pairs:
38
38
39
39
---
40
40
41
-
## 2. The 11 rules that make it look right
41
+
## 2. The 10 rules that make it look right
42
42
43
43
These are the ones Claude Code gets wrong most often. Treat as constraints.
44
44
@@ -47,12 +47,11 @@ These are the ones Claude Code gets wrong most often. Treat as constraints.
47
47
3.**Status → always a `<Badge variant="…">`** (not a raw span). Variants: `success | warning | error | info | muted | purple | brand`. Never colored dots alone.
48
48
4.**Row density is tight.** Table cells: `px-4 py-3`. Headers: `px-4 py-2.5 text-xs uppercase tracking-wide text-[var(--muted-foreground)] font-medium` on `bg-[var(--muted)]/50`. No extra padding "to breathe."
49
49
5.**Row hover is `bg-[var(--accent)]`.** Selected row gets the same. A trailing chevron may appear on hover only: `opacity-0 group-hover:opacity-100`.
50
-
6.**Empty state string: "No data for selected period".** Centered, `text-sm text-[var(--muted-foreground)]`, `py-12`. Don't invent copy.
50
+
6.**Empty states follow the two-pattern rule in §7.** Filter-empty = terse string`"No data for selected period"`. Zero-data empty = `<EmptyState>` component. Never mix them.
51
51
7.**Bulk action bar on selection**: inverted — `bg-[var(--foreground)] text-[var(--background)]` above the table header row, with count · summary · actions (secondary inline buttons, not full `Button` components inside).
52
52
8.**Tabs above tables**: underline style. Active tab has a 2px bottom bar (`bg-[var(--foreground)]`), inactive is `text-[var(--muted-foreground)]`. Each tab shows a count pill (see §4).
10.**Focus rings are thin.**`focus:ring-1 focus:ring-[var(--ring)]`, never `ring-2` or `ring-offset-*`. No glowy focus.
55
-
11.**Interaction feel is `transition-all` + `active:scale-[0.98]`, nothing else.** All interactive elements animate with `transition-all` (150ms ease-out default). Hover = `opacity-90` on filled variants, `bg-[var(--accent)]` on outline/ghost. Pressed = `active:scale-[0.98]` on buttons; outline/ghost also get `active:opacity-80`. Row hover stays `bg-[var(--accent)]` — no scale. Never add box-shadow lifts, color shifts, or `hover:-translate-*` transforms.
56
55
57
56
---
58
57
@@ -66,9 +65,6 @@ Use these exact components from `Primitives.jsx`. Don't re-implement.
@@ -87,6 +83,12 @@ Use these exact components from `Primitives.jsx`. Don't re-implement.
87
83
- Fixed 24×24 viewBox, `stroke="currentColor" strokeWidth="1.5"`. Size in px.
88
84
- Only use names present in `Primitives.jsx`. If you need a new one, add it there — don't inline SVGs elsewhere.
89
85
86
+
### `<EmptyState icon title sub action tone>`
87
+
- The canonical zero-data surface. See §7 for when to use it vs. the filter-empty string.
88
+
-`tone`: `default | locked | warning`
89
+
-`action` is an optional `<Button size="sm">` — typically `variant="primary"` for a direct CTA (e.g. *Add note*) or `variant="outline"` for a softer nudge (e.g. *Write a comment*).
90
+
-`title` max ~40 chars; `sub` max ~140 chars. No marketing fluff.
91
+
90
92
### `<PageHeader title subtitle actions>`
91
93
- Every page starts with this. Bottom border, `mb-6`.
92
94
- Actions are right-aligned; primary always rightmost.
| Pagination text |`text-xs`|`text-muted-foreground`|
210
-
211
-
---
212
-
213
-
## 6. Column conventions (invoice-like tables)
177
+
## 5. Column conventions (invoice-like tables)
214
178
215
179
Fixed column order when applicable:
216
180
@@ -225,7 +189,7 @@ Fixed column order when applicable:
225
189
226
190
---
227
191
228
-
## 7. Do / Don't
192
+
## 6. Do / Don't
229
193
230
194
**Do**
231
195
- Reach for existing primitives first (`Badge`, `Button`, `Card`, `Input`, `Icon`).
@@ -236,8 +200,6 @@ Fixed column order when applicable:
236
200
237
201
**Don't**
238
202
- Don't add gradients, emoji, drop shadows on cards, or colored left borders on rows.
239
-
- Don't add `hover:-translate-*`, shadow lifts, or color-shift hover effects on buttons — use `hover:opacity-90` (filled) or `hover:bg-[var(--accent)]` (outline/ghost) only.
240
-
- Don't use `transition-colors` on buttons — always `transition-all` so the native `:active` state animates too.
241
203
- Don't introduce solid-filled status pills (always tinted).
242
204
- Don't use `<div>`s where `<Badge>` should go.
243
205
- Don't add new colors outside the token set — compose with `color-mix(in oklch, var(--x) 10%, transparent)` if you need a faint surface.
@@ -248,13 +210,69 @@ Fixed column order when applicable:
248
210
249
211
---
250
212
213
+
## 7. Empty states
214
+
215
+
Two distinct patterns. Pick by asking: **"Would clearing filters reveal content?"**
216
+
217
+
### Pattern A — Filter-empty
218
+
*A list has data, but current filters hide all of it.*
219
+
220
+
- Copy: `"No data for selected period"` — exact string, no variations.
221
+
- Styling: `text-sm text-[var(--muted-foreground)] py-12` centered inside the table body.
222
+
-**No icon, no CTA.** The resolution (clear filters) lives above the table.
223
+
224
+
Used by: Invoices list, Payments list, Sync jobs, Companies search, any future table.
225
+
226
+
### Pattern B — Zero-data
227
+
*There is genuinely nothing here yet, for this record or feature.*
228
+
229
+
Use the `<EmptyState>` component. Three tones:
230
+
231
+
| Tone | When | CTA? |
232
+
|---|---|---|
233
+
|`default`| Nothing here yet, but the user could create something | Optional primary CTA |
234
+
|`locked`| Feature doesn't apply to this record (e.g. Payments on an income invoice) | Never — don't fake-tease a feature |
235
+
|`warning`| Something failed (OCR, sync) and needs user action | Always — `Retry …`|
236
+
237
+
### Rules
238
+
239
+
1.**One paragraph, max 140 chars** in `sub`. No marketing fluff.
240
+
2.**Never use the filter-empty string for a zero-data state** — copy must match the real cause.
241
+
3.**CTAs from empty states are never destructive.** No "Delete invoice" on empty Payments.
242
+
4.**Locked states don't show disabled CTAs.** State *why* it doesn't apply, then stop.
243
+
5.**No illustrations, no confetti, no color.** Same muted circle icon used everywhere else. *Exception:*`tone="warning"` (see `EmptyState` in `Primitives.jsx`) applies the warning tint to the circle icon because the state signals a real failure that needs attention; it must still pair with a succinct `sub` and a retry CTA. All other tones stay colorless.
244
+
6.**Activity logs are never empty.** A fetch event always exists — don't ship an empty state for Activity.
245
+
246
+
### Invoice detail tab examples
247
+
248
+
| Tab | Condition | Tone | Copy |
249
+
|---|---|---|---|
250
+
| Line items |`extraction=failed`|`warning`|*Line items couldn't be extracted* — The OCR run failed. Retry or enter items manually. |
251
+
| Payments | No requests, invoice is expense + approved |`default`|*No payment requests yet* — Create one to schedule a transfer for this invoice. |
252
+
| Payments | Invoice is income or rejected |`locked`|*Payments don't apply here* — Income and rejected invoices don't generate outgoing transfers. |
253
+
| Notes | None |`default`|*No notes yet* — Notes are private to your team. Use them to record decisions or context. |
254
+
| Comments | None |`default`|*Start the conversation* — @mention a teammate to pull them in. |
255
+
| Access | Owner only |`default`|*You're the sole user with access* — Share this invoice with teammates to collaborate. |
256
+
257
+
### App-wide examples
258
+
259
+
| Surface | State | Tone | Copy |
260
+
|---|---|---|---|
261
+
| Dashboard tile | New tenant, no invoices yet |`default`|*No invoices this month yet* — Sync runs hourly, or add one manually. |
262
+
| Companies | New tenant |`default`|*Add your first company* — Register a NIP to start syncing invoices, or wait for an invitation from an existing company. |
263
+
| Settings → Certificates | Not uploaded |`warning`|*No certificate uploaded* — Upload a XAdES certificate to enable KSeF sync. |
264
+
| Sync jobs | New tenant |`default`|*No sync jobs yet* — KSeF sync runs hourly, or trigger one manually. |
265
+
| Payments list | New tenant |`default`|*No payment requests yet* — Approve an expense to generate your first payment. |
266
+
267
+
---
268
+
251
269
## 8. Handoff workflow (recommended)
252
270
253
-
1.**Commit this file + the prototype** into the real repo under `docs/design-system/`.
271
+
1.**Commit this file + the prototype** into the real repo under `docs/design_system/` (this file lives at the repo root as `DESIGN_SYSTEM.md`; the HTML/JSX kit lives under `docs/design_system/`).
254
272
2.**Per screen** in the real app, open a PR that links to the matching prototype file. In the PR description, paste:
255
273
- Screenshot of prototype screen
256
274
- Screenshot of current real screen
257
-
- Checklist of the 11 rules from §2
275
+
- Checklist of the 10 rules from §2
258
276
3.**Ask Claude Code one screen at a time**, e.g.:
259
-
> Port `app/views/payments/index.tsx` to match `ui_kits/admin/Payments.jsx`. Follow `docs/design-system/DESIGN_SYSTEM.md`. Reuse `Button`, `Badge`, `Card` from `app/components/ui/`. Keep existing data hooks. Output a diff, don't touch other files.
260
-
4.**Review against §2**. Almost every miss is one of those 11 rules.
277
+
> Port `app/views/payments/index.tsx` to match `docs/design_system/ui_kits/admin/Payments.jsx`. Follow `DESIGN_SYSTEM.md` at the repo root. Reuse `Button`, `Badge`, `Card` from `app/components/ui/`. Keep existing data hooks. Output a diff, don't touch other files.
278
+
4.**Review against §2**. Almost every miss is one of those 10 rules.
| Duplicate suspected | "This invoice may be a duplicate. View original." |
68
68
| Connection lost flash | "We can't find the internet" * "Attempting to reconnect" |
69
-
| Empty state | "No data for selected period" |
69
+
| Empty state (filter result, table has data elsewhere) | "No data for selected period" — *for zero-data states (`<EmptyState>`), see `DESIGN_SYSTEM.md` §7 at the repo root*|
70
70
| Pagination | "Showing 1-25 of 412 invoices" * "Page 3 of 17" |
| Prediction hint | "Predicted with 87.3% probability, feel free to adjust" * "Manually adjusted" |
@@ -129,15 +129,14 @@ Use this skill whenever you're designing **anything KSeF Hub-branded** -- admin
129
129
130
130
### Animation
131
131
132
-
-**Only** what LiveView already does: `topbar` loading indicator (`#29d` blue, 300ms show delay), `motion-safe:animate-spin` on loading icons, CSS transitions on hover/focus/press (`transition-all`, ~150ms ease-out default).
132
+
-**Only** what LiveView already does: `topbar` loading indicator (`#29d` blue, 300ms show delay), `motion-safe:animate-spin` on loading icons, CSS transitions on hover/focus (`transition-colors`, ~150ms default).
133
133
- Flash toasts: `transition-all ease-out 300ms` on show, `ease-in 200ms` on hide, combined translate-y + scale + opacity. See `show/hide` helpers.
134
134
- No bounces, no entrance choreography, no scroll-jacking in marketing.
135
135
136
136
### Hover & press
137
137
138
138
-**Hover:**`hover:bg-shad-accent hover:text-shad-accent-foreground` for ghost/outline buttons and nav items. For primary buttons: `hover:bg-shad-primary/90`. Links: `underline-offset-4 hover:underline`.
139
-
-**Press / active:** buttons use `active:scale-[0.98]`; outline/ghost variants additionally get `active:opacity-80`.
0 commit comments