@mindstudio-ai/remy 0.1.40 → 0.1.41

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.
@@ -4,13 +4,11 @@ Design standards for web interfaces in MindStudio apps.
4
4
 
5
5
  ## Quality Bar
6
6
 
7
- Every interface must feel like a polished, shipping product — not a
8
- prototype, not a starter template, not a homework assignment. The standard
9
- is iOS and Apple's bundled iOS apps, Notion, Stripe. If it wouldn't look
10
- good on Dribbble, Behance, or Mobbin, it's not done.
7
+ Every interface must feel like a polished, shipping product — not a prototype, not a starter template, not a homework assignment. If it wouldn't look good on Dribbble, Behance, or Mobbin, it's not done.
11
8
 
12
- MindStudio apps are end-user products. The interface is the product. Users
13
- judge the entire app by how it looks and feels in the first 3 seconds.
9
+ MindStudio apps are end-user products. The interface is the product. Users judge the entire app by how it looks and feels in the first 3 seconds.
10
+
11
+ For questions about design, layout, UI, interfaces, components, and anything else, ask the design expert! They have access to countless resources and references and can provide great inspiration as you plan and work - the response to a quick "I'm making XYZ - wdyt?" can be such a powerful input to your thinking!
14
12
 
15
13
  ## Design System from the Spec
16
14
 
@@ -20,232 +18,83 @@ Set up a lightweight theme layer early (CSS variables or a small tokens file) so
20
18
 
21
19
  **When brand spec files are present, always use the defined fonts and colors in generated code.** Do not pick your own fonts or colors when the spec defines them. Reference colors semantically (as CSS variables or named constants) rather than scattering raw hex values through the codebase.
22
20
 
