noslopui 0.2.0 → 0.3.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.
- package/README.md +1 -1
- package/SKILL.md +96 -27
- package/dist/commands/catalog.js +30 -7
- package/dist/index.js +4 -1
- package/dist/lib/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ Anything else that speaks MCP over HTTP can be connected by hand — add `https:
|
|
|
59
59
|
|
|
60
60
|
| | |
|
|
61
61
|
|---|---|
|
|
62
|
-
| `npx noslopui search <query>` | Search
|
|
62
|
+
| `npx noslopui search <query>` | Search UI blocks and Motion Lab effects (`--section ui-blocks\|motion-lab`, `--tag style:editorial`, `--limit`) |
|
|
63
63
|
| `npx noslopui get <id>` | Print a component's code, or `--write src/Hero.tsx` (`--format tsx\|html\|vue`) |
|
|
64
64
|
| `npx noslopui prompt <id>` | Print a component's AI-prompt version |
|
|
65
65
|
| `npx noslopui design-systems [query]` | Search design systems (`--tag theme:dark`) |
|
package/SKILL.md
CHANGED
|
@@ -11,11 +11,12 @@ AI-generated) UI components, full page examples, and portable Design Systems.
|
|
|
11
11
|
## The rules
|
|
12
12
|
|
|
13
13
|
1. **Choose one design system before fetching any component, then restyle every component to it** — colours, type scale, spacing, radius, motion. One system applied everywhere is what stops a multi-page build reading as a collage.
|
|
14
|
-
2. **Search the
|
|
14
|
+
2. **Search the catalog before hand-building a section, not instead of hand-building it.** Check `search_components` for every required section first — having the design system's tokens and prose isn't a reason to skip that, and any component can be built in any system, so nothing is off-limits once one is chosen. Only build a section yourself if nothing in the catalog fits it, and tell the user that's what happened.
|
|
15
15
|
3. **Never substitute a catalog component for one the user explicitly saved** in a collection. Their pick was a decision, not an oversight.
|
|
16
16
|
4. **Ask about their brand before building** — logo, brand colours, typeface, real photography. Put their values into the system's tokens (their colour replaces `accent`) and keep the system's rules.
|
|
17
|
-
5. **
|
|
17
|
+
5. **A collection existing is not the same as the user asking for it.** `list_collections` is free to call, but only build from what it returns when the user asked for their saved items by name or in substance, or confirmed it when you asked. The default "Favorites" list is just where the save button drops a single item with zero setup — a lone design system sitting in it is not a decision to build the whole thing around. One collection with content and no explicit ask → surface it as a question, don't assume either answer. Nothing confirmed → shortlist two or three design systems with `search_design_systems` and let the user pick.
|
|
18
18
|
6. **If a gated tool says the trial has ended or the fair-use limit is reached, tell the user and stop** — don't retry, and don't swap in something generated from scratch.
|
|
19
|
+
7. **A Motion Lab effect is a signature moment, not a section.** Reach for one (`search_components` with `section: "motion_lab"`) when the user asks for an effect, motion, 3D or a showpiece, or the brief clearly calls for one — then use one per page unless they ask for more. Recolour it through its `colorVars` to the design system's tokens, follow its `usage`, and keep its reduced-motion fallback.
|
|
19
20
|
|
|
20
21
|
Setup, if the noslopUI tools aren't available: the user runs
|
|
21
22
|
`npx noslopui@latest init`, or adds `https://noslopui.com/api/mcp` in their
|
|
@@ -28,13 +29,21 @@ the account's free 3-day trial, which the first such call starts.
|
|
|
28
29
|
|
|
29
30
|
## Step 0 — Which path?
|
|
30
31
|
|
|
31
|
-
**Call `list_collections` first.** It's free on every account.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
**Call `list_collections` first.** It's free on every account. But a
|
|
33
|
+
collection existing is not the same as the user asking you to build from it —
|
|
34
|
+
the default "Favorites" list is just where the save button puts a single item
|
|
35
|
+
with zero setup, so a design system sitting there alone is very often a saved
|
|
36
|
+
reference, not a decision. Don't let its mere presence override what the user
|
|
37
|
+
actually asked for.
|
|
38
|
+
|
|
39
|
+
- The user already said to use their saved items ("build from my collection",
|
|
40
|
+
"use what I've saved", names a list by name) → **Path A**, no need to ask.
|
|
41
|
+
- Nothing was said either way, but exactly one collection has content that
|
|
42
|
+
looks relevant → **ask**: name what's in it and let them choose Path A or
|
|
43
|
+
Path B. Don't assume silently in either direction.
|
|
44
|
+
- No collection, or nothing in any of them fits → **Path B**.
|
|
45
|
+
- Several collections and no obvious match → ask which one. Don't guess
|
|
46
|
+
between two curated sets.
|
|
38
47
|
|
|
39
48
|
---
|
|
40
49
|
|
|
@@ -64,6 +73,17 @@ Do this on both paths, before choosing a system. Two groups of things.
|
|
|
64
73
|
No brand at all → say so plainly and use the design system's own palette. Don't
|
|
65
74
|
invent a logo.
|
|
66
75
|
|
|
76
|
+
No real photography → don't ship a grey box. Use a seeded placeholder from
|
|
77
|
+
[Picsum](https://picsum.photos) — `https://picsum.photos/seed/<brand-slug>-<slot-name>/<w>/<h>`
|
|
78
|
+
— so the same build gets the same photo on every regenerate instead of a new
|
|
79
|
+
random one. Mark every one with `<!-- TODO: Replace with real photo, target
|
|
80
|
+
size <w>x<h> -->` immediately above it, and write alt text for the *intended*
|
|
81
|
+
subject ("Hand-poured ceramic mug, studio lighting"), not the placeholder
|
|
82
|
+
("Picsum image"), so it's already correct once they swap it in. Skip
|
|
83
|
+
photography altogether where the chosen design system's own register is
|
|
84
|
+
photo-free (type-led, editorial, most SaaS) rather than forcing a stock photo
|
|
85
|
+
in where the system doesn't call for one.
|
|
86
|
+
|
|
67
87
|
If the user already stated all this, don't re-ask — restate it in one line and
|
|
68
88
|
move on.
|
|
69
89
|
|
|
@@ -72,8 +92,9 @@ move on.
|
|
|
72
92
|
## Path A — the user curated a collection
|
|
73
93
|
|
|
74
94
|
`get_collection({ listId })` (or `name`). Every item carries a `kind` —
|
|
75
|
-
`design_systems` or `
|
|
76
|
-
`guidance` line.
|
|
95
|
+
`design_systems`, `ui_blocks` or `motion_lab` (an effect) — plus a top-level
|
|
96
|
+
`designSystems` array and a `guidance` line. A saved effect is a pick like any
|
|
97
|
+
other: use it where it fits, restyled as in Step 3.
|
|
77
98
|
|
|
78
99
|
1. **The collection's design system wins.** One in it → that is the system, no
|
|
79
100
|
searching; `get_design_system_file` for its DESIGN.md. More than one → ask
|
|
@@ -83,8 +104,10 @@ move on.
|
|
|
83
104
|
something that covers a section, use it. Always. **Never substitute a
|
|
84
105
|
catalog component for one they explicitly saved**, even if you found
|
|
85
106
|
something you think is better — that was their call, not an oversight.
|
|
86
|
-
3. **Fill the gaps with `search_components
|
|
87
|
-
fit, and restyle what you find to
|
|
107
|
+
3. **Fill the gaps with `search_components`, checked before you hand-build
|
|
108
|
+
anything.** Search the whole catalog on fit, and restyle what you find to
|
|
109
|
+
the collection's design system. Only build a gap section yourself if
|
|
110
|
+
nothing in the catalog fits it, and say so in step 4's report.
|
|
88
111
|
4. **Report what you filled in.** "Hero, pricing and footer from your
|
|
89
112
|
collection; you had no testimonial section saved, so I pulled one from the
|
|
90
113
|
catalog and restyled it." They should never have to diff the result to find
|
|
@@ -107,12 +130,27 @@ Then filter with `tags` (ANDed) and/or a free-text `query` built from the tone
|
|
|
107
130
|
words plus purpose:
|
|
108
131
|
|
|
109
132
|
```
|
|
110
|
-
search_design_systems({ tags: ["
|
|
111
|
-
search_design_systems({
|
|
133
|
+
search_design_systems({ tags: ["industry:hair-beauty"] })
|
|
134
|
+
search_design_systems({ tags: ["industry:dentist", "theme:light"] })
|
|
135
|
+
search_design_systems({ query: "calm minimal", tags: ["industry:saas"] })
|
|
112
136
|
```
|
|
113
137
|
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
`industry:` is a two-level tree, shaped like a template marketplace's: a
|
|
139
|
+
**field** (`food-drink`, `medical`, `home-services`, `technology`…) and, under
|
|
140
|
+
it, **niches** (`restaurant`, `dentist`, `roofing`, `saas`…). Every system
|
|
141
|
+
tagged with a niche is also tagged with its field, so start with the niche if
|
|
142
|
+
the brief names one and widen to the field when that returns fewer than three.
|
|
143
|
+
|
|
144
|
+
**"Local business" is not an industry — name the trade.** A plumber is
|
|
145
|
+
`home-services`, a barber `hair-beauty`, a café `food-drink`, a physio
|
|
146
|
+
`medical`, a yoga studio `wellness`, an accountant `professional-services`.
|
|
147
|
+
Each of those looks and reads differently, and filtering on the specific field
|
|
148
|
+
is what keeps two local businesses from getting the same site.
|
|
149
|
+
|
|
150
|
+
A system can carry several fields — a warm serif system serves a restaurant, a
|
|
151
|
+
wedding planner and a spa alike — so don't treat one tag as excluding the
|
|
152
|
+
others, and don't pick the same system for every brief in a field: read the
|
|
153
|
+
descriptions and match the tone the user described.
|
|
116
154
|
|
|
117
155
|
Every result comes back with its tags, its token summary (colours, fonts,
|
|
118
156
|
radius) and a `previewUrl`. **Present the 2-3 best matches to the user with
|
|
@@ -135,11 +173,32 @@ work.
|
|
|
135
173
|
|
|
136
174
|
### B2. Find components on fit
|
|
137
175
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
**Never write a section's markup from scratch before checking the catalog.**
|
|
177
|
+
Having the DESIGN.md in hand is not the same as being done searching — for
|
|
178
|
+
*every* required section, call `search_components` first — `section` and
|
|
179
|
+
`tags` filters as needed, with `tags` ANDed the same way (`["style:editorial"]`,
|
|
180
|
+
`["animation:carousel"]`). Pick the best **structural** match: the thing that
|
|
181
|
+
actually does what the section needs. Largely ignore how it currently looks;
|
|
182
|
+
you are restyling it regardless. Only once nothing in the catalog fits a
|
|
183
|
+
section is hand-building it acceptable — and say so, don't pass it off as a
|
|
184
|
+
catalog pick.
|
|
185
|
+
|
|
186
|
+
Every result is published as React/TSX with Tailwind classes, whatever stack
|
|
187
|
+
the build target is. That's not a limitation — the markup and styling aren't
|
|
188
|
+
React-specific, so converting a result to plain HTML, Vue, or anything else is
|
|
189
|
+
a mechanical step (`className` to `class`, drop event-handler props, etc.),
|
|
190
|
+
not a rebuild. Never skip a structurally-right component, or conclude the
|
|
191
|
+
catalog has nothing for a non-React project, over its source format.
|
|
192
|
+
|
|
193
|
+
**Effects are a separate kind.** `section: "motion_lab"` searches Motion Lab:
|
|
194
|
+
shader backgrounds, 3D, liquid glass, text, cursor and scroll effects. An
|
|
195
|
+
effect isn't a section of the page — it's the one moment people remember the
|
|
196
|
+
page for. Use one where the brief asks for it (an effect, motion, 3D, "make it
|
|
197
|
+
pop") or clearly calls for a showpiece, and one per page unless the user asks
|
|
198
|
+
for more. `get_component` tells you how: `usage` says where it earns its
|
|
199
|
+
place, `props` are its knobs, `colorVars` are the CSS custom properties its
|
|
200
|
+
colours come from, and `libraries` are the exact npm packages and versions to
|
|
201
|
+
install.
|
|
143
202
|
|
|
144
203
|
Then `get_component_code` for the ones you'll use.
|
|
145
204
|
|
|
@@ -164,6 +223,13 @@ result *theirs* rather than a nice demo:
|
|
|
164
223
|
Body text stays the system's text face unless they insist.
|
|
165
224
|
- Their photography replaces placeholder imagery at the same crops and ratios.
|
|
166
225
|
|
|
226
|
+
**C. Effects take the system's colours through their variables.** A Motion
|
|
227
|
+
Lab effect exposes each colour as a CSS custom property (`colorVars`, e.g.
|
|
228
|
+
`--ml-accent`) and as a prop. Set those from the system's tokens — the brand
|
|
229
|
+
accent, the darkest surface — and tune its `props` toward the system's motion
|
|
230
|
+
rules (slower if the system is calm). Never recolour inside a shader or
|
|
231
|
+
script, and never remove its reduced-motion fallback.
|
|
232
|
+
|
|
167
233
|
**Substitute values, keep the rules.** The system's spacing scale, radius
|
|
168
234
|
discipline, contrast requirements and "one accent moment per screen" all still
|
|
169
235
|
apply after the swap — that is the point of a system: it survives a brand
|
|
@@ -196,7 +262,9 @@ exists to avoid — catch them here rather than shipping them:
|
|
|
196
262
|
- **Spacing** — compose from the DESIGN.md's spacing scale; don't invent
|
|
197
263
|
in-between values. Prefer more whitespace over a denser grid.
|
|
198
264
|
- **Motion** — only where it communicates a real state change; the DESIGN.md's
|
|
199
|
-
durations and easing, never a default bounce or spring.
|
|
265
|
+
durations and easing, never a default bounce or spring. The one exception is
|
|
266
|
+
a Motion Lab effect placed where its `usage` says it earns its place — one
|
|
267
|
+
per page, recoloured to the system.
|
|
200
268
|
- **Named slop-tells to actively reject**: gradient text, purple-on-purple
|
|
201
269
|
palettes, bounce/spring easing on everything, every layout centered with no
|
|
202
270
|
asymmetry, decorative-only animation.
|
|
@@ -204,8 +272,8 @@ exists to avoid — catch them here rather than shipping them:
|
|
|
204
272
|
## Step 5 — Point back to the catalog for variations
|
|
205
273
|
|
|
206
274
|
If the result doesn't land, don't re-prompt the same component blind. Point the
|
|
207
|
-
user at `noslopui.com/explore` (
|
|
208
|
-
|
|
275
|
+
user at `noslopui.com/explore` (every UI block in one grid, with Motion Lab and
|
|
276
|
+
design-system shelves) to browse real alternatives, and at the
|
|
209
277
|
Collections tab on `noslopui.com/account` to save the ones they like — which
|
|
210
278
|
turns the next build into Path A. Then repeat from Step 3 with whatever they
|
|
211
279
|
pick.
|
|
@@ -218,12 +286,13 @@ pick.
|
|
|
218
286
|
list_collections({}) -> { collections: [{ id, name, itemCount }] }
|
|
219
287
|
get_collection({ listId?, name? }) -> { collection, designSystems: [{id,name}], items: [{ id, name, kind, ... }], guidance }
|
|
220
288
|
|
|
221
|
-
search_design_systems({ query?, tags?, tag?, limit? }) -> { results: [{ id, name, description, tags, tokens, previewUrl, url }], availableTags }
|
|
289
|
+
search_design_systems({ query?, tags?, tag?, limit? }) -> { results: [{ id, name, description, tags, tokens, previewUrl, url }], availableTags, industryTree }
|
|
222
290
|
get_design_system({ id }) -> { id, name, tokens, previewUrl, detailUrl }
|
|
223
291
|
get_design_system_file({ id }) -> { id, name, designMd } [gated: paid or trial]
|
|
224
292
|
|
|
225
|
-
search_components({ query?, section?,
|
|
293
|
+
search_components({ query?, section?, tags?, tag?, limit? }) -> { results: [{ id, name, kind, tags, url }] }
|
|
226
294
|
get_component({ id }) -> { id, name, dependencies, builtWithDesignSystem, previewUrl, detailUrl }
|
|
295
|
+
motion_lab items add { usage, props, colorVars, libraries }
|
|
227
296
|
get_component_code({ id }) -> { id, name, snippets: [{ language, code }] } [gated: paid or trial]
|
|
228
297
|
get_component_prompt({ id }) -> { id, name, promptText } [gated: paid or trial]
|
|
229
298
|
```
|
package/dist/commands/catalog.js
CHANGED
|
@@ -13,6 +13,32 @@ import { dirname, resolve } from 'node:path';
|
|
|
13
13
|
import { callTool } from '../lib/api.js';
|
|
14
14
|
import { readSession } from '../lib/session.js';
|
|
15
15
|
import { color } from '../lib/ui.js';
|
|
16
|
+
// The catalog's two component sections, as search_components names them.
|
|
17
|
+
// Hyphens are accepted too, since that's how they read in URLs.
|
|
18
|
+
const SECTIONS = {
|
|
19
|
+
ui_blocks: 'ui_blocks',
|
|
20
|
+
'ui-blocks': 'ui_blocks',
|
|
21
|
+
blocks: 'ui_blocks',
|
|
22
|
+
motion_lab: 'motion_lab',
|
|
23
|
+
'motion-lab': 'motion_lab',
|
|
24
|
+
effects: 'motion_lab',
|
|
25
|
+
};
|
|
26
|
+
/** search_components arguments for these options; throws on a section it doesn't know. */
|
|
27
|
+
export function searchArguments(options) {
|
|
28
|
+
let section;
|
|
29
|
+
if (options.section !== undefined) {
|
|
30
|
+
section = SECTIONS[options.section.toLowerCase()];
|
|
31
|
+
if (!section)
|
|
32
|
+
throw new Error(`--section takes ui-blocks or motion-lab, not "${options.section}".`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...(options.args.length ? { query: options.args.join(' ') } : {}),
|
|
36
|
+
...(section ? { section } : {}),
|
|
37
|
+
...(options.tags.length ? { tags: options.tags } : {}),
|
|
38
|
+
...(options.framework ? { framework: options.framework } : {}),
|
|
39
|
+
...(options.limit ? { limit: options.limit } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
16
42
|
const out = (s = '') => process.stdout.write(`${s}\n`);
|
|
17
43
|
const note = (s = '') => process.stderr.write(`${s}\n`);
|
|
18
44
|
function requireKey() {
|
|
@@ -48,13 +74,9 @@ function writeOut(path, text, force) {
|
|
|
48
74
|
}
|
|
49
75
|
const truncate = (s, n) => (s && s.length > n ? `${s.slice(0, n - 1)}…` : s ?? '');
|
|
50
76
|
export async function search(options) {
|
|
77
|
+
const args = searchArguments(options);
|
|
51
78
|
const key = requireKey();
|
|
52
|
-
const r = check(await callTool(key, 'search_components',
|
|
53
|
-
...(options.args.length ? { query: options.args.join(' ') } : {}),
|
|
54
|
-
...(options.tags.length ? { tags: options.tags } : {}),
|
|
55
|
-
...(options.framework ? { framework: options.framework } : {}),
|
|
56
|
-
...(options.limit ? { limit: options.limit } : {}),
|
|
57
|
-
}));
|
|
79
|
+
const r = check(await callTool(key, 'search_components', args));
|
|
58
80
|
if (options.json)
|
|
59
81
|
return out(JSON.stringify(r.data, null, 2)), 0;
|
|
60
82
|
const results = r.data?.results ?? [];
|
|
@@ -63,7 +85,8 @@ export async function search(options) {
|
|
|
63
85
|
return 1;
|
|
64
86
|
}
|
|
65
87
|
for (const item of results) {
|
|
66
|
-
|
|
88
|
+
const kind = item.kind === 'motion_lab' ? ` ${color.cyan('Motion Lab')}` : '';
|
|
89
|
+
out(`${color.bold(item.name)} ${color.dim(item.id)}${kind}`);
|
|
67
90
|
if (item.description)
|
|
68
91
|
out(` ${truncate(item.description, 110)}`);
|
|
69
92
|
out(` ${color.cyan(item.url)}`);
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ ${color.bold('Account')}
|
|
|
37
37
|
logout Forget this CLI's sign-in
|
|
38
38
|
|
|
39
39
|
${color.bold('Catalog')}
|
|
40
|
-
search <query> Search
|
|
40
|
+
search <query> Search blocks and effects --section ui-blocks|motion-lab, --tag <facet:value>, --limit
|
|
41
41
|
get <id> Print a component's code --format tsx|html|vue, --write <file>
|
|
42
42
|
prompt <id> Print a component's AI-prompt version
|
|
43
43
|
design-systems [query] Search design systems --tag theme:dark …
|
|
@@ -98,6 +98,8 @@ function parse(argv) {
|
|
|
98
98
|
parsed.format = take();
|
|
99
99
|
else if (flag === '--framework')
|
|
100
100
|
parsed.framework = take();
|
|
101
|
+
else if (flag === '--section')
|
|
102
|
+
parsed.section = take();
|
|
101
103
|
else if (flag === '--write' || flag === '-o')
|
|
102
104
|
parsed.write = take();
|
|
103
105
|
else if (flag === '--limit') {
|
|
@@ -136,6 +138,7 @@ async function main() {
|
|
|
136
138
|
...(args.limit ? { limit: args.limit } : {}),
|
|
137
139
|
...(args.format ? { format: args.format } : {}),
|
|
138
140
|
...(args.framework ? { framework: args.framework } : {}),
|
|
141
|
+
...(args.section ? { section: args.section } : {}),
|
|
139
142
|
...(args.write ? { write: args.write } : {}),
|
|
140
143
|
};
|
|
141
144
|
switch (args.command) {
|
package/dist/lib/version.js
CHANGED