@tamsensedev/runner 0.13.0 → 0.18.0

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 (82) hide show
  1. package/LICENSE +5 -5
  2. package/README.md +61 -57
  3. package/dist/cli.js +646 -315
  4. package/dist/cli.js.map +1 -1
  5. package/dist/recorder.cjs.map +1 -1
  6. package/library/.claude-plugin/plugin.json +5 -5
  7. package/library/THIRD_PARTY_NOTICES.md +123 -123
  8. package/library/agents/code-architect.md +67 -67
  9. package/library/agents/code-explorer.md +61 -61
  10. package/library/agents/code-reviewer.md +73 -73
  11. package/library/agents/code-simplifier.md +62 -62
  12. package/library/agents/design-critic.md +52 -52
  13. package/library/agents/second-opinion.md +64 -64
  14. package/library/agents/security-auditor.md +86 -86
  15. package/library/agents/silent-failure-hunter.md +70 -70
  16. package/library/agents/test-coverage-analyzer.md +78 -78
  17. package/library/agents/test-runner.md +48 -48
  18. package/library/agents/ui-tester.md +50 -50
  19. package/library/licenses/Apache-2.0.txt +202 -202
  20. package/library/skills/accessibility/SKILL.md +144 -144
  21. package/library/skills/accessibility/references/wcag-checks.md +168 -168
  22. package/library/skills/addressing-review-feedback/SKILL.md +109 -109
  23. package/library/skills/app-screens/SKILL.md +96 -96
  24. package/library/skills/app-screens/references/shell-kpis-charts.md +108 -108
  25. package/library/skills/app-screens/references/states-density-phone.md +54 -54
  26. package/library/skills/app-screens/references/tables-forms-settings.md +104 -104
  27. package/library/skills/avoiding-generic-ai-design/SKILL.md +92 -92
  28. package/library/skills/avoiding-generic-ai-design/references/tells.md +112 -112
  29. package/library/skills/clean-commits/SKILL.md +85 -85
  30. package/library/skills/design-review/SKILL.md +107 -107
  31. package/library/skills/design-review/references/critique-passes.md +101 -101
  32. package/library/skills/design-review/references/screenshot-procedure.md +121 -121
  33. package/library/skills/differential-security-review/LICENSE +427 -427
  34. package/library/skills/differential-security-review/SKILL.md +105 -105
  35. package/library/skills/differential-security-review/references/adversarial.md +115 -115
  36. package/library/skills/differential-security-review/references/methodology.md +145 -145
  37. package/library/skills/differential-security-review/references/patterns.md +108 -108
  38. package/library/skills/false-positive-check/LICENSE +427 -427
  39. package/library/skills/false-positive-check/SKILL.md +95 -95
  40. package/library/skills/false-positive-check/references/bug-class-verification.md +128 -128
  41. package/library/skills/false-positive-check/references/deep-verification.md +113 -113
  42. package/library/skills/false-positive-check/references/evidence-templates.md +93 -93
  43. package/library/skills/false-positive-check/references/false-positive-patterns.md +122 -122
  44. package/library/skills/false-positive-check/references/gate-reviews.md +36 -36
  45. package/library/skills/false-positive-check/references/standard-verification.md +80 -80
  46. package/library/skills/focused-implementation/SKILL.md +104 -104
  47. package/library/skills/implementation-plan/SKILL.md +66 -66
  48. package/library/skills/interface-design/SKILL.md +144 -144
  49. package/library/skills/interface-design/references/craft-defaults.md +130 -130
  50. package/library/skills/interface-design/references/new-direction.md +62 -62
  51. package/library/skills/interface-design/references/writing.md +52 -52
  52. package/library/skills/interface-polish/SKILL.md +103 -103
  53. package/library/skills/interface-polish/references/detail-rules.md +154 -154
  54. package/library/skills/interface-polish/references/worst-case-data.md +158 -158
  55. package/library/skills/motion-design/SKILL.md +106 -106
  56. package/library/skills/motion-design/references/recipes.md +243 -243
  57. package/library/skills/postgres-performance/SKILL.md +97 -97
  58. package/library/skills/postgres-performance/references/rules.md +265 -265
  59. package/library/skills/project-orientation/SKILL.md +77 -77
  60. package/library/skills/reviewing-changes/SKILL.md +183 -183
  61. package/library/skills/safe-database-changes/SKILL.md +111 -111
  62. package/library/skills/safe-database-changes/references/backfills.md +75 -75
  63. package/library/skills/safe-database-changes/references/migration-tools.md +69 -69
  64. package/library/skills/safe-database-changes/references/postgres-ddl.md +199 -199
  65. package/library/skills/security-review/SKILL.md +132 -132
  66. package/library/skills/security-review/references/checks-by-area.md +107 -107
  67. package/library/skills/security-review/references/do-not-report.md +86 -86
  68. package/library/skills/spec-writing/SKILL.md +144 -144
  69. package/library/skills/systematic-debugging/SKILL.md +130 -130
  70. package/library/skills/systematic-debugging/references/flaky-tests.md +51 -51
  71. package/library/skills/systematic-debugging/references/root-cause-tracing.md +55 -55
  72. package/library/skills/task-interview/SKILL.md +84 -84
  73. package/library/skills/test-driven-development/SKILL.md +116 -116
  74. package/library/skills/test-driven-development/references/writing-good-tests.md +85 -85
  75. package/library/skills/ui-testing/SKILL.md +102 -102
  76. package/library/skills/ui-testing/references/headless-check.md +295 -295
  77. package/library/skills/ui-testing/references/local-stand.md +51 -51
  78. package/library/skills/ui-testing/references/playwright-patterns.md +134 -134
  79. package/library/skills/ui-testing/references/recording.md +61 -61
  80. package/library/skills/verification-before-completion/SKILL.md +113 -113
  81. package/library/skills/web-interface-guidelines/SKILL.md +202 -202
  82. package/package.json +46 -46