23
- ### Colors block format
24
-
25
- A `` ```colors `` fenced block in a `type: design/color` spec file declares 3-5 brand colors with evocative names, hex values, and descriptions. The names are brand identity names (not CSS property names), and the descriptions explain the color's role in the brand:
26
-
27
- ```colors
28
- Midnight:
29
- value: "#000000"
30
- description: Primary background and dark surfaces
31
- Charcoal:
32
- value: "#1C1C1E"
33
- description: Elevated surfaces and containers
34
- Snow:
35
- value: "#F5F5F7"
36
- description: Primary text and foreground elements
37
- Smoke:
38
- value: "#86868B"
39
- description: Secondary text and supporting content
40
- ```
41
-
42
- Derive additional implementation colors (borders, focus states, hover states, disabled states) from the brand palette rather than expecting them to be specified.
43
-
44
- ### Typography block format
45
-
46
- A `` ```typography `` fenced block in a `type: design/typography` spec file declares fonts (with source URLs) and one or two anchor styles (typically Display and Body). Styles can include an optional `case` field (`uppercase`, `lowercase`, `capitalize`) for text-transform. Derive additional styles (labels, buttons, captions, overlines) from these anchors:
47
-
48
- ```typography
49
- fonts:
50
- Satoshi:
51
- src: https://api.fontshare.com/v2/css?f[]=satoshi@400,500,600,700&display=swap
52
- Clash Grotesk:
53
- src: https://api.fontshare.com/v2/css?f[]=clash-grotesk@400,500,600&display=swap
54
-
55
- styles:
56
- Display:
57
- font: Satoshi
58
- size: 40px
59
- weight: 600
60
- letterSpacing: -0.03em
61
- lineHeight: 1.1
62
- case: uppercase
63
- description: Page titles and hero text
64
- Body:
65
- font: Satoshi
66
- size: 16px
67
- weight: 400
68
- lineHeight: 1.5
69
- description: Default reading text
70
- ```
71
-
72
- ## Be Distinctive
73
-
74
- AI-generated interfaces tend to converge on the same generic look: safe
75
- fonts, timid colors, predictable layouts. Fight this actively. Every
76
- interface should have character and intentionality — it should look like a
77
- designer made deliberate choices, not like it was generated from a template.
78
-
79
- **Typography must be a conscious choice.** Pick fonts that are beautiful,
80
- distinctive, and appropriate for the product's personality. Generic system
81
- fonts and overused defaults make everything look the same. Typography is
82
- the single fastest way to give an interface identity.
83
-
84
- **Commit to a color palette.** One or two dominant colors with sharp accents
85
- outperform timid, evenly-distributed palettes. Draw inspiration from the
86
- app's domain — a finance tool might use deep greens and golds; a creative
87
- tool might use bold, saturated primaries. The palette should feel intentional
88
- and owned, not randomly selected.
89
-
90
- **Backgrounds create atmosphere.** Solid white or solid gray is the safe
91
- default and the enemy of distinctiveness. Layer subtle gradients, use warm
92
- or cool tints, add geometric patterns or contextual textures. The background
93
- sets the mood before the user reads a single word.
94
-
95
- **Layouts should surprise.** Avoid the predictable patterns: three equal
96
- boxes with icons, centered hero with subtitle, generic card grid. Use
97
- asymmetry, varied column widths, creative negative space, unexpected
98
- compositions. Study Behance and Mobbin for layout inspiration. Every screen
99
- should feel considered, not generated.
100
-
101
- ## App-Like, Not Web-Page-Like
102
-
103
- Interfaces run fullscreen in the user's browser or a wrapped webview mobile
104
- app. They should feel like native apps, not websites.
105
-
106
- - **No long scrolling pages.** Use structured layouts: cards, split panes,
107
- steppers, tabs, grouped sections that fit the viewport. The interface
108
- should feel like a single-purpose tool, not a document.
109
- - **On mobile**, scrolling may be necessary, but use sticky headers, fixed
110
- CTAs, and anchored navigation to keep key actions within reach.
111
- - Think of every screen as something the user opens, uses, and closes —
112
- not something they read.
113
-
114
- ## Visual Design
115
-
116
- - **Typography:** Strong hierarchy — clear distinction between headings,
117
- body, labels, and captions. Use weight and size, not just color, to
118
- create hierarchy. Choose fonts that elevate the interface and give it
119
- personality.
120
- - **Color:** Clean, vibrant, intentional. Use color to communicate state
121
- and guide attention, not to decorate. Commit to a direction — bold and
122
- saturated, or muted and editorial — and follow through consistently.
123
- - **Spacing:** Consistent and generous. Padding and margins should be
124
- uniform across all components — nothing should feel cramped or uneven.
125
- White space is a feature, not wasted space.
126
- - **Components:** Every component (buttons, inputs, cards, modals, lists)
127
- should look like it belongs to the same design system. Consistent border
128
- radii, consistent shadows, consistent padding. If two buttons on the
129
- same screen look different for no reason, that's a bug.
130
-
131
- ## Animation
132
-
133
- Use motion to make interactions feel polished, not to show off. Focus on
134
- high-impact moments: a well-orchestrated page load with staggered reveals
135
- creates more delight than scattered micro-interactions everywhere.
136
-
137
- - Transitions between states should be smooth but fast.
138
- - Streaming content should flow into containers that grow naturally without
139
- pushing sibling elements around.
140
- - No parallax, no cheesy scroll effects, no bounce/elastic easing, no
141
- gratuitous loading animations.
21
+ ## Important: Build Apps, Not Web Pages
22
+
23
+ Interfaces run fullscreen in the user's browser or a wrapped webview mobile app. They must feel like native Mac or iOS apps, not websites.
24
+
25
+ - **No long scrolling pages.** Use structured layouts: cards, split panes, steppers, tabs, grouped sections that fit the viewport. The interface should feel like an award winning iOS or macOS app, not a document.
26
+ - **On mobile**, scrolling may be necessary, but use sticky headers, fixed CTAs, and anchored navigation to keep key actions within reach.
27
+ - Think of every screen as something the user opens, uses, and closes — not something they read.
142
28
 
143
29
  ## Layout Stability
144
30
 
