@ekanos/cli 0.1.5 → 0.1.7
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.
- package/README.md +81 -2
- package/dist/bin.js +14 -1
- package/dist/bin.js.map +1 -1
- package/dist/commands/dev.d.ts +4 -0
- package/dist/commands/dev.js +6 -20
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts +61 -0
- package/dist/commands/init.js +145 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/publish.d.ts +13 -0
- package/dist/commands/publish.js +38 -15
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/status.js +9 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/upgrade.d.ts +47 -0
- package/dist/commands/upgrade.js +546 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/validate.d.ts +5 -0
- package/dist/commands/validate.js +20 -6
- package/dist/commands/validate.js.map +1 -1
- package/dist/context.d.ts +9 -0
- package/dist/context.js +9 -0
- package/dist/context.js.map +1 -1
- package/dist/copy-tree.d.ts +48 -0
- package/dist/copy-tree.js +55 -0
- package/dist/copy-tree.js.map +1 -0
- package/dist/delegate.d.ts +28 -0
- package/dist/delegate.js +136 -0
- package/dist/delegate.js.map +1 -0
- package/dist/harness-scaffold.d.ts +21 -6
- package/dist/harness-scaffold.js +8 -5
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.js +40 -3
- package/dist/index.js.map +1 -1
- package/dist/package-manager.d.ts +10 -0
- package/dist/package-manager.js +32 -0
- package/dist/package-manager.js.map +1 -1
- package/dist/parse-argv.d.ts +7 -1
- package/dist/parse-argv.js +10 -2
- package/dist/parse-argv.js.map +1 -1
- package/dist/project-checks.d.ts +9 -0
- package/dist/project-checks.js +29 -0
- package/dist/project-checks.js.map +1 -1
- package/dist/project.d.ts +23 -0
- package/dist/project.js +66 -1
- package/dist/project.js.map +1 -1
- package/dist/publish-api.d.ts +1 -1
- package/dist/publish-api.js +2 -2
- package/dist/publish-api.js.map +1 -1
- package/dist/safe-fs.d.ts +1 -1
- package/dist/safe-fs.js.map +1 -1
- package/dist/schema-skew.d.ts +77 -0
- package/dist/schema-skew.js +163 -0
- package/dist/schema-skew.js.map +1 -0
- package/dist/sources-api.d.ts +1 -1
- package/dist/sources-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +54 -0
- package/dist/toolchain-api.js +58 -0
- package/dist/toolchain-api.js.map +1 -0
- package/dist/toolchain-resolve.d.ts +26 -0
- package/dist/toolchain-resolve.js +100 -0
- package/dist/toolchain-resolve.js.map +1 -0
- package/dist/toolchain.d.ts +21 -0
- package/dist/toolchain.js +16 -0
- package/dist/toolchain.js.map +1 -0
- package/dist/update-notice.d.ts +32 -0
- package/dist/update-notice.js +180 -0
- package/dist/update-notice.js.map +1 -0
- package/dist/validate-findings.d.ts +12 -0
- package/dist/validate-findings.js +14 -1
- package/dist/validate-findings.js.map +1 -1
- package/package.json +1 -1
- package/templates/AGENTS.md.tmpl +81 -0
- package/templates/CLAUDE.md.tmpl +2 -1
- package/templates/claude-skill.md.tmpl +20 -2
- package/templates/docs/api-notes.md.tmpl +21 -0
- package/templates/mcp.json.tmpl +8 -0
- package/templates/skills/build-integration/SKILL.md +178 -0
- package/templates/skills/build-integration/references/api-docs-research.md +93 -0
- package/templates/skills/build-integration/references/api-wiring.md +94 -0
- package/templates/skills/build-integration/references/widget-design.md +132 -0
- package/templates/skills/polish-widgets/SKILL.md +139 -0
- package/templates/skills/polish-widgets/references/patterns.md +269 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Widget fix patterns — before → after
|
|
2
|
+
|
|
3
|
+
Each pattern names the failure, then shows the smallest honest fix. All
|
|
4
|
+
imports are `@ekanos/ui/*`; icons go through `@ekanos/ui/icon` (Font Awesome
|
|
5
|
+
names — verify against the Free set). Everything sits inside the standard
|
|
6
|
+
`Widget.Active` body from the SDK's compound API; these are the bodies, not
|
|
7
|
+
the wrapper.
|
|
8
|
+
|
|
9
|
+
## 1. Flat list row → row with a first read
|
|
10
|
+
|
|
11
|
+
**Failure:** every field the same size, so nothing answers "what needs my
|
|
12
|
+
attention?". Values styled like labels, metadata styled like values.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// BEFORE — three spans, one weight
|
|
16
|
+
{
|
|
17
|
+
invoices.map((inv) => (
|
|
18
|
+
<div key={inv.id} className="flex justify-between p-2 text-sm">
|
|
19
|
+
<span>{inv.customer}</span>
|
|
20
|
+
<span className="text-xs">{inv.dueDate}</span>
|
|
21
|
+
<span>{inv.amount}</span>
|
|
22
|
+
</div>
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// AFTER — name leads, value anchors right, metadata recedes
|
|
29
|
+
<div className="divide-border divide-y">
|
|
30
|
+
{invoices.map((inv) => (
|
|
31
|
+
<div key={inv.id} className="flex items-center gap-3 px-4 py-3">
|
|
32
|
+
<div className="min-w-0 flex-1">
|
|
33
|
+
<p className="truncate text-base font-medium">{inv.customer}</p>
|
|
34
|
+
<p className="text-muted-foreground text-sm">
|
|
35
|
+
Due {formatDate(inv.dueDate)}
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
<span className="shrink-0 text-right text-base font-semibold tabular-nums">
|
|
39
|
+
{formatCurrency(inv.amount)}
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Rules at work: `min-w-0 flex-1` + `truncate` so long names never push the
|
|
47
|
+
value out; `shrink-0` + `tabular-nums` so amounts hold a column; rows own
|
|
48
|
+
their padding (`px-4 py-3`), the container owns none.
|
|
49
|
+
|
|
50
|
+
## 2. Bare stat grid → hero KPI with supporting stats
|
|
51
|
+
|
|
52
|
+
**Failure:** four equal chips. The widget's one question ("how are we
|
|
53
|
+
doing?") has no answer — every number claims the same importance.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// BEFORE — nothing is the headline
|
|
57
|
+
<div className="grid grid-cols-2 gap-2 p-4">
|
|
58
|
+
<div className="text-sm">Revenue: {revenue}</div>
|
|
59
|
+
<div className="text-sm">Orders: {orders}</div>
|
|
60
|
+
<div className="text-sm">Avg: {avg}</div>
|
|
61
|
+
<div className="text-sm">Refunds: {refunds}</div>
|
|
62
|
+
</div>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// AFTER — one hero, a delta, the rest demoted to a footer strip
|
|
67
|
+
<div className="flex flex-1 flex-col items-center justify-center px-4 py-6">
|
|
68
|
+
<span className="text-muted-foreground text-sm">Revenue this month</span>
|
|
69
|
+
<span className="text-3xl font-semibold tracking-tight tabular-nums">
|
|
70
|
+
{formatCurrency(revenue)}
|
|
71
|
+
</span>
|
|
72
|
+
<span
|
|
73
|
+
className={cn(
|
|
74
|
+
'mt-1 text-sm font-medium',
|
|
75
|
+
delta >= 0 ? 'text-success-strong' : 'text-destructive',
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{delta >= 0 ? '+' : ''}
|
|
79
|
+
{delta.toFixed(1)}% vs last month
|
|
80
|
+
</span>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className="flex shrink-0 items-center border-t">
|
|
84
|
+
{[
|
|
85
|
+
{ label: 'Orders', value: orders },
|
|
86
|
+
{ label: 'Avg order', value: formatCurrency(avg) },
|
|
87
|
+
{ label: 'Refunds', value: refunds },
|
|
88
|
+
].map((stat, i) => (
|
|
89
|
+
<Fragment key={stat.label}>
|
|
90
|
+
{i > 0 && <div className="bg-border h-8 w-px" />}
|
|
91
|
+
<div className="flex flex-1 flex-col items-center py-3">
|
|
92
|
+
<span className="text-lg font-semibold tabular-nums">{stat.value}</span>
|
|
93
|
+
<span className="text-muted-foreground text-xs">{stat.label}</span>
|
|
94
|
+
</div>
|
|
95
|
+
</Fragment>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
(`cn` from `@ekanos/ui/utils`.) Pick the hero by the interview's SEE-outcome,
|
|
101
|
+
not by which number is largest. One `text-3xl` element per widget — two heroes
|
|
102
|
+
is zero heroes.
|
|
103
|
+
|
|
104
|
+
## 3. Unstyled status → semantic badge or dot
|
|
105
|
+
|
|
106
|
+
**Failure:** status as bare text, or hardcoded palette (`bg-green-500`) that
|
|
107
|
+
breaks in dark mode and off-brand tenants.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// BEFORE
|
|
111
|
+
<span className="text-xs">{sub.status}</span>
|
|
112
|
+
// or
|
|
113
|
+
<span className="rounded bg-green-500 px-1 text-white">{sub.status}</span>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// AFTER — Badge variants carry the semantics; the map is the single source
|
|
118
|
+
import { Badge } from '@ekanos/ui/badge';
|
|
119
|
+
|
|
120
|
+
const STATUS_VARIANT = {
|
|
121
|
+
active: 'success',
|
|
122
|
+
trialing: 'info',
|
|
123
|
+
past_due: 'warning',
|
|
124
|
+
canceled: 'destructive',
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
127
|
+
<Badge variant={STATUS_VARIANT[sub.status] ?? 'outline'}>
|
|
128
|
+
{STATUS_LABEL[sub.status] ?? sub.status}
|
|
129
|
+
</Badge>;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When a badge is too loud (dense lists), use a dot plus muted text instead:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<span
|
|
136
|
+
className={cn(
|
|
137
|
+
'h-2.5 w-2.5 shrink-0 rounded-full',
|
|
138
|
+
sub.status === 'active' ? 'bg-success' : 'bg-muted-foreground/40',
|
|
139
|
+
)}
|
|
140
|
+
/>
|
|
141
|
+
<span className="text-muted-foreground text-sm">{STATUS_LABEL[sub.status]}</span>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
One status treatment per row. A row with a colored badge AND a colored icon
|
|
145
|
+
AND colored money is signalling nothing.
|
|
146
|
+
|
|
147
|
+
## 4. Cramped card → sizing floors applied
|
|
148
|
+
|
|
149
|
+
**Failure:** `p-2`, `space-y-1`, `text-xs` everywhere, `h-3 w-3` icons. Dense
|
|
150
|
+
is not information-rich; it is unreadable.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// BEFORE
|
|
154
|
+
<div className="space-y-1 p-2">
|
|
155
|
+
{events.map((e) => (
|
|
156
|
+
<div key={e.id} className="flex gap-1 text-xs">
|
|
157
|
+
<Icon name="fa-light fa-bolt" className="h-3 w-3" />
|
|
158
|
+
<span>{e.title}</span>
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
// AFTER — floors: rows px-4 py-3, icons h-5 w-5, body text-sm+
|
|
166
|
+
<div className="divide-border divide-y">
|
|
167
|
+
{events.slice(0, 6).map((e) => (
|
|
168
|
+
<div key={e.id} className="flex items-start gap-3 px-4 py-3">
|
|
169
|
+
<span className="bg-muted text-muted-foreground shrink-0 rounded-full p-2">
|
|
170
|
+
<Icon name={eventIcon(e.type)} className="h-5 w-5" />
|
|
171
|
+
</span>
|
|
172
|
+
<div className="min-w-0 flex-1">
|
|
173
|
+
<p className="truncate text-base font-medium">{e.title}</p>
|
|
174
|
+
<p className="text-muted-foreground text-sm">{e.summary}</p>
|
|
175
|
+
</div>
|
|
176
|
+
<span className="text-muted-foreground shrink-0 text-xs">
|
|
177
|
+
{formatTimeAgo(e.at)}
|
|
178
|
+
</span>
|
|
179
|
+
</div>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
If the content no longer fits after applying floors, show fewer items — the
|
|
185
|
+
floors are not negotiable, the item count is.
|
|
186
|
+
|
|
187
|
+
## 5. Overflowing card → inner scroll + "View all"
|
|
188
|
+
|
|
189
|
+
**Failure:** the list grows the card past its grid slot, or a fixed
|
|
190
|
+
`max-h-[180px]` caps it arbitrarily and clips the last row.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// BEFORE — the card scrolls (or grows); a magic pixel height fights the grid
|
|
194
|
+
<div className="max-h-[180px] overflow-y-auto">{rows}</div>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// AFTER — the LIST scrolls inside the flex column; the card holds its size
|
|
199
|
+
import { Button } from '@ekanos/ui/button';
|
|
200
|
+
|
|
201
|
+
<div className="flex h-full min-h-0 flex-col">
|
|
202
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
203
|
+
<div className="divide-border divide-y">{rows}</div>
|
|
204
|
+
</div>
|
|
205
|
+
{items.length > 7 && (
|
|
206
|
+
<div className="shrink-0 border-t px-4 py-2">
|
|
207
|
+
<Button variant="ghost" size="sm" className="text-muted-foreground">
|
|
208
|
+
View all {items.length}
|
|
209
|
+
</Button>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>;
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`min-h-0` is the load-bearing class — without it the flex child refuses to
|
|
216
|
+
shrink and nothing scrolls. Test at the sm viewport preset, where the failure
|
|
217
|
+
actually shows.
|
|
218
|
+
|
|
219
|
+
## 6. Button pile-up → one action + menu
|
|
220
|
+
|
|
221
|
+
**Failure:** two or three buttons per row; the row becomes a toolbar.
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// AFTER — one visible action, the rest behind a menu
|
|
225
|
+
import {
|
|
226
|
+
DropdownMenu,
|
|
227
|
+
DropdownMenuContent,
|
|
228
|
+
DropdownMenuItem,
|
|
229
|
+
DropdownMenuTrigger,
|
|
230
|
+
} from '@ekanos/ui/dropdown-menu';
|
|
231
|
+
|
|
232
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
233
|
+
<Button size="sm">Pay now</Button>
|
|
234
|
+
<DropdownMenu>
|
|
235
|
+
<DropdownMenuTrigger
|
|
236
|
+
render={<Button variant="ghost" size="icon" className="h-8 w-8" />}
|
|
237
|
+
>
|
|
238
|
+
<Icon name="fa-light fa-ellipsis-vertical" className="h-4 w-4" />
|
|
239
|
+
</DropdownMenuTrigger>
|
|
240
|
+
<DropdownMenuContent align="end">
|
|
241
|
+
<DropdownMenuItem>View details</DropdownMenuItem>
|
|
242
|
+
<DropdownMenuItem>Payment history</DropdownMenuItem>
|
|
243
|
+
</DropdownMenuContent>
|
|
244
|
+
</DropdownMenu>
|
|
245
|
+
</div>;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The one visible action should be the urgent one; if no action is urgent, all
|
|
249
|
+
of them belong in the menu.
|
|
250
|
+
|
|
251
|
+
## 7. Blank empty state → designed empty state
|
|
252
|
+
|
|
253
|
+
**Failure:** an empty array renders an empty card, indistinguishable from a
|
|
254
|
+
bug. Empty is a state you design, distinct from error.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// AFTER — icon, one sentence of what appears here, what action produces it
|
|
258
|
+
<div className="flex flex-1 flex-col items-center justify-center gap-3 p-8 text-center">
|
|
259
|
+
<div className="bg-muted flex h-16 w-16 items-center justify-center rounded-full">
|
|
260
|
+
<Icon name="fa-light fa-inbox" className="text-muted-foreground h-8 w-8" />
|
|
261
|
+
</div>
|
|
262
|
+
<p className="text-base font-medium">No invoices yet</p>
|
|
263
|
+
<p className="text-muted-foreground max-w-xs text-sm">
|
|
264
|
+
Invoices appear here as soon as your first one is issued.
|
|
265
|
+
</p>
|
|
266
|
+
</div>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
No CTA unless there is a real action the user can take from the dashboard.
|