@@ -1,154 +1,154 @@
1
- # Detail rules with code
2
-
3
- Read this when applying a specific polish rule and you want the exact values. Examples are in plain CSS with a Tailwind equivalent where it helps; translate them into the project's styling system and tokens.
4
-
5
- ## Concentric radius
6
-
7
- ```css
8
- .card { border-radius: 16px; padding: 8px; } /* 8 + 8 */
9
- .card-inner { border-radius: 8px; }
10
- ```
11
-
12
- Tailwind: `rounded-2xl p-2` (16px radius, 8px padding) on the outer element, `rounded-lg` (8px = 16 - 8) on the inner one.
13
-
14
- With padding over about 24px, the layers read as separate surfaces; choose each radius from the scale.
15
-
16
- ## Optical alignment
17
-
18
- ```css
19
- .button-with-trailing-icon { padding-inline: 16px 14px; } /* icon side = text side - 2px */
20
- .play-button svg { translate: 2px 0; } /* triangle's visual centre */
21
- ```
22
-
23
- For lopsided icons (stars, carets), adjust the SVG `viewBox` or path so no wrapper offset is needed.
24
-
25
- ## Shadow as border, and elevation
26
-
27
- ```css
28
- :root {
29
- --shadow-border:
30
- 0 0 0 1px rgb(0 0 0 / 0.06),
31
- 0 1px 2px -1px rgb(0 0 0 / 0.06),
32
- 0 2px 4px 0 rgb(0 0 0 / 0.04);
33
- --shadow-border-hover:
34
- 0 0 0 1px rgb(0 0 0 / 0.08),
35
- 0 1px 2px -1px rgb(0 0 0 / 0.08),
36
- 0 2px 4px 0 rgb(0 0 0 / 0.06);
37
- }
38
- .dark {
39
- --shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
40
- --shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.13);
41
- }
42
- .card { box-shadow: var(--shadow-border); transition: box-shadow 150ms ease-out; }
43
- .card:hover { box-shadow: var(--shadow-border-hover); }
44
- ```
45
-
46
- | Prefer a shadow | Keep a border |
47
- |---|---|
48
- | Cards and buttons on varied backgrounds | Dividers between list items |
49
- | Floating layers (menus, popovers, dialogs) | Table cell boundaries |
50
- | Hover lift on clickable cards | Form input outlines |
51
-
52
- Use this only if it matches the product's depth strategy; a product built on plain borders keeps borders.
53
-
54
- ## Image outlines
55
-
56
- ```css
57
- img.media { outline: 1px solid rgb(0 0 0 / 0.1); outline-offset: -1px; }
58
- .dark img.media { outline-color: rgb(255 255 255 / 0.1); }
59
- ```
60
-
61
- Tailwind: `outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10`. Pure black or white only, never a tinted grey from the palette.
62
-
63
- ## Hit areas
64
-
65
- ```css
66
- .icon-button { position: relative; width: 20px; height: 20px; }
67
- .icon-button::after {
68
- content: "";
69
- position: absolute;
70
- inset: 50% auto auto 50%;
71
- width: 44px; height: 44px;
72
- translate: -50% -50%;
73
- }
74
- ```
75
-
76
- If the extended area would overlap a neighbour, shrink it to the largest size that does not.
77
-
78
- ## Type rendering
79
-
80
- ```css
81
- html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
82
- h1, h2, h3 { text-wrap: balance; } /* short text only: browsers balance up to about 6 lines */
83
- p, li, figcaption { text-wrap: pretty; }
84
- .num { font-variant-numeric: tabular-nums; }
85
- ```
86
-
87
- `tabular-nums` belongs on counters, timers, prices that update and table columns, not on phone numbers, version strings or decorative display numbers. Some fonts change the look of "1" with it; check it renders well.
88
-
89
- Font family is not a polish decision: use the product's type system and never add a paid face for polish.
90
-
91
- ## Overflow in rows
92
-
93
- ```css
94
- .row { display: flex; align-items: center; gap: 12px; }
95
- .row-avatar { flex-shrink: 0; }
96
- .row-text { min-width: 0; flex: 1; }
97
- .row-email { overflow-wrap: anywhere; }
98
- .row-badge { flex-shrink: 0; white-space: nowrap; }
99
- .row-menu { flex-shrink: 0; }
100
- ```
101
-
102
- Grid equivalent: `grid-template-columns: auto minmax(0, 1fr) auto`. When text can wrap to several lines, top-align the row (`align-items: flex-start`) so the avatar does not drift.
103
-
104
- ## Icons
105
-
106
- | Text beside the icon | Stroke on a 24px grid |
107
- |---|---|
108
- | Regular (400), 14 to 16px | 1.5px |
109
- | Medium or semibold (500 to 600) | 2px |
110
- | Bold (700) or standalone emphasis | 2.5px |
111
-
112
- ```html
113
- <svg fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">...</svg>
114
- ```
115
-
116
- ```css
117
- .icon-button { color: var(--text-muted); }
118
- .icon-button:hover { color: var(--text); }
119
- .icon-button[aria-pressed="true"] { color: var(--accent); }
120
- ```
121
-
122
- Strip hard-coded `fill` and `stroke` colours from imported icons. Inline icons are about 1em to 1.25em. Directional icons (back, forward, chevrons, send) flip in RTL; logos, checkmarks, clocks and media controls do not. Icon-only buttons need an accessible name; decorative icons get `aria-hidden="true"`.
123
-
124
- ## Press feedback and transitions
125
-
126
- ```css
127
- .button {
128
- transition-property: transform, background-color;
129
- transition-duration: 150ms;
130
- transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
131
- }
132
- .button:active:not(:disabled) { transform: scale(0.97); }
133
-
134
- @media (hover: hover) and (pointer: fine) {
135
- .card-link:hover { background-color: var(--surface-hover); }
136
- }
137
- ```
138
-
139
- Tailwind: `transition-[transform,background-color] duration-150 active:not-disabled:scale-[0.97]`. Never `transition-all`. Use `will-change: transform` only to cure an observed first-frame stutter, never broadly.
140
-
141
- ## Focus
142
-
143
- ```css
144
- :focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
145
- .input:focus-visible { outline-offset: 0; }
146
- ```
147
-
148
- Never `outline: none` without a replacement. Sticky headers and footers must not cover the focused element: `scroll-padding-top` on the scroll container equal to the header height.
149
-
150
- ## Overlays and clipping
151
-
152
- A menu inside an `overflow: hidden` or `overflow: auto` ancestor is clipped. Use the project's popover or portal component, `<dialog>`, the `popover` attribute, or `position: fixed` with collision handling. Popovers scale from their trigger (`transform-origin` at the trigger), dialogs from the centre.
153
-
154
- Sources: adapted from jakubkrehel/make-interfaces-feel-better surfaces, typography, icons, animations and performance references (MIT, changed: values reconciled to scale(0.97), Motion-specific code removed, plain CSS first), emilkowalski/skills break-ui failure signatures (MIT) and pbakaus/impeccable operate reference (Apache-2.0, changed: rewritten).
1
+ # Detail rules with code
2
+
3
+ Read this when applying a specific polish rule and you want the exact values. Examples are in plain CSS with a Tailwind equivalent where it helps; translate them into the project's styling system and tokens.
4
+
5
+ ## Concentric radius
6
+
7
+ ```css
8
+ .card { border-radius: 16px; padding: 8px; } /* 8 + 8 */
9
+ .card-inner { border-radius: 8px; }
10
+ ```
11
+
12
+ Tailwind: `rounded-2xl p-2` (16px radius, 8px padding) on the outer element, `rounded-lg` (8px = 16 - 8) on the inner one.
13
+
14
+ With padding over about 24px, the layers read as separate surfaces; choose each radius from the scale.
15
+
16
+ ## Optical alignment
17
+
18
+ ```css
19
+ .button-with-trailing-icon { padding-inline: 16px 14px; } /* icon side = text side - 2px */
20
+ .play-button svg { translate: 2px 0; } /* triangle's visual centre */
21
+ ```
22
+
23
+ For lopsided icons (stars, carets), adjust the SVG `viewBox` or path so no wrapper offset is needed.
24
+
25
+ ## Shadow as border, and elevation
26
+
27
+ ```css
28
+ :root {
29
+ --shadow-border:
30
+ 0 0 0 1px rgb(0 0 0 / 0.06),
31
+ 0 1px 2px -1px rgb(0 0 0 / 0.06),
32
+ 0 2px 4px 0 rgb(0 0 0 / 0.04);
33
+ --shadow-border-hover:
34
+ 0 0 0 1px rgb(0 0 0 / 0.08),
35
+ 0 1px 2px -1px rgb(0 0 0 / 0.08),
36
+ 0 2px 4px 0 rgb(0 0 0 / 0.06);
37
+ }
38
+ .dark {
39
+ --shadow-border: 0 0 0 1px rgb(255 255 255 / 0.08);
40
+ --shadow-border-hover: 0 0 0 1px rgb(255 255 255 / 0.13);
41
+ }
42
+ .card { box-shadow: var(--shadow-border); transition: box-shadow 150ms ease-out; }
43
+ .card:hover { box-shadow: var(--shadow-border-hover); }
44
+ ```
45
+
46
+ | Prefer a shadow | Keep a border |
47
+ |---|---|
48
+ | Cards and buttons on varied backgrounds | Dividers between list items |
49
+ | Floating layers (menus, popovers, dialogs) | Table cell boundaries |
50
+ | Hover lift on clickable cards | Form input outlines |
51
+
52
+ Use this only if it matches the product's depth strategy; a product built on plain borders keeps borders.
53
+
54
+ ## Image outlines
55
+
56
+ ```css
57
+ img.media { outline: 1px solid rgb(0 0 0 / 0.1); outline-offset: -1px; }
58
+ .dark img.media { outline-color: rgb(255 255 255 / 0.1); }
59
+ ```
60
+
61
+ Tailwind: `outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10`. Pure black or white only, never a tinted grey from the palette.
62
+
63
+ ## Hit areas
64
+
65
+ ```css
66
+ .icon-button { position: relative; width: 20px; height: 20px; }
67
+ .icon-button::after {
68
+ content: "";
69
+ position: absolute;
70
+ inset: 50% auto auto 50%;
71
+ width: 44px; height: 44px;
72
+ translate: -50% -50%;
73
+ }
74
+ ```
75
+
76
+ If the extended area would overlap a neighbour, shrink it to the largest size that does not.
77
+
78
+ ## Type rendering
79
+
80
+ ```css
81
+ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
82
+ h1, h2, h3 { text-wrap: balance; } /* short text only: browsers balance up to about 6 lines */
83
+ p, li, figcaption { text-wrap: pretty; }
84
+ .num { font-variant-numeric: tabular-nums; }
85
+ ```
86
+
87
+ `tabular-nums` belongs on counters, timers, prices that update and table columns, not on phone numbers, version strings or decorative display numbers. Some fonts change the look of "1" with it; check it renders well.
88
+
89
+ Font family is not a polish decision: use the product's type system and never add a paid face for polish.
90
+
91
+ ## Overflow in rows
92
+
93
+ ```css
94
+ .row { display: flex; align-items: center; gap: 12px; }
95
+ .row-avatar { flex-shrink: 0; }
96
+ .row-text { min-width: 0; flex: 1; }
97
+ .row-email { overflow-wrap: anywhere; }
98
+ .row-badge { flex-shrink: 0; white-space: nowrap; }
99
+ .row-menu { flex-shrink: 0; }
100
+ ```
101
+
102
+ Grid equivalent: `grid-template-columns: auto minmax(0, 1fr) auto`. When text can wrap to several lines, top-align the row (`align-items: flex-start`) so the avatar does not drift.
103
+
104
+ ## Icons
105
+
106
+ | Text beside the icon | Stroke on a 24px grid |
107
+ |---|---|
108
+ | Regular (400), 14 to 16px | 1.5px |
109
+ | Medium or semibold (500 to 600) | 2px |
110
+ | Bold (700) or standalone emphasis | 2.5px |
111
+
112
+ ```html
113
+ <svg fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">...</svg>
114
+ ```
115
+
116
+ ```css
117
+ .icon-button { color: var(--text-muted); }
118
+ .icon-button:hover { color: var(--text); }
119
+ .icon-button[aria-pressed="true"] { color: var(--accent); }
120
+ ```
121
+
122
+ Strip hard-coded `fill` and `stroke` colours from imported icons. Inline icons are about 1em to 1.25em. Directional icons (back, forward, chevrons, send) flip in RTL; logos, checkmarks, clocks and media controls do not. Icon-only buttons need an accessible name; decorative icons get `aria-hidden="true"`.
123
+
124
+ ## Press feedback and transitions
125
+
126
+ ```css
127
+ .button {
128
+ transition-property: transform, background-color;
129
+ transition-duration: 150ms;
130
+ transition-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
131
+ }
132
+ .button:active:not(:disabled) { transform: scale(0.97); }
133
+
134
+ @media (hover: hover) and (pointer: fine) {
135
+ .card-link:hover { background-color: var(--surface-hover); }
136
+ }
137
+ ```
138
+
139
+ Tailwind: `transition-[transform,background-color] duration-150 active:not-disabled:scale-[0.97]`. Never `transition-all`. Use `will-change: transform` only to cure an observed first-frame stutter, never broadly.
140
+
141
+ ## Focus
142
+
143
+ ```css
144
+ :focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
145
+ .input:focus-visible { outline-offset: 0; }
146
+ ```
147
+
148
+ Never `outline: none` without a replacement. Sticky headers and footers must not cover the focused element: `scroll-padding-top` on the scroll container equal to the header height.
149
+
150
+ ## Overlays and clipping
151
+
152
+ A menu inside an `overflow: hidden` or `overflow: auto` ancestor is clipped. Use the project's popover or portal component, `<dialog>`, the `popover` attribute, or `position: fixed` with collision handling. Popovers scale from their trigger (`transform-origin` at the trigger), dialogs from the centre.
153
+
154
+ Sources: adapted from jakubkrehel/make-interfaces-feel-better surfaces, typography, icons, animations and performance references (MIT, changed: values reconciled to scale(0.97), Motion-specific code removed, plain CSS first), emilkowalski/skills break-ui failure signatures (MIT) and pbakaus/impeccable operate reference (Apache-2.0, changed: rewritten).
@@ -1,158 +1,158 @@
1
- # Stress-testing with worst-case data
2
-
3
- Read this when checking that a component or screen survives real data, before finishing UI work or as part of a review.
4
-
5
- The component was almost certainly built against kind data: "Jane Doe, jane@acme.com, 12 members". Production has Aleksandra Wiśniewska-Kowalczyk, `bartholomew.fitzgerald@northwind-industries-holdings.example.com`, an intern called Jo and a workspace with 1,284 members.
6
-
7
- ## Rules
8
-
9
- 1. **Plausible or schema-backed, never random.** Each value is something a real user could produce, or the actual limit from the validation schema, database column or API contract. Five thousand "a" characters prove nothing. If no limit exists, that is a finding ("unbounded"); test something long but believable.
10
- 2. **Change the data, not the component.** The worst case enters through the same boundary as the demo data: the fixture, mock, story args, seed, or an intercepted API response. Never edit markup or CSS to provoke a break.
11
- 3. **One dataset, many failures.** Spread different worst cases across the first rows (row 1 the long name, row 2 the long email, row 3 the one-letter name), the way real data mixes them.
12
- 4. **Do not stop at long text.** The breaks that ship are the short name, the missing avatar, "1 members", the empty list, the translated badge.
13
- 5. **Leave no test toggles in product code.** In a code step, a worst-case fixture may be committed only where the project keeps fixtures (tests, stories, mocks), following its conventions. In a review step, use only interception or existing fixtures and change nothing.
14
-
15
- ## Phase 1: map what the component renders
16
-
17
- List every value it shows, with its source, type, limit and whether it is optional:
18
-
19
- | Field | Source | Type | Limit | Optional |
20
- |---|---|---|---|---|
21
- | name | member.name | string | 255 (schema.ts:14) | no |
22
- | email | member.email | string | none found | no |
23
- | role | member.title | string | 120 | yes |
24
- | status | enum | active / invited / expired | n/a | no |
25
- | count | workspace.memberCount | int | n/a | no |
26
-
27
- Include the values people forget: counts in headers, relative times, badge and status text, labels that come from data, tooltips, avatar images, and the list length itself. Find limits in validation schemas, migrations, API types and `maxlength` attributes; note where front end and back end disagree.
28
-
29
- ## Phase 2: build the worst case
30
-
31
- Pick values from the catalog below. Also cover cases that are not one dataset:
32
-
33
- - **Empty**: zero items, and no results after filtering.
34
- - **One**: a single item, and every count at exactly 1.
35
- - **Huge**: the realistic upper bound (1,000 or more rows if unpaginated).
36
-
37
- ## Phase 3: look
38
-
39
- Check the worst case:
40
-
41
- - at the component's real container width (a sidebar list is not a full-page list), then at 320px and the widest supported layout;
42
- - with browser zoom at 200%, because text grows and boxes do not;
43
- - in dark mode and with `dir="rtl"` if the product supports them.
44
-
45
- Screenshot both demo and worst case at each width (procedure in `design-review`). Say which findings you saw and which you inferred from code.
46
-
47
- ## Catalog
48
-
49
- Use `example.com`, `example.org` or `.test` domains so fixtures never point at real inboxes.
50
-
51
- **People and names**
52
-
53
- | Value | Breaks |
54
- |---|---|
55
- | `Aleksandra Wiśniewska-Kowalczyk` | Long, hyphenated, diacritics; wraps at the hyphen |
56
- | `Christopher Alexander Montgomery III` | Suffix; naive initials give "CI" |
57
- | `Jo`, `J` | Near-empty name column; one-letter initials; tiny click target if the name is the link |
58
- | `Đặng Thị Ngọc Hân` | Stacked diacritics clipped by tight line height with `overflow: hidden` |
59
- | `王秀英` | No spaces; "first plus last word" initials find one word |
60
- | `نور الهدى عبد الرحمن` | RTL; punctuation and icons on the wrong side without `dir="auto"` |
61
- | `Seán O'Brien-Ó Súilleabháin` | Apostrophe and accents: escaping, initials, search |
62
- | A name starting with an emoji ("Fox" after a fox emoji), or with a skin-tone and joiner sequence before "Priya" | `charAt(0)` returns half a character; slicing splits the sequence |
63
- | ` Sam Lee ` | Leading, trailing and repeated spaces |
64
- | missing name | Only an email; the UI needs a fallback |
65
-
66
- **Emails, URLs, identifiers**
67
-
68
- | Value | Breaks |
69
- |---|---|
70
- | `bartholomew.fitzgerald@northwind-industries-holdings.example.com` | No wrap points; pushes siblings off the row |
71
- | `a@b.co` | Layout that assumed a long email looks empty |
72
- | `first.last+billing-notifications@example.com` | Plus addressing rejected or truncated |
73
- | A long URL with query string | End truncation hides the part that differs |
74
- | `9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f` | UUID width; candidate for middle truncation |
75
- | `Q3 Board Deck FINAL (revised) v12 [approved].pdf` | End truncation hides version and extension |
76
-
77
- **Labels and text from data**
78
-
79
- | Value | Breaks |
80
- |---|---|
81
- | `Senior Product Design Engineer, Platform Infrastructure` | Wraps to three lines in a secondary slot |
82
- | `Invitation expired 12 days ago` as a badge | Badge wraps or squeezes the name |
83
- | `Benachrichtigungseinstellungen` | German compound, no break points |
84
- | French or German UI strings | About 30% longer than English |
85
- | Twelve tags on one item | Tag rows become a wall; needs "+8" |
86
- | Empty string or whitespace title | Collapsed heading, zero-height row |
87
- | `<script>alert(1)</script>`, `&amp;`, `**bold**` | Must render as literal text |
88
- | A 2,000-character description | Clamping, "show more", textarea growth |
89
-
90
- **Numbers and money**
91
-
92
- | Value | Breaks |
93
- |---|---|
94
- | 0 | "0 members", empty bars, division by zero in percentages |
95
- | 1 | "1 members", "1 days ago" |
96
- | 1284, 1000000 | Missing separators; badge width; consider compact form where precision does not matter |
97
- | 12345678.9 as currency | Overflowing totals columns |
98
- | -42.5 | Sign, colour logic, accounting parentheses |
99
- | 0.1 + 0.2 | `0.30000000000000004` rendered raw |
100
- | 142%, -3% | Progress bars past their bounds |
101
- | null, undefined, NaN | Rendered literally |
102
- | A live value 99 then 100 | Width jump without tabular figures |
103
-
104
- **Collections**: 0 items; 1 item (grids that look broken with one card); exactly the page size and one more; 1,000+ unpaginated; one item ten times larger than the others; items with identical names.
105
-
106
- **Time**: now ("0 seconds ago"); 12 days, 11 months, 3 years ago (switch to absolute dates); a future date; `1970-01-01`; a time near midnight in another time zone; a duration of 1,284 hours.
107
-
108
- **Images**: avatar URL that fails (fallback to initials); no avatar; a 4000 x 200 panorama and a 200 x 4000 strip (need `object-fit: cover`); a dark transparent logo on a dark theme; a slow image (needs fixed dimensions).
109
-
110
- **States**: loading; API error (no raw `TypeError` text on screen); partial data; every status value at once; no permission; the current user in the list.
111
-
112
- ## Failure signatures
113
-
114
- | What you see | Cause | Fix |
115
- |---|---|---|
116
- | Avatar or icon squashed into an oval | Flex child shrinking | `flex-shrink: 0` on fixed-size items |
117
- | Text overflows instead of wrapping or truncating | `min-width: auto` on a flex or grid child | `min-width: 0`, or `minmax(0, 1fr)` |
118
- | Email or URL runs past the edge | No break opportunities | `overflow-wrap: anywhere` on that element |
119
- | Trailing menu pushed off-screen | Middle content took the space | `min-width: 0` on the middle, `flex-shrink: 0` on the action |
120
- | Badge wraps onto two lines | Badge allowed to shrink | `white-space: nowrap; flex-shrink: 0`, decide what yields |
121
- | Avatar adrift beside a three-line name | `align-items: center` on rows of varying height | Top-align once text can wrap |
122
- | Last row cut mid-glyph | Fixed height without scroll affordance | Visible scroll or a fade, intended `overflow` |
123
- | Heading breaks mid-word | `word-break: break-all` | `overflow-wrap: anywhere` |
124
- | Wrong initials | `split(' ')[0][0]` | Grapheme-aware initials (`Intl.Segmenter`), first and last word, icon fallback |
125
- | Orphaned dash where an optional field was | Placeholder for a missing value | Omit the line, or reserve its height on purpose |
126
- | "1 members" | Hard-coded plural | `Intl.PluralRules` or the project's i18n |
127
- | Numbers jitter as they update | Proportional figures | `tabular-nums` |
128
- | `1284`, `NaN`, `undefined` | Raw number rendered | `Intl.NumberFormat`, guard null |
129
- | Translated button label overflows | Fixed width | Width from content with a `min-width` |
130
- | Tall scripts clipped | Tight line height with hidden overflow | Looser line height, no clipping on text |
131
- | Broken-image icon | No error fallback | Fall back to initials or a neutral icon |
132
- | Truncated text with no way to read it | Ellipsis only | Title or tooltip, and the full value in a detail view |
133
- | 1,000 rows stutter | Every row rendered | Virtualize or paginate |
134
- | Raw `<b>`, `&amp;` or `**text**` shown | Wrong escaping layer | Escape once at render; never inject user HTML |
135
-
136
- ## Truncate, wrap or clamp
137
-
138
- Decide per field:
139
-
140
- - **Wrap** what identifies something (names, titles in a detail view). Two lines is fine; four means the column is too narrow.
141
- - **Truncate at the end** for secondary metadata where the start carries the meaning (role, preview text), always with a way to see the full value.
142
- - **Truncate in the middle** where items differ at the end (file names, paths, hashes, emails sharing a long domain).
143
- - **Clamp** (`line-clamp: 2`) multi-line previews in cards so heights stay predictable.
144
- - **Never truncate** numbers, amounts, dates, or anything people compare.
145
-
146
- ## Output
147
-
148
- | # | Severity | Field | Worst-case value | What happens | Fix (`file:line`) |
149
- |---|---|---|---|---|---|
150
- | 1 | Broken | email | `bartholomew.fitzgerald@...example.com` | Menu unreachable at 400px | `min-width: 0` on text column, `overflow-wrap: anywhere` on email |
151
- | 2 | Ugly | count | 1 | "1 members" | Plural rules |
152
- | 3 | Fragile | name | n/a | No max length in schema or form | Matching limits in both |
153
-
154
- Severity: **Broken** (unreadable, unreachable, wrong data), **Ugly** (readable but visibly wrong), **Fragile** (fine now, one realistic step from breaking). Also list what held up, so the test is visibly real.
155
-
156
- In a code step, fix the breaks; where there are several right answers (truncate or wrap, what an empty role shows), choose by the rules above, apply it, and list the choice in the report. In a review step, report only.
157
-
158
- Sources: adapted from emilkowalski/skills break-ui SKILL.md and CATALOG.md (MIT, changed: no interactive toggle shipped, no "report and wait for the user" step; code steps fix, review steps report) and pbakaus/impeccable harden reference (Apache-2.0, changed: rewritten).
1
+ # Stress-testing with worst-case data
2
+
3
+ Read this when checking that a component or screen survives real data, before finishing UI work or as part of a review.
4
+
5
+ The component was almost certainly built against kind data: "Jane Doe, jane@acme.com, 12 members". Production has Aleksandra Wiśniewska-Kowalczyk, `bartholomew.fitzgerald@northwind-industries-holdings.example.com`, an intern called Jo and a workspace with 1,284 members.
6
+
7
+ ## Rules
8
+
9
+ 1. **Plausible or schema-backed, never random.** Each value is something a real user could produce, or the actual limit from the validation schema, database column or API contract. Five thousand "a" characters prove nothing. If no limit exists, that is a finding ("unbounded"); test something long but believable.
10
+ 2. **Change the data, not the component.** The worst case enters through the same boundary as the demo data: the fixture, mock, story args, seed, or an intercepted API response. Never edit markup or CSS to provoke a break.
11
+ 3. **One dataset, many failures.** Spread different worst cases across the first rows (row 1 the long name, row 2 the long email, row 3 the one-letter name), the way real data mixes them.
12
+ 4. **Do not stop at long text.** The breaks that ship are the short name, the missing avatar, "1 members", the empty list, the translated badge.
13
+ 5. **Leave no test toggles in product code.** In a code step, a worst-case fixture may be committed only where the project keeps fixtures (tests, stories, mocks), following its conventions. In a review step, use only interception or existing fixtures and change nothing.
14
+
15
+ ## Phase 1: map what the component renders
16
+
17
+ List every value it shows, with its source, type, limit and whether it is optional:
18
+
19
+ | Field | Source | Type | Limit | Optional |
20
+ |---|---|---|---|---|
21
+ | name | member.name | string | 255 (schema.ts:14) | no |
22
+ | email | member.email | string | none found | no |
23
+ | role | member.title | string | 120 | yes |
24
+ | status | enum | active / invited / expired | n/a | no |
25
+ | count | workspace.memberCount | int | n/a | no |
26
+
27
+ Include the values people forget: counts in headers, relative times, badge and status text, labels that come from data, tooltips, avatar images, and the list length itself. Find limits in validation schemas, migrations, API types and `maxlength` attributes; note where front end and back end disagree.
28
+
29
+ ## Phase 2: build the worst case
30
+
31
+ Pick values from the catalog below. Also cover cases that are not one dataset:
32
+
33
+ - **Empty**: zero items, and no results after filtering.
34
+ - **One**: a single item, and every count at exactly 1.
35
+ - **Huge**: the realistic upper bound (1,000 or more rows if unpaginated).
36
+
37
+ ## Phase 3: look
38
+
39
+ Check the worst case:
40
+
41
+ - at the component's real container width (a sidebar list is not a full-page list), then at 320px and the widest supported layout;
42
+ - with browser zoom at 200%, because text grows and boxes do not;
43
+ - in dark mode and with `dir="rtl"` if the product supports them.
44
+
45
+ Screenshot both demo and worst case at each width (procedure in `design-review`). Say which findings you saw and which you inferred from code.
46
+
47
+ ## Catalog
48
+
49
+ Use `example.com`, `example.org` or `.test` domains so fixtures never point at real inboxes.
50
+
51
+ **People and names**
52
+
53
+ | Value | Breaks |
54
+ |---|---|
55
+ | `Aleksandra Wiśniewska-Kowalczyk` | Long, hyphenated, diacritics; wraps at the hyphen |
56
+ | `Christopher Alexander Montgomery III` | Suffix; naive initials give "CI" |
57
+ | `Jo`, `J` | Near-empty name column; one-letter initials; tiny click target if the name is the link |
58
+ | `Đặng Thị Ngọc Hân` | Stacked diacritics clipped by tight line height with `overflow: hidden` |
59
+ | `王秀英` | No spaces; "first plus last word" initials find one word |
60
+ | `نور الهدى عبد الرحمن` | RTL; punctuation and icons on the wrong side without `dir="auto"` |
61
+ | `Seán O'Brien-Ó Súilleabháin` | Apostrophe and accents: escaping, initials, search |
62
+ | A name starting with an emoji ("Fox" after a fox emoji), or with a skin-tone and joiner sequence before "Priya" | `charAt(0)` returns half a character; slicing splits the sequence |
63
+ | ` Sam Lee ` | Leading, trailing and repeated spaces |
64
+ | missing name | Only an email; the UI needs a fallback |
65
+
66
+ **Emails, URLs, identifiers**
67
+
68
+ | Value | Breaks |
69
+ |---|---|
70
+ | `bartholomew.fitzgerald@northwind-industries-holdings.example.com` | No wrap points; pushes siblings off the row |
71
+ | `a@b.co` | Layout that assumed a long email looks empty |
72
+ | `first.last+billing-notifications@example.com` | Plus addressing rejected or truncated |
73
+ | A long URL with query string | End truncation hides the part that differs |
74
+ | `9f8e7d6c-5b4a-4c3d-8e2f-1a0b9c8d7e6f` | UUID width; candidate for middle truncation |
75
+ | `Q3 Board Deck FINAL (revised) v12 [approved].pdf` | End truncation hides version and extension |
76
+
77
+ **Labels and text from data**
78
+
79
+ | Value | Breaks |
80
+ |---|---|
81
+ | `Senior Product Design Engineer, Platform Infrastructure` | Wraps to three lines in a secondary slot |
82
+ | `Invitation expired 12 days ago` as a badge | Badge wraps or squeezes the name |
83
+ | `Benachrichtigungseinstellungen` | German compound, no break points |
84
+ | French or German UI strings | About 30% longer than English |
85
+ | Twelve tags on one item | Tag rows become a wall; needs "+8" |
86
+ | Empty string or whitespace title | Collapsed heading, zero-height row |
87
+ | `<script>alert(1)</script>`, `&amp;`, `**bold**` | Must render as literal text |
88
+ | A 2,000-character description | Clamping, "show more", textarea growth |
89
+
90
+ **Numbers and money**
91
+
92
+ | Value | Breaks |
93
+ |---|---|
94
+ | 0 | "0 members", empty bars, division by zero in percentages |
95
+ | 1 | "1 members", "1 days ago" |
96
+ | 1284, 1000000 | Missing separators; badge width; consider compact form where precision does not matter |
97
+ | 12345678.9 as currency | Overflowing totals columns |
98
+ | -42.5 | Sign, colour logic, accounting parentheses |
99
+ | 0.1 + 0.2 | `0.30000000000000004` rendered raw |
100
+ | 142%, -3% | Progress bars past their bounds |
101
+ | null, undefined, NaN | Rendered literally |
102
+ | A live value 99 then 100 | Width jump without tabular figures |
103
+
104
+ **Collections**: 0 items; 1 item (grids that look broken with one card); exactly the page size and one more; 1,000+ unpaginated; one item ten times larger than the others; items with identical names.
105
+
106
+ **Time**: now ("0 seconds ago"); 12 days, 11 months, 3 years ago (switch to absolute dates); a future date; `1970-01-01`; a time near midnight in another time zone; a duration of 1,284 hours.
107
+
108
+ **Images**: avatar URL that fails (fallback to initials); no avatar; a 4000 x 200 panorama and a 200 x 4000 strip (need `object-fit: cover`); a dark transparent logo on a dark theme; a slow image (needs fixed dimensions).
109
+
110
+ **States**: loading; API error (no raw `TypeError` text on screen); partial data; every status value at once; no permission; the current user in the list.
111
+
112
+ ## Failure signatures
113
+
114
+ | What you see | Cause | Fix |
115
+ |---|---|---|
116
+ | Avatar or icon squashed into an oval | Flex child shrinking | `flex-shrink: 0` on fixed-size items |
117
+ | Text overflows instead of wrapping or truncating | `min-width: auto` on a flex or grid child | `min-width: 0`, or `minmax(0, 1fr)` |
118
+ | Email or URL runs past the edge | No break opportunities | `overflow-wrap: anywhere` on that element |
119
+ | Trailing menu pushed off-screen | Middle content took the space | `min-width: 0` on the middle, `flex-shrink: 0` on the action |
120
+ | Badge wraps onto two lines | Badge allowed to shrink | `white-space: nowrap; flex-shrink: 0`, decide what yields |
121
+ | Avatar adrift beside a three-line name | `align-items: center` on rows of varying height | Top-align once text can wrap |
122
+ | Last row cut mid-glyph | Fixed height without scroll affordance | Visible scroll or a fade, intended `overflow` |
123
+ | Heading breaks mid-word | `word-break: break-all` | `overflow-wrap: anywhere` |
124
+ | Wrong initials | `split(' ')[0][0]` | Grapheme-aware initials (`Intl.Segmenter`), first and last word, icon fallback |
125
+ | Orphaned dash where an optional field was | Placeholder for a missing value | Omit the line, or reserve its height on purpose |
126
+ | "1 members" | Hard-coded plural | `Intl.PluralRules` or the project's i18n |
127
+ | Numbers jitter as they update | Proportional figures | `tabular-nums` |
128
+ | `1284`, `NaN`, `undefined` | Raw number rendered | `Intl.NumberFormat`, guard null |
129
+ | Translated button label overflows | Fixed width | Width from content with a `min-width` |
130
+ | Tall scripts clipped | Tight line height with hidden overflow | Looser line height, no clipping on text |
131
+ | Broken-image icon | No error fallback | Fall back to initials or a neutral icon |
132
+ | Truncated text with no way to read it | Ellipsis only | Title or tooltip, and the full value in a detail view |
133
+ | 1,000 rows stutter | Every row rendered | Virtualize or paginate |
134
+ | Raw `<b>`, `&amp;` or `**text**` shown | Wrong escaping layer | Escape once at render; never inject user HTML |
135
+
136
+ ## Truncate, wrap or clamp
137
+
138
+ Decide per field:
139
+
140
+ - **Wrap** what identifies something (names, titles in a detail view). Two lines is fine; four means the column is too narrow.
141
+ - **Truncate at the end** for secondary metadata where the start carries the meaning (role, preview text), always with a way to see the full value.
142
+ - **Truncate in the middle** where items differ at the end (file names, paths, hashes, emails sharing a long domain).
143
+ - **Clamp** (`line-clamp: 2`) multi-line previews in cards so heights stay predictable.
144
+ - **Never truncate** numbers, amounts, dates, or anything people compare.
145
+
146
+ ## Output
147
+
148
+ | # | Severity | Field | Worst-case value | What happens | Fix (`file:line`) |
149
+ |---|---|---|---|---|---|
150
+ | 1 | Broken | email | `bartholomew.fitzgerald@...example.com` | Menu unreachable at 400px | `min-width: 0` on text column, `overflow-wrap: anywhere` on email |
151
+ | 2 | Ugly | count | 1 | "1 members" | Plural rules |
152
+ | 3 | Fragile | name | n/a | No max length in schema or form | Matching limits in both |
153
+
154
+ Severity: **Broken** (unreadable, unreachable, wrong data), **Ugly** (readable but visibly wrong), **Fragile** (fine now, one realistic step from breaking). Also list what held up, so the test is visibly real.
155
+
156
+ In a code step, fix the breaks; where there are several right answers (truncate or wrap, what an empty role shows), choose by the rules above, apply it, and list the choice in the report. In a review step, report only.
157
+
158
+ Sources: adapted from emilkowalski/skills break-ui SKILL.md and CATALOG.md (MIT, changed: no interactive toggle shipped, no "report and wait for the user" step; code steps fix, review steps report) and pbakaus/impeccable harden reference (Apache-2.0, changed: rewritten).