145
- Layout shift is never acceptable. Elements jumping around as content loads
146
- or streams in makes an interface feel broken.
147
-
148
- - Reserve space for content that hasn't arrived yet. Use fixed/min-height
149
- containers, skeletons, or aspect-ratio boxes.
150
- - Images must always have explicit dimensions so the browser reserves space
151
- before the image loads.
152
- - Loading-to-loaded transitions should swap content in-place without
153
- changing container size.
154
- - Buttons must not change size during loading states. Use a fixed width or
155
- `min-width`, and swap the label for a spinner or short text that fits the
156
- same space. "Submit" becoming "Submitting..." should not make the button
157
- wider and push adjacent elements around.
158
- - Conditional UI should use opacity/overlay transitions, not insertion into
159
- flow that displaces existing content.
31
+ Layout shift is never acceptable. Elements jumping around as content loads or streams in makes an interface feel broken.
32
+
33
+ - Reserve space for content that hasn't arrived yet. Use fixed/min-height containers, skeletons, or aspect-ratio boxes.
34
+ - Images must always have explicit dimensions so the browser reserves space before the image loads.
35
+ - Loading-to-loaded transitions should swap content in-place without changing container size.
36
+ - Buttons must not change size during loading states. Use a fixed width or `min-width`, and swap the label for a spinner or short text that fits the same space. "Submit" becoming "Submitting..." should not make the button wider and push adjacent elements around.
37
+ - Conditional UI should use opacity/overlay transitions, not insertion into flow that displaces existing content.
38
+ - This is especially important to keep in mind when building things that display AI generated text, especially if the text streams in. Make sure to never shift layout because of streaming AI text.
160
39
 
161
40
  ## Responsive Design
162
41
 
163
- Every interface must work on both desktop and mobile.
42
+ Every interface must work on both desktop and mobile. Think about how the app will be used and prioritize the primary use case - mobile apps should be mobile-first, business apps are more likely to be used on desktops.
164
43
 
165
44
  - Use the full viewport. Backgrounds should extend to edges.
166
- - On desktop, use the space — multi-column layouts, sidebars, spacious
167
- cards. Avoid narrow centered columns with empty gutters on a wide screen.
45
+ - On desktop, use the space — multi-column layouts, sidebars, spacious cards. Avoid narrow centered columns with empty gutters on a wide screen.
168
46
  - On mobile, stack gracefully. Prioritize content and actions.
169
- - Test at both extremes. A layout that only looks good at one breakpoint
170
- is not done.
47
+ - Test at both extremes. A layout that only looks good at one breakpoint is not done.
48
+ - When the app is primarily mobile (e.g., a mobile-first consumer app, a tool designed for on-the-go use), set `"defaultPreviewMode": "mobile"` in `web.json` so the editor previews in a mobile viewport by default.
171
49
 
172
50
  ## Forms
173
51
 
174
52
  Forms should feel like interactions, not paperwork.
175
53
 
176
54
  - Group related fields visually. Use cards or sections, not a flat list.
177
- - Inline validation — show errors as the user types, not after submit.
178
- Validation must never introduce layout shift.
179
- - Loading states after submission. Always indicate that something is
180
- happening.
55
+ - Inline validation — show errors as the user types, not after submit. Validation must never introduce layout shift.
56
+ - Loading states after submission. Always indicate that something is happening.
181
57
  - Disabled states should be visually distinct but not jarring.
182
- - Even data entry can be beautiful. Pay attention to alignment, padding,
183
- and spacing. Consistency is key.
58
+ - Even data entry can be beautiful. Pay attention to alignment, padding, and spacing. Consistency is key.
59
+
60
+ #### Form Elements
61
+
62
+ - When loading elements dynamically, make sure the experience isn't janky (e.g., user selects something from a dropdown and suddenly a bunch of thigns snap in, or user loads a form and then after 500ms once a network call resolves the user sees a jump for a new element to appear)
63
+
64
+ ### Placeholders
65
+
66
+ - Always use icon placeholders for things like empty user profile pictures and other empty images.
67
+ - Create beautiful empty states by using icons alongside labels. Empty states should feel like an invitation to get started, not an error mode.
68
+
69
+ #### Loading states
70
+
71
+ Buttons should use a small animated spinner during loading, not text labels like "Loading..." or "Submitting...". The `loader-2` tabler icon with a CSS spin animation is a common pattern. The spinner replaces the button label while maintaining the button's size — be sure there is no layout shift. Recommend the developer implement this as a reusable pattern early.
184
72
 
185
73
  ## Data Fetching and Updates
186
74
 
187
- The UI should feel instant. Never make the user wait for a server round-trip
188
- to see the result of their own action.
189
-
190
- - **Optimistic updates.** When a user adds a row, toggles a setting, or
191
- submits a form, update the UI immediately and let the backend confirm
192
- in the background. If the backend fails, revert and show an error.
193
- - **Use SWR for data fetching** (`useSWR` from the `swr` package). It
194
- handles caching, revalidation, and stale-while-revalidate out of the box.
195
- Prefer SWR over manual `useEffect` + `useState` fetch patterns.
196
- - **Mutate after actions.** After a successful create/update/delete, call
197
- `mutate()` to revalidate the relevant SWR cache rather than manually
198
- updating local state.
199
- - **Skeleton loading.** Show skeletons that mirror the layout on initial
200
- load. Never show a blank page or centered spinner while data is loading.
201
-
202
- ## What Good Looks Like
203
-
204
- - A dashboard that feels like Linear clean data, clear hierarchy, every
205
- pixel intentional.
206
- - A form that feels like Stripe Checkout focused, calm, confident.
207
- - A settings page that feels like iOS Settings organized, scannable,
208
- no clutter.
209
- - A list view that feels like Notion flexible, spacious, information-dense
210
- without feeling crowded.
211
-
212
- ## What to Actively Avoid
213
-
214
- These are the hallmarks of generic AI-generated interfaces. Every one of
215
- them makes an interface look like it was auto-generated rather than designed.
216
-
217
- - **Generic fonts.** Overused defaults that strip away all personality.
218
- Instead: pick a distinctive Google Font that fits the app's character.
219
- - **Purple or indigo anything.** Purple gradients, purple buttons, purple
220
- accents. This is the #1 AI-generated aesthetic cliché. Instead: use
221
- a color palette that fits the app's domain — greens for finance, warm
222
- neutrals for productivity, bold primaries for creative tools, or just
223
- confident grayscale.
224
- - **Colored left-border callout boxes.** Rounded divs with a thick colored
225
- `border-left` — the generic "info card" pattern. Instead: use typography,
226
- spacing, and background tints to create hierarchy. If you need to call
227
- something out, use a full subtle background or a top border.
228
- - **Three equal boxes with icons.** The default AI landing page layout.
229
- Instead: use asymmetric layouts, varied column widths, or a single
230
- focused content area.
231
- - **Timid color palettes.** Evenly distributed, non-committal colors.
232
- Instead: one or two dominant colors with sharp accents. Commit to a
233
- direction.
234
- - **Card-heavy nested layouts.** Cards inside cards, everything boxed.
235
- Instead: use space, typography, and dividers to create hierarchy without
236
- extra containers.
237
- - **Inconsistent spacing.** 12px here, 20px there, 8px somewhere else.
238
- Instead: define a spacing scale (4/8/12/16/24/32/48/64) and use it
239
- everywhere.
240
- - **Components from different visual languages.** Rounded buttons next to
241
- square inputs, shadows mixed with flat design. Instead: pick one system
242
- and apply it consistently.
243
- - **Long scrolling forms with no visual grouping.** Instead: group fields
244
- into sections with clear headings, cards, or stepped flows.
245
- - **Cramped layouts.** Text pressed against edges, no room to breathe.
246
- Instead: generous padding, comfortable margins, let the content float.
247
- - **Loading states that are just a centered spinner on a blank page.**
248
- Instead: use skeletons that mirror the layout, or keep the existing
249
- structure visible with a subtle loading indicator.
250
- - **Any interface where the first reaction is "this looks like a demo" or
251
- "this looks like it was made with a website builder."**
75
+ The UI should feel instant. Never make the user wait for a server round-trip to see the result of their own action.
76
+
77
+ - **Optimistic updates.** When a user adds a row, toggles a setting, or submits a form, update the UI immediately and let the backend confirm in the background. If the backend fails, revert and show an error.
78
+ - **Use SWR for data fetching** (`useSWR` from the `swr` package). It handles caching, revalidation, and stale-while-revalidate out of the box. Prefer SWR over manual `useEffect` + `useState` fetch patterns.
79
+ - **Mutate after actions.** After a successful create/update/delete, call `mutate()` to revalidate the relevant SWR cache rather than manually updating local state.
80
+ - **Skeleton loading.** Show skeletons that mirror the layout on initial load. Never show a blank page or centered spinner while data is loading.
81
+
82
+ ## FTUE
83
+
84
+ All interactive apps must be intuitive and easy to use. Form elements must be well-labelled. Complex interfaces should have descriptions or tooltips when helpful. Complex apps benefit from a beautiful simple onboarding modal on first use or a simple click tour. Even if the app is intuitive and easy to use, users showing up for the first time might still be overwhelmed or confused, and we have an opportunity to set expectations, provide context, and make the user confident as they use our product. Don't neglect this.
85
+
86
+ ## What to Actively Avoid At All Costs
87
+
88
+ - **Avoid generic fonts.** Overused defaults that strip away all personality. Instead: pick a distinctive Google Font that fits the app's character.
89
+ - **Avoid purple or indigo anything.** Purple gradients, purple buttons, purple accents are overused. The user will be dismissive of our designs if they come out looking purple or indigo.
90
+ - **Avoid colored left-border callout boxes.** Rounded divs with a thick colored `border-left` — the generic "info card" pattern. Instead: use typography, spacing, and background tints to create hierarchy. If you need to call something out, use a full subtle background or a top border.
91
+ - **Avoid three equal boxes with icons.** The default AI landing page layout. Instead: use asymmetric layouts, varied column widths, or a single focused content area.
92
+ - **Avoid timid color palettes.** Evenly distributed, non-committal colors. Instead: one or two dominant colors with sharp accents. Commit to a direction.
93
+ - **Avoid card-heavy nested layouts.** Cards inside cards, everything boxed. Instead: use space, typography, and dividers to create hierarchy without extra containers.
94
+ - **Avoid inconsistent spacing.** 12px here, 20px there, 8px somewhere else. Instead: define a spacing scale (4/8/12/16/24/32/48/64) and use it everywhere.
95
+ - **Avoid components from different visual languages.** Rounded buttons next to square inputs, shadows mixed with flat design. Instead: pick one system and apply it consistently.
96
+ - **Avoid long scrolling forms with no visual grouping.** Instead: group fields into sections with clear headings, cards, or stepped flows.
97
+ - **Avoid cramped layouts.** Text pressed against edges, no room to breathe. Instead: generous padding, comfortable margins, let the content float.
98
+ - **Avoid loading states that are just a centered spinner on a blank page.** Instead: use skeletons that mirror the layout, or keep the existing structure visible with a subtle loading indicator.
99
+
100
+ Most importantly: **Avoid any interface where the first reaction is "this looks like a demo" or "this looks like it was made with a website builder."**
@@ -27,7 +27,8 @@ dist/interfaces/web/
27
27
  ```json
28
28
  {
29
29
  "devPort": 5173,
30
- "devCommand": "npm run dev"
30
+ "devCommand": "npm run dev",
31
+ "defaultPreviewMode": "desktop"
31
32
  }
32
33
  ```
33
34
 
@@ -35,6 +36,7 @@ dist/interfaces/web/
35
36
  |-------|------|---------|-------------|
36
37
  | `devPort` | `number` | `5173` | Port for the dev server |
37
38
  | `devCommand` | `string` | `"npm run dev"` | Command to start the dev server |
39
+ | `defaultPreviewMode` | `"desktop"` \| `"mobile"` | `"desktop"` | Default preview viewport in the editor. Set to `"mobile"` for mobile-first apps. |
38
40
 
39
41
  ### Frontend SDK
40
42
 
@@ -209,7 +209,7 @@ The card system generates images using the brand's typography and color
209
209
  palette, creating shareable assets that feel native to the app's identity.
210
210
 
211
211
  ~~~
212
- Use generateImage with Seedream to create styled cards. Card template
212
+ Use generateImage to create styled cards. Card template
213
213
  applies brand typography and colors from the spec. Export as PNG via
214
214
  CDN transform at 2x resolution. Social sharing via Web Share API with
215
215
  clipboard fallback for unsupported browsers.
@@ -217,6 +217,6 @@ clipboard fallback for unsupported browsers.
217
217
 
218
218
  ## History
219
219
 
220
- - **2026-03-22** — Built card generation using generateImage with Seedream.
220
+ - **2026-03-22** — Built card generation using generateImage.
221
221
  Added share button to haiku detail view.
222
222
  ```
@@ -10,7 +10,7 @@ Note: when you talk about the team to the user, refer to them by their name or a
10
10
 
11
11
  Your designer. Consult for any visual decision — choosing a color, picking fonts, proposing a layout, soucing images, reviewing whether something looks good. Not just during intake or big design moments. If you're about to write CSS and you're not sure about a color, ask. If you just built a page and want a gut check, ask the designer to take a quick look. If the user says "I don't like how this looks," ask the design expert what to change rather than guessing yourself, or if they say "I want a different image," that's the designer's problem, not yours. The design expert can also source images if you need images for placeholders in scenarios - use it for bespoke, tailor-made images suited to the scenario instead of trying to guess stock photo URLs.
12
12
 
13
- The design expert cannot see your conversation with the user, so include all relevant context and requirements in your task. It can take screenshots of the app preview on its own — just ask it to review what's been built.
13
+ The design expert cannot see your conversation with the user, so include all relevant context and requirements in your task. It also can not see its own conversation history, so if you want an audit you need to provide the exact values to check, or any other necessary context for it to do its job. It can take screenshots of the app preview on its own — just ask it to review what's been built.
14
14
 
15
15
  Returns concrete resources: hex values, font names with CSS URLs, image URLs, layout descriptions. It has curated font catalogs and design inspiration built in — don't ask it to research generic inspiration or look up "best X apps." Only point it at specific URLs if the user references a particular site, brand, or identity to match.
16
16
 
@@ -534,6 +534,43 @@ function useInspiration() {
534
534
  return { data, loading, error, addImage, deleteImage, analyze, dedup, reload };
535
535
  }
536
536
 
537
+ function useUiInspiration() {
538
+ const [data, setData] = useState(null);
539
+ const [loading, setLoading] = useState(true);
540
+ const [error, setError] = useState(null);
541
+
542
+ const reload = useCallback(async () => {
543
+ try {
544
+ setLoading(true);
545
+ const d = await api('/api/ui-inspiration');
546
+ setData(d);
547
+ setError(null);
548
+ } catch (e) {
549
+ setError(e.message);
550
+ } finally {
551
+ setLoading(false);
552
+ }
553
+ }, []);
554
+
555
+ useEffect(() => { reload(); }, [reload]);
556
+
557
+ const addScreen = useCallback(async (entry) => {
558
+ await api('/api/ui-inspiration', { method: 'POST', body: entry });
559
+ await reload();
560
+ }, [reload]);
561
+
562
+ const deleteScreen = useCallback(async (index) => {
563
+ await api('/api/ui-inspiration/' + index, { method: 'DELETE' });
564
+ await reload();
565
+ }, [reload]);
566
+
567
+ const analyze = useCallback(async (url) => {
568
+ return api('/api/ui-inspiration/analyze', { method: 'POST', body: { url } });
569
+ }, []);
570
+
571
+ return { data, loading, error, addScreen, deleteScreen, analyze, reload };
572
+ }
573
+
537
574
  // ---------------------------------------------------------------------------
538
575
  // Toast
539
576
  // ---------------------------------------------------------------------------
@@ -862,6 +899,71 @@ function InspirationTab({ inspiration }) {
862
899
  `;
863
900
  }
864
901
 
902
+ function UiInspirationDetail({ screen, index, onDelete }) {
903
+ if (!screen) return html`<div class="inspiration-empty">Select a screen</div>`;
904
+
905
+ return html`
906
+ <div class="inspiration-detail">
907
+ <img class="inspiration-detail-img" src=${screen.url} alt="UI reference" />
908
+ <div class="inspiration-detail-body">
909
+ <div class="inspiration-detail-header">
910
+ <span>#${index}</span>
911
+ <button class="btn-danger" onClick=${() => onDelete(index)} title="Delete">✕</button>
912
+ </div>
913
+ <div class="inspiration-analysis"
914
+ dangerouslySetInnerHTML=${{ __html: renderAnalysis(screen.analysis) }}
915
+ />
916
+ </div>
917
+ </div>
918
+ `;
919
+ }
920
+
921
+ function UiInspirationTab({ uiInspiration }) {
922
+ const [selected, setSelected] = useState(0);
923
+ const sidebarRef = { current: null };
924
+
925
+ if (uiInspiration.loading) return html`<div class="loading"><span class="spinner"></span></div>`;
926
+ if (uiInspiration.error) return html`<div class="loading" style="color:#e00">Error: ${uiInspiration.error}</div>`;
927
+ if (!uiInspiration.data) return null;
928
+
929
+ const screens = uiInspiration.data.screens;
930
+ const current = screens[selected] || null;
931
+
932
+ const select = (i) => {
933
+ const clamped = Math.max(0, Math.min(i, screens.length - 1));
934
+ setSelected(clamped);
935
+ sidebarRef.current?.children[clamped]?.scrollIntoView({ block: 'center' });
936
+ };
937
+
938
+ const onKey = (e) => {
939
+ if (e.key === 'ArrowDown') { e.preventDefault(); select(selected + 1); }
940
+ if (e.key === 'ArrowUp') { e.preventDefault(); select(selected - 1); }
941
+ };
942
+
943
+ const handleDelete = async (index) => {
944
+ const nextIndex = index >= screens.length - 1 ? Math.max(0, index - 1) : index;
945
+ setSelected(nextIndex);
946
+ await uiInspiration.deleteScreen(index);
947
+ };
948
+
949
+ return html`
950
+ <div class="inspiration-layout">
951
+ <div class="inspiration-sidebar" ref=${el => sidebarRef.current = el} tabindex="0" onKeyDown=${onKey}>
952
+ ${screens.map((s, i) => html`
953
+ <img
954
+ key=${i}
955
+ class="inspiration-thumb ${i === selected ? 'active' : ''}"
956
+ src=${s.url}
957
+ loading="lazy"
958
+ onClick=${() => select(i)}
959
+ />
960
+ `)}
961
+ </div>
962
+ <${UiInspirationDetail} screen=${current} index=${selected} onDelete=${handleDelete} />
963
+ </div>
964
+ `;
965
+ }
966
+
865
967
  // ---------------------------------------------------------------------------
866
968
  // App
867
969
  // ---------------------------------------------------------------------------
@@ -870,6 +972,7 @@ function App() {
870
972
  const [tab, setTab] = useState('fonts');
871
973
  const fonts = useFonts();
872
974
  const inspiration = useInspiration();
975
+ const uiInspiration = useUiInspiration();
873
976
  const toast = useToast();
874
977
 
875
978
  return html`
@@ -878,6 +981,7 @@ function App() {
878
981
  <button class="tab ${tab === 'fonts' ? 'active' : ''}" onClick=${() => setTab('fonts')}>Fonts${fonts.data ? ` (${fonts.data.fonts.length})` : ''}</button>
879
982
  <button class="tab ${tab === 'pairings' ? 'active' : ''}" onClick=${() => setTab('pairings')}>Pairings${fonts.data ? ` (${fonts.data.pairings.length})` : ''}</button>
880
983
  <button class="tab ${tab === 'inspiration' ? 'active' : ''}" onClick=${() => setTab('inspiration')}>Inspiration${inspiration.data ? ` (${inspiration.data.images.length})` : ''}</button>
984
+ <button class="tab ${tab === 'ui' ? 'active' : ''}" onClick=${() => setTab('ui')}>UI Patterns${uiInspiration.data ? ` (${uiInspiration.data.screens.length})` : ''}</button>
881
985
  </div>
882
986
  ${tab === 'inspiration' ? html`
883
987
  <div style="margin-left: auto;">
@@ -888,6 +992,7 @@ function App() {
888
992
  <div class="content">
889
993
  ${tab === 'fonts' ? html`<${FontsTab} fonts=${fonts} />`
890
994
  : tab === 'pairings' ? html`<${PairingsTab} fonts=${fonts} />`
995
+ : tab === 'ui' ? html`<${UiInspirationTab} uiInspiration=${uiInspiration} />`
891
996
  : html`<${InspirationTab} inspiration=${inspiration} />`
892
997
  }
893
998
  </div>
@@ -17,6 +17,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
17
17
  const dataDir = join(__dirname, '..');
18
18
  const fontsPath = join(dataDir, 'fonts.json');
19
19
  const inspirationPath = join(dataDir, 'inspiration.json');
20
+ const uiInspirationPath = join(dataDir, 'ui_inspiration_compiled.json');
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // JSON helpers
@@ -222,6 +223,50 @@ const server = createServer(async (req, res) => {
222
223
  return json(res, { ok: true, removed, count: deduped.length });
223
224
  }
224
225
 
226
+ // --- UI Inspiration API ---
227
+
228
+ if (path === '/api/ui-inspiration' && method === 'GET') {
229
+ return json(res, readJson(uiInspirationPath));
230
+ }
231
+
232
+ if (path === '/api/ui-inspiration' && method === 'POST') {
233
+ const entry = await parseBody(req);
234
+ const data = readJson(uiInspirationPath);
235
+ data.screens.push(entry);
236
+ writeJson(uiInspirationPath, data);
237
+ return json(res, { ok: true, count: data.screens.length }, 201);
238
+ }
239
+
240
+ const uiInspirationDeleteMatch = path.match(/^\/api\/ui-inspiration\/(\d+)$/);
241
+ if (uiInspirationDeleteMatch && method === 'DELETE') {
242
+ const index = parseInt(uiInspirationDeleteMatch[1], 10);
243
+ const data = readJson(uiInspirationPath);
244
+ if (index < 0 || index >= data.screens.length) {
245
+ return error(res, 'Screen index out of range', 404);
246
+ }
247
+ data.screens.splice(index, 1);
248
+ writeJson(uiInspirationPath, data);
249
+ return json(res, { ok: true, count: data.screens.length });
250
+ }
251
+
252
+ if (path === '/api/ui-inspiration/analyze' && method === 'POST') {
253
+ const { url } = await parseBody(req);
254
+ if (!url) return error(res, 'url is required');
255
+
256
+ const promptFile = join(dataDir, 'prompts', 'ui-analysis.md');
257
+ const prompt = readFileSync(promptFile, 'utf-8').trim();
258
+
259
+ try {
260
+ const result = execSync(
261
+ `mindstudio analyze-image --prompt ${JSON.stringify(prompt)} --image-url ${JSON.stringify(url)} --output-key analysis --no-meta`,
262
+ { encoding: 'utf-8', timeout: 120000 },
263
+ ).trim();
264
+ return json(res, { url, analysis: result });
265
+ } catch (err) {
266
+ return error(res, `Analysis failed: ${err.message}`, 500);
267
+ }
268
+ }
269
+
225
270
  notFound(res);
226
271
  } catch (err) {
227
272
  error(res, err.message, 500);