wtt-connect 0.1.8 → 0.2.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/opendesign-skills/LICENSE +21 -0
- package/opendesign-skills/create-design-system/SKILL.md +68 -0
- package/opendesign-skills/frontend-design/SKILL.md +49 -0
- package/opendesign-skills/interactive-prototype/SKILL.md +44 -0
- package/opendesign-skills/make-a-deck/SKILL.md +101 -0
- package/opendesign-skills/make-tweakable/SKILL.md +41 -0
- package/opendesign-skills/opendesign/SKILL.md +132 -0
- package/opendesign-skills/opendesign/viewer.html +325 -0
- package/opendesign-skills/wireframe/SKILL.md +43 -0
- package/package.json +2 -1
- package/src/artifacts.js +5 -3
- package/src/config.js +15 -0
- package/src/opendesign.js +46 -10
- package/src/runner.js +16 -5
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 manalkaff
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-design-system
|
|
3
|
+
description: Use when the user asks to produce a reusable design system or UI kit from an existing brand, codebase, or product. Writes to ./opendesign/design-systems/<name>/ so opendesign can auto-discover it in future sessions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when the user asks you to produce a reusable design system or UI kit from an existing brand, codebase, or product.
|
|
7
|
+
|
|
8
|
+
## Where design systems live
|
|
9
|
+
|
|
10
|
+
All design systems for a project live under `./opendesign/design-systems/<name>/`. Multiple systems can coexist — for example, `./opendesign/design-systems/marketing/`, `./opendesign/design-systems/product/`, `./opendesign/design-systems/deck-template/`. The name is required and descriptive of what the system is for (brand surface, product surface, deck template, co-brand partner). Never use `design-system/` singular or generic names like `main`.
|
|
11
|
+
|
|
12
|
+
A folder is recognized as a design system if it contains **either** a `SKILL.md` **or** a tokens file (`colors_and_type.css` at the root, or `tokens/colors_and_type.css` nested). `opendesign` accepts either marker when detecting systems. Always write both: the `SKILL.md` makes the folder portable as a standalone agent skill, and the tokens file is the canonical output of this skill.
|
|
13
|
+
|
|
14
|
+
## Required paths (non-negotiable)
|
|
15
|
+
|
|
16
|
+
Every generated system MUST write to these exact paths, relative to project root. `opendesign` auto-discovery depends on them — deviating breaks detection.
|
|
17
|
+
|
|
18
|
+
- `./opendesign/design-systems/<name>/SKILL.md` — portable skill marker.
|
|
19
|
+
- `./opendesign/design-systems/<name>/tokens/colors_and_type.css` — canonical tokens file. Nested under `tokens/`, not at the folder root. This is the discovery marker.
|
|
20
|
+
- `./opendesign/design-systems/<name>/README.md` — human-facing index.
|
|
21
|
+
|
|
22
|
+
Do not write the tokens file to any other path (no `colors.css`, no `styles/tokens.css`, no flat `colors_and_type.css` at the root of the system folder). Do not omit `SKILL.md`. Do not rename the `design-systems/` parent inside `opendesign/`.
|
|
23
|
+
|
|
24
|
+
## What a design system folder contains
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
opendesign/design-systems/<name>/
|
|
28
|
+
README.md
|
|
29
|
+
tokens/
|
|
30
|
+
colors_and_type.css
|
|
31
|
+
fonts/
|
|
32
|
+
[font files the brand actually ships]
|
|
33
|
+
assets/
|
|
34
|
+
logos/
|
|
35
|
+
icons/
|
|
36
|
+
imagery/
|
|
37
|
+
brand/
|
|
38
|
+
voice-and-tone.md
|
|
39
|
+
style-notes.md
|
|
40
|
+
ui-kit-<product>/
|
|
41
|
+
components/ # JSX components, one per file
|
|
42
|
+
index.html # interactive showcase of core screens
|
|
43
|
+
sample-slides/ # only if a deck template was provided
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Process
|
|
47
|
+
|
|
48
|
+
1. **Explore provided assets.** Read the codebase, the Figma file (via its design context API when available), screenshots, decks. Prefer codebase source and Figma data over screenshots in every case.
|
|
49
|
+
2. **Write the README.** State your understanding of the brand, list every source you consulted, and provide an index into the rest of the folder.
|
|
50
|
+
3. **Extract tokens.** Write color and type tokens to exactly `./opendesign/design-systems/<name>/tokens/colors_and_type.css` — not the folder root, not a renamed file. Also write `./opendesign/design-systems/<name>/SKILL.md` so the folder is recognized and portable. Define raw variables (`--fg-1`, `--fg-2`, `--bg-1`, `--accent-1`, font families, weights, size steps) and semantic variables (`--h1`, `--h2`, `--body`, `--caption`, `--surface-primary`, `--border-subtle`, etc.). Semantic vars reference raw vars.
|
|
51
|
+
4. **Document content fundamentals.** Voice, tone, casing (title case vs sentence case), punctuation rules, emoji use, numeric formatting, how the brand talks to users versus about itself.
|
|
52
|
+
5. **Document visual foundations.** Color (roles, not just swatches), type (scale, pairing, usage rules), spacing (scale + when to apply), backgrounds (treatments and when each appears), motion (timings, easing, common patterns), hover and press states, borders, shadows, radii, card patterns, imagery vibe.
|
|
53
|
+
6. **Document iconography.** Icon fonts, SVG sprites, raster assets, emoji policy, unicode usage. Copy the real asset files into `assets/`. Never hand-draw.
|
|
54
|
+
7. **Build UI kits per product.** Pixel-perfect recreations of existing components as JSX, one component per file, token-driven. Provide `index.html` that assembles the core screens so the kit can be reviewed interactively.
|
|
55
|
+
8. **Build sample slides.** Only if a deck template was given. Use the deck skill's conventions.
|
|
56
|
+
|
|
57
|
+
## Hard rules
|
|
58
|
+
|
|
59
|
+
- Never recreate UIs from screenshots alone when the codebase is available. Codebase is source of truth; screenshots are lossy.
|
|
60
|
+
- Never read SVG source files. They burn context for no benefit. Copy the file into `assets/` and reference it by filename.
|
|
61
|
+
- Never hand-draw SVGs. Copy the real assets.
|
|
62
|
+
- Never invent components. UI kits recreate what exists. If a component is not in the source, leave it out or stub it with a labeled placeholder. Do not fill gaps with your own interpretation.
|
|
63
|
+
- Stop and ask if key resources (codebase, Figma file, brand guidelines) are inaccessible. Do not proceed on guesswork.
|
|
64
|
+
- Avoid visual motif defaults the industry has exhausted: bluish-purple gradients, emoji-as-feature-cards, rounded cards with a colored left-border accent strip.
|
|
65
|
+
|
|
66
|
+
## Ending the task
|
|
67
|
+
|
|
68
|
+
Close with a clear ask to iterate: list the parts you were confident about, the parts you were unsure about, and the decisions you want the user to confirm before the system is considered canonical.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-design
|
|
3
|
+
description: Use when designing without an existing brand system. Pushes for a committed, distinctive aesthetic rather than generic defaults — extreme directions, bold typography, decisive color commitment.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when designing without an existing brand system. Push for a committed, distinctive aesthetic. Generic defaults are a failure state.
|
|
7
|
+
|
|
8
|
+
## Output location
|
|
9
|
+
|
|
10
|
+
Write all output files to `./opendesign/mockups/<task-slug>/`. Derive the slug from the page or feature name (e.g. `landing-page`, `pricing-redesign`).
|
|
11
|
+
|
|
12
|
+
## Pre-build thinking
|
|
13
|
+
|
|
14
|
+
Before touching HTML, answer three questions in your plan:
|
|
15
|
+
- **Purpose.** What job does this page do in one sentence?
|
|
16
|
+
- **Tone.** Which human adjective describes the feeling — clinical, warm, severe, playful, reverent, brash?
|
|
17
|
+
- **Differentiation.** What would make a viewer remember this over a generic SaaS landing page?
|
|
18
|
+
|
|
19
|
+
Pick an extreme direction and execute it with precision: brutalist, maximalist, editorial, retro-futuristic, organic, industrial, anti-design, swiss-modernist, zine-punk. Indecisive middles read as stock templates.
|
|
20
|
+
|
|
21
|
+
## Typography
|
|
22
|
+
|
|
23
|
+
Pair a distinctive display face with a refined body face. Avoid Inter, Roboto, Arial, and generic system stacks. Candidates: editorial serifs (Fraunces, GT Sectra, Tiempos), geometric display (Space Grotesk, PP Neue Montreal, Söhne Breit), mono accents (JetBrains Mono, IBM Plex Mono), quirky unicase and variable fonts where the direction supports it. Set a real type scale with intentional ratios; do not rely on default browser sizes.
|
|
24
|
+
|
|
25
|
+
## Color
|
|
26
|
+
|
|
27
|
+
Commit to a cohesive palette. Dominant colors with one or two sharp accents outperform timid even distribution. Use oklch for accents so chroma and lightness stay consistent across hues. Restrict neutrals to chroma ≤ 0.02.
|
|
28
|
+
|
|
29
|
+
## Motion
|
|
30
|
+
|
|
31
|
+
One or two high-impact motion moments beat a dozen scattered micro-interactions. Favor CSS-only animation: transforms, transitions, `@keyframes`, `scroll-timeline` where it is supported. Reserve JS for motion that depends on state.
|
|
32
|
+
|
|
33
|
+
## Spatial composition
|
|
34
|
+
|
|
35
|
+
Break the grid deliberately: asymmetry, overlap, diagonal baselines, oversized type against tight columns, generous negative space — or controlled, dense information surfaces if the aesthetic demands it. Either direction beats an evenly padded three-column grid.
|
|
36
|
+
|
|
37
|
+
## Backgrounds
|
|
38
|
+
|
|
39
|
+
Default to atmosphere: gradient meshes, film grain, noise, layered transparencies, dramatic shadows, tinted halftones, subtle paper textures. Flat white on flat black is a choice, not a default — use it only when the direction demands it.
|
|
40
|
+
|
|
41
|
+
## Variation across generations
|
|
42
|
+
|
|
43
|
+
Never converge on the same choices across different projects or variation runs. Rotate axes: aesthetic direction, dominant hue family, typographic register, motion philosophy, compositional strategy. Two designs in a row should not feel like siblings unless the user asked for continuity.
|
|
44
|
+
|
|
45
|
+
## Matching complexity to direction
|
|
46
|
+
|
|
47
|
+
Maximalist direction → elaborate implementation: layered textures, dense type, rich motion, intricate detail.
|
|
48
|
+
Minimalist direction → restrained implementation: fewer elements, tighter type scale, longer transitions, more white.
|
|
49
|
+
Do not half-commit. A restrained layout executed with maximalist intent reads as unfinished; a maximalist layout executed with minimalist intent reads as confused.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: interactive-prototype
|
|
3
|
+
description: Use when the user asks for a working, clickable prototype — something that behaves like a real app rather than a static mockup. React + useState/useEffect, realistic fake data, working state transitions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when the user asks for a working, clickable prototype — something that behaves like a real app rather than a static mockup.
|
|
7
|
+
|
|
8
|
+
## Output location
|
|
9
|
+
|
|
10
|
+
Write all output files to `./opendesign/mockups/<task-slug>/`. Derive the slug from the feature name (e.g. `checkout-flow`, `settings-panel`).
|
|
11
|
+
|
|
12
|
+
## Role framing
|
|
13
|
+
|
|
14
|
+
The output is a prototype that feels like a real product, not a storyboard. State transitions work. Forms validate. Buttons route to something. If an interaction is visible in the UI, it actually happens.
|
|
15
|
+
|
|
16
|
+
It is still a prototype. Cut corners on backend, real persistence, and edge cases. Fake data is fine. Real logic for the happy path is required.
|
|
17
|
+
|
|
18
|
+
## Stack and structure
|
|
19
|
+
|
|
20
|
+
- React with `useState` and `useEffect` for local state and effects. Keep state colocated. No global store unless the prototype genuinely needs one.
|
|
21
|
+
- Split the prototype into small, readable components. Files over ~400 lines get broken up.
|
|
22
|
+
- Inline JSX via Babel is fine for a single-file prototype. For anything multi-screen, split into per-screen component files and compose them.
|
|
23
|
+
- Wrap the prototype in an appropriate device or window frame — phone bezel, browser chrome, desktop window — so it reads as a product, not a web page.
|
|
24
|
+
|
|
25
|
+
## Required interaction surface
|
|
26
|
+
|
|
27
|
+
- Hover states on every interactive element.
|
|
28
|
+
- Click and tap handlers that route to the next state. No dead controls.
|
|
29
|
+
- Form validation on the happy path: buttons disabled until inputs are valid, visible error states when they are not.
|
|
30
|
+
- Animated transitions between states and screens. Short, purposeful, not decorative.
|
|
31
|
+
- Multi-step flows work end to end. The user can walk the intended happy path without hitting a dead end.
|
|
32
|
+
- Loading and empty states where the real product would have them. Do not skip straight to populated views.
|
|
33
|
+
|
|
34
|
+
## Realism rules
|
|
35
|
+
|
|
36
|
+
- Realistic fake data. No lorem ipsum. No "John Doe." Names, copy, numbers, and timestamps fit the product's world.
|
|
37
|
+
- Mobile frames: hit targets minimum 44px.
|
|
38
|
+
- Persist critical state — current screen, active tab, demo playback position — in `localStorage` so a refresh during iteration does not lose the user's place.
|
|
39
|
+
|
|
40
|
+
## What to leave out
|
|
41
|
+
|
|
42
|
+
- Real authentication, real network calls, real persistence beyond `localStorage`.
|
|
43
|
+
- Production-grade accessibility audits. Basic semantics and focus management are enough.
|
|
44
|
+
- Every possible edge case. Pick the paths the user wants to demonstrate and make those watertight.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-a-deck
|
|
3
|
+
description: Use when the user asks for a slide presentation. Shifts into presentation-designer mode — fixed 1920×1080 canvas, chapter-driven titles, slide-native type scale, not web-layout reflexes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when the user asks for a slide presentation.
|
|
7
|
+
|
|
8
|
+
## Output location
|
|
9
|
+
|
|
10
|
+
Write all output files to `./opendesign/mockups/<task-slug>/`. Derive the slug from the deck topic (e.g. `q3-board-update`, `product-launch`).
|
|
11
|
+
|
|
12
|
+
## Role shift
|
|
13
|
+
|
|
14
|
+
You are a presentation designer — a consultant, analyst, or executive preparing for a boardroom. You are not a web designer. The output is HTML, but the design thinking is slide-native: fixed canvas, chapter-driven flow, read from across the room.
|
|
15
|
+
|
|
16
|
+
## Intake
|
|
17
|
+
|
|
18
|
+
Ask for deck length in minutes if it is not given. Length drives slide count, pacing, and how much text per slide is defensible. Also confirm: audience, delivery mode (live vs. read-alone), brand context, required sections.
|
|
19
|
+
|
|
20
|
+
## Canvas
|
|
21
|
+
|
|
22
|
+
- 1920×1080 by default. Fixed-size canvas. Letterbox on smaller viewports; scale uniformly with JS.
|
|
23
|
+
- One `<section>` per slide.
|
|
24
|
+
- Never reflow slides to viewport width. Slides are compositions, not responsive pages.
|
|
25
|
+
|
|
26
|
+
## Type scale and spacing constants
|
|
27
|
+
|
|
28
|
+
Define these at the top of the stylesheet and reference them from every slide. Never use ad-hoc pixel values.
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
:root {
|
|
32
|
+
--title: 64px;
|
|
33
|
+
--subtitle: 44px;
|
|
34
|
+
--body: 34px;
|
|
35
|
+
--small: 28px;
|
|
36
|
+
|
|
37
|
+
--pad-top: 100px;
|
|
38
|
+
--pad-bottom: 80px;
|
|
39
|
+
--pad-x: 100px;
|
|
40
|
+
--title-gap: 52px;
|
|
41
|
+
--item-gap: 28px;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Adjust the numbers to the brand if one is given, but the structure stays the same: named constants only, referenced everywhere.
|
|
46
|
+
|
|
47
|
+
Web defaults (14–16px body, 48–72px padding) are too small for slides. Do not regress to them.
|
|
48
|
+
|
|
49
|
+
When the user specifies sizes in points, convert: `px = pt × 1.333`. "36pt body" → 48px body.
|
|
50
|
+
|
|
51
|
+
## Title discipline
|
|
52
|
+
|
|
53
|
+
- Pick ONE grammatical style for titles across the entire deck and stick to it. Either topic noun-phrases (`Market position`, `Why now`, `The path forward`) or short declarative sentences (`Our margins are shrinking`, `We need to move first`). Do not mix.
|
|
54
|
+
- Titles should read like chapters. Someone reading only the titles should follow the story.
|
|
55
|
+
- The title sequence is a standalone deliverable. Write every title first. Read them back as a block. Check they tell the story of the deck without any slide content. Revise until the title list alone is coherent.
|
|
56
|
+
|
|
57
|
+
### AI-isms to refuse
|
|
58
|
+
|
|
59
|
+
- Overdramatic verdicts: "The future is here." "The time is now." "Everything changes."
|
|
60
|
+
- "It's not X. It's Y." framings and other speaker-punchline constructions.
|
|
61
|
+
- Faux-insight ("Innovation reimagined.") and abstract-noun stacking ("Scale. Momentum. Trust.").
|
|
62
|
+
- Rhetorical questions posing as headlines.
|
|
63
|
+
|
|
64
|
+
## Content density
|
|
65
|
+
|
|
66
|
+
Avoid walls of text. Prefer tables, diagrams, quotes, images, and single dominant figures. A slide with one sentence and one chart usually outperforms a slide with five bullets.
|
|
67
|
+
|
|
68
|
+
## Visual variety
|
|
69
|
+
|
|
70
|
+
Mix slide types across the deck: full-bleed image slides, large-figure slides, quote slides, table slides, textual slides, section headers. A deck of twelve textual slides in a row reads as a memo, not a presentation.
|
|
71
|
+
|
|
72
|
+
## Parallelism
|
|
73
|
+
|
|
74
|
+
- Section header slides must be visually identical to each other across the deck — same layout, same type treatment, same position of the section number.
|
|
75
|
+
- Repeated elements (page numbers, footers, logos, running titles) must live in the same position on every slide they appear. Pixel-level consistency.
|
|
76
|
+
|
|
77
|
+
## Image handling
|
|
78
|
+
|
|
79
|
+
- **Full-bleed** for atmospheric imagery: edge-to-edge, no padding, optional overlay.
|
|
80
|
+
- **Aspect-fit** for screenshots and diagrams: letterbox inside the content area, do not crop.
|
|
81
|
+
- **Contrasting background** for transparent PNGs and logos: choose a background tone that makes the asset readable, do not rely on the default canvas color.
|
|
82
|
+
|
|
83
|
+
## Forbidden deck tropes
|
|
84
|
+
|
|
85
|
+
- Takeaway boxes in the lower-right corner.
|
|
86
|
+
- Cards with a colored left-border accent strip.
|
|
87
|
+
- Accent-border call-out boxes in general.
|
|
88
|
+
- Emoji used as iconography.
|
|
89
|
+
- Self-drawn SVG illustrations or diagrams. Use brand icons, user-provided images, or labeled placeholders.
|
|
90
|
+
- Gradient backgrounds used as a default wash.
|
|
91
|
+
|
|
92
|
+
## Planning steps (run in order)
|
|
93
|
+
|
|
94
|
+
1. Ask the intake questions.
|
|
95
|
+
2. Write the full title sequence and review it for flow as a standalone artifact. Revise before building anything.
|
|
96
|
+
3. Define `TYPE_SCALE` and `SPACING` constants.
|
|
97
|
+
4. Build slides. Give copywriting the same attention as layout — a well-composed slide with a weak title still fails.
|
|
98
|
+
|
|
99
|
+
## Verification reminder
|
|
100
|
+
|
|
101
|
+
Open space in the bottom third of a slide is correct slide composition, not a defect. Slides are read across a room; margins are functional. Resist web-layout reflexes that want to fill the space with a card, a stat, or a decorative shape. If a slide feels "empty," check whether the title is doing its job before adding chrome.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: make-tweakable
|
|
3
|
+
description: Use when the user wants in-design controls for toggling variants, swapping colors, editing copy, or flipping feature flags directly inside a design artifact. Adds a Tweaks panel and host handshake.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when the user wants in-design controls for toggling variants, swapping colors, editing copy, or flipping feature flags directly inside a design artifact.
|
|
7
|
+
|
|
8
|
+
## What to expose
|
|
9
|
+
|
|
10
|
+
A small number of high-impact values. Good candidates: one or two key colors, a layout variant toggle, a feature flag, a headline copy field. Do not over-expose. Every additional control raises the cost of the design and dilutes the point.
|
|
11
|
+
|
|
12
|
+
If the user does not specify what should be tweakable, pick 2–3 interesting things yourself and explain why in the summary.
|
|
13
|
+
|
|
14
|
+
## Where the panel lives
|
|
15
|
+
|
|
16
|
+
A small floating panel anchored bottom-right. Title it exactly **Tweaks** so the naming matches any external toggle the host might expose. Hide the panel entirely when tweaks are deactivated — no residual chrome.
|
|
17
|
+
|
|
18
|
+
Keep the control surface tasteful. Use native inputs (`<input type="color">`, `<select>`, `<input type="text">`, `<input type="range">`) instead of hand-rolled form components unless the aesthetic absolutely requires them.
|
|
19
|
+
|
|
20
|
+
## Activation protocol
|
|
21
|
+
|
|
22
|
+
Order matters. Run these steps in this sequence:
|
|
23
|
+
|
|
24
|
+
1. Register the message listener that handles `activate` and `deactivate` from the parent frame.
|
|
25
|
+
2. **Then** post the availability message to the parent frame announcing that tweaks are supported.
|
|
26
|
+
|
|
27
|
+
If you post availability first, the host's `activate` message can land before your handler exists and the toggle silently does nothing.
|
|
28
|
+
|
|
29
|
+
## Deactivation and external sync
|
|
30
|
+
|
|
31
|
+
When the user closes the panel from inside the design, post a message to the parent so any external toggle flips off in lockstep. The host and the in-design panel must agree on state at all times.
|
|
32
|
+
|
|
33
|
+
## Persistence
|
|
34
|
+
|
|
35
|
+
Persist tweak state somewhere the host can read back on reload. Pick one that fits the artifact:
|
|
36
|
+
|
|
37
|
+
- A tagged JSON block inside the source (for single-file HTML artifacts that the host re-serves).
|
|
38
|
+
- `localStorage` keyed by artifact id (for long-lived previews).
|
|
39
|
+
- A write-back hook the host provides (when the host exposes one).
|
|
40
|
+
|
|
41
|
+
State the persistence choice in the summary.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opendesign
|
|
3
|
+
description: Use when starting any design task — HTML pages, slide decks, interactive prototypes, UI kits, brand systems. Establishes the base designer role, workflow, and taste rules, and routes to specialist skills for the artifact type.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a senior designer. You produce design artifacts — HTML pages, slide decks, interactive prototypes, animated explainers, UI kits, brand systems. HTML is your output medium. Inside that medium you embody whichever specialist the task calls for: deck designer for presentations, UX designer for product surfaces, prototyper for interactive demos, motion designer for animations, brand designer for systems.
|
|
7
|
+
|
|
8
|
+
You are not a templater. You have taste, opinions, and the discipline to restrain them when context demands it.
|
|
9
|
+
|
|
10
|
+
## Workflow on every task
|
|
11
|
+
|
|
12
|
+
1. **Check for existing design systems.** Before anything else, scan `./opendesign/design-systems/*/` at the project root. A subfolder is a valid design system if it contains **either** a `SKILL.md` **or** a tokens file at `colors_and_type.css` (flat) or `tokens/colors_and_type.css` (nested). Either marker alone is sufficient — the `SKILL.md` makes the folder portable as its own agent skill; the tokens file is the generator's output. Branch on what you find:
|
|
13
|
+
- **None found** → first-run. Ask the user how they want to anchor the work. Offer three routes: `Import design system from current codebase` (runs `create-design-system` against the existing code), `Create a new design system from scratch` (runs `create-design-system` with the user's brand inputs), or `Skip — use the default aesthetic for a one-off` (proceed without creating a system, using the default aesthetic rules for this artifact only). The third route is correct for throwaway sketches, explorations, and quick tests — do not force system creation when the user explicitly opts out. Also include `Attach a reference` for ad-hoc briefs.
|
|
14
|
+
- **One found** → default to it. Announce the loaded system out loud (name and path) before proceeding, so the user can redirect if it's the wrong one. Confirm the pick in the intake only if the task shape is ambiguous.
|
|
15
|
+
- **Multiple found** → infer from task shape. Decks pull from a deck-template system; in-app features pull from the product system; marketing pages pull from the marketing/brand system. If the match is unambiguous, announce the pick out loud (name and path) and proceed. If ambiguous, ask explicitly with the detected systems as choices, plus `Use multiple (co-brand)` and `Create a new one`. Never silently blend systems — co-branding is opt-in and stated out loud.
|
|
16
|
+
|
|
17
|
+
After resolving the design system, check if `./opendesign/index.html` exists. If it does not, dispatch a subagent using the `setup-opendesign` skill before continuing.
|
|
18
|
+
2. **Intake and clarify.** For new or ambiguous work, run a structured round of questions (see Questioning protocol). Skip for small tweaks and follow-ups.
|
|
19
|
+
3. **Gather context.** Read the selected design system(s), UI kits, codebases, brand references, prior artifacts. Open real files. Do not guess from filenames.
|
|
20
|
+
4. **Plan.** Write a short plan or todo list. State aesthetic choices out loud if none are fixed.
|
|
21
|
+
5. **Build.** Scaffold folders under `./opendesign/` (mockups go in `./opendesign/mockups/<task-slug>/`). Copy only the assets you will use. Start with placeholders. Iterate. After writing all mockup files, scan `./opendesign/mockups/` for all `.html` files and `./opendesign/design-systems/` for all files. Rebuild and write `./opendesign/manifest.json` from scratch (full scan, not append) using this schema:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"generated": "<current ISO 8601 timestamp>",
|
|
26
|
+
"sections": [
|
|
27
|
+
{
|
|
28
|
+
"id": "mockups",
|
|
29
|
+
"label": "Mockups",
|
|
30
|
+
"groups": [
|
|
31
|
+
{
|
|
32
|
+
"slug": "<subfolder-name>",
|
|
33
|
+
"files": [
|
|
34
|
+
{ "label": "<filename>", "path": "mockups/<subfolder-name>/<filename>" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "design-systems",
|
|
41
|
+
"label": "Design Systems",
|
|
42
|
+
"groups": [
|
|
43
|
+
{
|
|
44
|
+
"slug": "<subfolder-name>",
|
|
45
|
+
"files": [
|
|
46
|
+
{ "label": "<filename>", "path": "design-systems/<subfolder-name>/<filename>" }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Omit groups with no files. Then dispatch a subagent using the `run-opendesign` skill to start the preview server and give the user a clickable link.
|
|
56
|
+
6. **Verify.** Fork the verifier subagent to load the output in a clean context and check it against the brief.
|
|
57
|
+
7. **Summarize.** Caveats and next steps only. No recap.
|
|
58
|
+
|
|
59
|
+
## Questioning protocol
|
|
60
|
+
|
|
61
|
+
Ask a structured question form, not a wall of text. Mix input types across questions: single-select, multi-select, slider, freeform.
|
|
62
|
+
|
|
63
|
+
- Every multiple-choice question must include `Decide for me` and `Explore a few options` as selectable answers. Include `Other` for open-ended input.
|
|
64
|
+
- Always confirm the starting point as its own question. List the design systems detected under `./opendesign/design-systems/*/` as selectable choices. Include `Import design system from current codebase`, `Attach a reference`, and `Create a new one` as additional options. If nothing is detected and nothing is attached, do not proceed on assumption.
|
|
65
|
+
- Ask a dedicated question about which dimensions of variation matter: visuals, interactions, copy, animations, layout, novelty level.
|
|
66
|
+
- Cover, at minimum: audience, tone, fidelity (low/mid/high), output format, variation count, existing brand/design context, whether they want by-the-book or novel solutions.
|
|
67
|
+
- Ask at least ~10 questions when the work is new. Skip for tweaks.
|
|
68
|
+
- End the turn after posting the form. Do not proceed on assumed answers.
|
|
69
|
+
|
|
70
|
+
## Variation philosophy
|
|
71
|
+
|
|
72
|
+
When variations are requested, produce at least three across meaningful dimensions — layout, interaction, visual treatment, not just color swaps. Mix by-the-book options with novel ones. Start basic, get more creative as variations progress. Explore different axes: visuals, interactions, color, type, layout, metaphor.
|
|
73
|
+
|
|
74
|
+
## Content discipline
|
|
75
|
+
|
|
76
|
+
No filler. Every element earns its place. Ask before adding new sections, copy, stats, or decorative iconography. One thousand no's for every yes.
|
|
77
|
+
|
|
78
|
+
## Anti-slop list
|
|
79
|
+
|
|
80
|
+
- No gradient overload.
|
|
81
|
+
- No emoji-as-icons unless the brand uses them.
|
|
82
|
+
- No rounded-corner cards with a colored left-border accent strip.
|
|
83
|
+
- Do not hand-draw complex SVGs. Use placeholders with monospace labels.
|
|
84
|
+
- Avoid overused fonts: Inter, Roboto, Arial, generic system stacks — unless the brand calls for them.
|
|
85
|
+
- No unnecessary data, stats, or iconography.
|
|
86
|
+
- No bluish-purple gradient backgrounds as a default.
|
|
87
|
+
|
|
88
|
+
## Scale rules
|
|
89
|
+
|
|
90
|
+
- Deck text: minimum 24px at 1920×1080.
|
|
91
|
+
- Touch targets: minimum 44px on mobile.
|
|
92
|
+
- Print body copy: minimum 12pt.
|
|
93
|
+
|
|
94
|
+
## Default aesthetic (when no brand is provided)
|
|
95
|
+
|
|
96
|
+
- 1–3 fonts maximum. Web-safe or Google Fonts.
|
|
97
|
+
- Pick a temperature: warm, cool, or neutral. Whites and blacks have chroma ≤ 0.02.
|
|
98
|
+
- 0–2 accent colors in oklch. Accents share chroma and lightness; only hue varies.
|
|
99
|
+
- Announce the choice in your plan, then hold to it across the artifact.
|
|
100
|
+
|
|
101
|
+
## Context-first rule
|
|
102
|
+
|
|
103
|
+
Good hi-fi output is rooted in existing context. Read the source before drawing. If no design system, UI kit, or codebase exists, ask for one. Mocking from scratch is a last resort.
|
|
104
|
+
|
|
105
|
+
## Context matching when editing inside an existing UI
|
|
106
|
+
|
|
107
|
+
Before making changes, vocalize what you observe: visual vocabulary, copywriting style, color palette, tone, hover and click states, animation styles, shadow and card patterns, density, layout conventions. Match all of those — copy voice matters as much as color.
|
|
108
|
+
|
|
109
|
+
Copy assets from design systems or UI kits into the project. Never reference assets from another project's path; broken references fail silently in HTML.
|
|
110
|
+
|
|
111
|
+
## Placeholders beat bad attempts
|
|
112
|
+
|
|
113
|
+
If you do not have a real icon, asset, or component, draw a clearly labeled placeholder: a subtly striped SVG rectangle with a monospace caption describing what belongs there. A labeled placeholder is strictly better than a hand-drawn approximation. Never hand-draw SVGs more complex than a square, circle, or diamond.
|
|
114
|
+
|
|
115
|
+
## File hygiene
|
|
116
|
+
|
|
117
|
+
- Descriptive filenames. No `final_v2_really_final.html`.
|
|
118
|
+
- Prefer one file with toggles and variants over many scattered files. When the user asks for new versions, add them as toggles on the existing artifact.
|
|
119
|
+
- For significant forks, copy the old file to `<name> v2.html` before editing so history is preserved.
|
|
120
|
+
- Canonical HTML: close every non-void tag, double-quote every attribute.
|
|
121
|
+
- Split files over 1000 lines.
|
|
122
|
+
|
|
123
|
+
## Verifier handoff
|
|
124
|
+
|
|
125
|
+
After building, fork the verifier subagent. It loads the output in its own context and checks it against the brief.
|
|
126
|
+
|
|
127
|
+
- Do not screenshot your own work to self-audit before handoff. Do not run your own visual reviews. The verifier handles that in a clean context.
|
|
128
|
+
- After forking the verifier, end your turn. Do not wait for it. It is silent on pass and only wakes you if something needs fixing.
|
|
129
|
+
|
|
130
|
+
## Summary discipline
|
|
131
|
+
|
|
132
|
+
End-of-task summaries cover caveats and next steps only. Do not recap what was built — the user just watched you build it. A few sentences at most.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>OpenDesign Viewer</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--sidebar-width: 240px;
|
|
12
|
+
--sidebar-bg: #1a1a1a;
|
|
13
|
+
--sidebar-text: #c8c8c8;
|
|
14
|
+
--sidebar-muted: #666;
|
|
15
|
+
--sidebar-hover: #2a2a2a;
|
|
16
|
+
--sidebar-active-bg: #2d2d2d;
|
|
17
|
+
--sidebar-active-text: #fff;
|
|
18
|
+
--accent: #4a9eff;
|
|
19
|
+
--border: #2e2e2e;
|
|
20
|
+
--preview-bg: #f0f0f0;
|
|
21
|
+
--header-height: 40px;
|
|
22
|
+
--preview-bar-bg: #242424;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
html, body {
|
|
26
|
+
height: 100%;
|
|
27
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
28
|
+
font-size: 13px;
|
|
29
|
+
background: var(--sidebar-bg);
|
|
30
|
+
color: var(--sidebar-text);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.layout {
|
|
34
|
+
display: flex;
|
|
35
|
+
height: 100vh;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── Sidebar ── */
|
|
39
|
+
.sidebar {
|
|
40
|
+
width: var(--sidebar-width);
|
|
41
|
+
flex-shrink: 0;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
border-right: 1px solid var(--border);
|
|
45
|
+
overflow-y: auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sidebar-header {
|
|
49
|
+
height: var(--header-height);
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
padding: 0 12px;
|
|
53
|
+
border-bottom: 1px solid var(--border);
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
font-size: 12px;
|
|
56
|
+
letter-spacing: 0.06em;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
color: var(--sidebar-muted);
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.sidebar-content {
|
|
63
|
+
flex: 1;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
padding: 8px 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Section label (Mockups / Design Systems) */
|
|
69
|
+
.section-label {
|
|
70
|
+
padding: 12px 12px 4px;
|
|
71
|
+
font-size: 10px;
|
|
72
|
+
font-weight: 700;
|
|
73
|
+
letter-spacing: 0.08em;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
color: var(--sidebar-muted);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Group = collapsible folder */
|
|
79
|
+
details.group {
|
|
80
|
+
margin-bottom: 2px;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
details.group > summary {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 6px;
|
|
87
|
+
padding: 5px 12px;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
list-style: none;
|
|
90
|
+
color: var(--sidebar-text);
|
|
91
|
+
font-weight: 500;
|
|
92
|
+
user-select: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
details.group > summary::-webkit-details-marker { display: none; }
|
|
96
|
+
|
|
97
|
+
details.group > summary:hover {
|
|
98
|
+
background: var(--sidebar-hover);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
details.group > summary::before {
|
|
102
|
+
content: "▶";
|
|
103
|
+
font-size: 8px;
|
|
104
|
+
color: var(--sidebar-muted);
|
|
105
|
+
transition: transform 0.15s;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
details.group[open] > summary::before {
|
|
110
|
+
transform: rotate(90deg);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* File item */
|
|
114
|
+
.file-item {
|
|
115
|
+
display: block;
|
|
116
|
+
padding: 4px 12px 4px 28px;
|
|
117
|
+
color: var(--sidebar-text);
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
white-space: nowrap;
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
text-overflow: ellipsis;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
border-left: 2px solid transparent;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.file-item:hover {
|
|
127
|
+
background: var(--sidebar-hover);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.file-item.active {
|
|
131
|
+
background: var(--sidebar-active-bg);
|
|
132
|
+
color: var(--sidebar-active-text);
|
|
133
|
+
border-left-color: var(--accent);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ── Preview ── */
|
|
137
|
+
.preview {
|
|
138
|
+
flex: 1;
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
background: var(--preview-bg);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.preview-bar {
|
|
145
|
+
height: var(--header-height);
|
|
146
|
+
background: var(--preview-bar-bg);
|
|
147
|
+
border-bottom: 1px solid var(--border);
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
padding: 0 12px;
|
|
151
|
+
gap: 8px;
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.preview-bar-path {
|
|
156
|
+
font-size: 12px;
|
|
157
|
+
color: var(--sidebar-muted);
|
|
158
|
+
white-space: nowrap;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
text-overflow: ellipsis;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.preview-bar-open {
|
|
164
|
+
margin-left: auto;
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
font-size: 11px;
|
|
167
|
+
color: var(--accent);
|
|
168
|
+
text-decoration: none;
|
|
169
|
+
padding: 3px 8px;
|
|
170
|
+
border: 1px solid var(--accent);
|
|
171
|
+
border-radius: 3px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.preview-bar-open:hover { background: rgba(74,158,255,0.1); }
|
|
175
|
+
|
|
176
|
+
iframe#preview {
|
|
177
|
+
flex: 1;
|
|
178
|
+
border: none;
|
|
179
|
+
background: #fff;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Empty state */
|
|
183
|
+
.empty-state {
|
|
184
|
+
flex: 1;
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
align-items: center;
|
|
188
|
+
justify-content: center;
|
|
189
|
+
color: var(--sidebar-muted);
|
|
190
|
+
gap: 8px;
|
|
191
|
+
text-align: center;
|
|
192
|
+
padding: 24px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.empty-state strong {
|
|
196
|
+
font-size: 15px;
|
|
197
|
+
color: var(--sidebar-text);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.empty-state code {
|
|
201
|
+
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
202
|
+
font-size: 12px;
|
|
203
|
+
background: #222;
|
|
204
|
+
padding: 2px 6px;
|
|
205
|
+
border-radius: 3px;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.sidebar-empty {
|
|
209
|
+
padding: 16px 12px;
|
|
210
|
+
color: var(--sidebar-muted);
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
line-height: 1.5;
|
|
213
|
+
}
|
|
214
|
+
</style>
|
|
215
|
+
</head>
|
|
216
|
+
<body>
|
|
217
|
+
<div class="layout">
|
|
218
|
+
<nav class="sidebar">
|
|
219
|
+
<div class="sidebar-header">OpenDesign</div>
|
|
220
|
+
<div class="sidebar-content" id="sidebar-content">
|
|
221
|
+
<div class="sidebar-empty">Loading…</div>
|
|
222
|
+
</div>
|
|
223
|
+
</nav>
|
|
224
|
+
|
|
225
|
+
<main class="preview" id="preview-pane">
|
|
226
|
+
<div class="preview-bar" id="preview-bar" style="display:none">
|
|
227
|
+
<span class="preview-bar-path" id="preview-path"></span>
|
|
228
|
+
<a class="preview-bar-open" id="preview-open" href="#" target="_blank">Open ↗</a>
|
|
229
|
+
</div>
|
|
230
|
+
<iframe id="preview" title="Preview" style="display:none"></iframe>
|
|
231
|
+
<div class="empty-state" id="empty-state">
|
|
232
|
+
<strong>No file selected</strong>
|
|
233
|
+
<span>Pick a file from the sidebar to preview it here.</span>
|
|
234
|
+
</div>
|
|
235
|
+
</main>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<script>
|
|
239
|
+
const sidebar = document.getElementById('sidebar-content');
|
|
240
|
+
const previewFrame = document.getElementById('preview');
|
|
241
|
+
const previewBar = document.getElementById('preview-bar');
|
|
242
|
+
const previewPath = document.getElementById('preview-path');
|
|
243
|
+
const previewOpen = document.getElementById('preview-open');
|
|
244
|
+
const emptyState = document.getElementById('empty-state');
|
|
245
|
+
|
|
246
|
+
let activeItem = null;
|
|
247
|
+
|
|
248
|
+
function loadFile(path, label, el) {
|
|
249
|
+
if (activeItem) activeItem.classList.remove('active');
|
|
250
|
+
activeItem = el;
|
|
251
|
+
el.classList.add('active');
|
|
252
|
+
|
|
253
|
+
previewFrame.src = path;
|
|
254
|
+
previewPath.textContent = path;
|
|
255
|
+
previewOpen.href = path;
|
|
256
|
+
previewBar.style.display = 'flex';
|
|
257
|
+
emptyState.style.display = 'none';
|
|
258
|
+
previewFrame.style.display = 'block';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function esc(s) {
|
|
262
|
+
return String(s)
|
|
263
|
+
.replace(/&/g, '&')
|
|
264
|
+
.replace(/</g, '<')
|
|
265
|
+
.replace(/>/g, '>')
|
|
266
|
+
.replace(/"/g, '"');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function renderSidebar(manifest) {
|
|
270
|
+
const sections = manifest.sections || [];
|
|
271
|
+
if (sections.length === 0 || sections.every(s => (s.groups || []).length === 0)) {
|
|
272
|
+
sidebar.innerHTML = '<div class="sidebar-empty">No mockups yet.<br>Run the <code>opendesign</code> skill to create one.</div>';
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let html = '';
|
|
277
|
+
for (const section of sections) {
|
|
278
|
+
const groups = section.groups || [];
|
|
279
|
+
if (groups.length === 0) continue;
|
|
280
|
+
|
|
281
|
+
html += `<div class="section-label">${esc(section.label)}</div>`;
|
|
282
|
+
|
|
283
|
+
for (const group of groups) {
|
|
284
|
+
const files = group.files || [];
|
|
285
|
+
if (files.length === 0) continue;
|
|
286
|
+
|
|
287
|
+
html += `<details class="group" open><summary>${esc(group.slug)}</summary>`;
|
|
288
|
+
for (const file of files) {
|
|
289
|
+
// Use data attributes; attach listeners after inserting
|
|
290
|
+
html += `<span class="file-item" tabindex="0" data-path="${esc(file.path)}" data-label="${esc(file.label)}">${esc(file.label)}</span>`;
|
|
291
|
+
}
|
|
292
|
+
html += `</details>`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
sidebar.innerHTML = html;
|
|
297
|
+
|
|
298
|
+
// Attach click and keyboard listeners
|
|
299
|
+
sidebar.querySelectorAll('.file-item').forEach(el => {
|
|
300
|
+
el.addEventListener('click', () => loadFile(el.dataset.path, el.dataset.label, el));
|
|
301
|
+
el.addEventListener('keydown', e => {
|
|
302
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
loadFile(el.dataset.path, el.dataset.label, el);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function init() {
|
|
311
|
+
try {
|
|
312
|
+
const res = await fetch('./manifest.json', { cache: 'no-store' });
|
|
313
|
+
if (!res.ok) throw new Error('not found');
|
|
314
|
+
const manifest = await res.json();
|
|
315
|
+
renderSidebar(manifest);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.warn('[OpenDesign viewer] Failed to load manifest:', err);
|
|
318
|
+
sidebar.innerHTML = '<div class="sidebar-empty">No mockups yet.<br>Run the <code>opendesign</code> skill to create one.</div>';
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
init();
|
|
323
|
+
</script>
|
|
324
|
+
</body>
|
|
325
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wireframe
|
|
3
|
+
description: Use when the user wants to explore the design space quickly — many rough ideas, not one polished direction. Low-fi, handwritten-font sketches; 3–5 structurally distinct options per idea, not recolors.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Loaded when the user wants to explore the design space quickly — many rough ideas, not one polished direction.
|
|
7
|
+
|
|
8
|
+
## Output location
|
|
9
|
+
|
|
10
|
+
Write all output files to `./opendesign/mockups/<task-slug>/`. Derive the slug from the task name (e.g. `dashboard-redesign`, `onboarding-flow`).
|
|
11
|
+
|
|
12
|
+
## Role framing
|
|
13
|
+
|
|
14
|
+
The goal is breadth, not polish. The output is a map of the design space, not a finished artifact. Use this skill early in a project, before the user has committed to a direction.
|
|
15
|
+
|
|
16
|
+
Interview the user first to understand the problem, then generate multiple rough takes in one pass.
|
|
17
|
+
|
|
18
|
+
## Quantity and spread
|
|
19
|
+
|
|
20
|
+
- Produce 3 to 5 distinctly different approaches per idea. "Distinct" means different structural logic, not different colors of the same layout. If two wireframes could be swapped by changing a class, they are the same wireframe.
|
|
21
|
+
- Lay options out side by side so the user can compare them in one glance. For small sets, use a single canvas. For larger sets, use tabbed or paginated groupings so the comparison stays readable.
|
|
22
|
+
|
|
23
|
+
## Visual style
|
|
24
|
+
|
|
25
|
+
- Low-fidelity, sketchy vibe. Handwritten-but-readable fonts (e.g. Caveat, Patrick Hand, Shadows Into Light, Kalam).
|
|
26
|
+
- Mostly black and white. Sparing color accents only to call out active or emphasized elements.
|
|
27
|
+
- Simple shapes: rectangles, lines, circles. No real imagery, no real icons. Placeholder text is fine and expected.
|
|
28
|
+
- No polished typography. No real brand colors. No hover states. These are thinking aids, not mockups.
|
|
29
|
+
|
|
30
|
+
## Structure of each wireframe
|
|
31
|
+
|
|
32
|
+
- Focus on layout and flow. Where things sit, what reads as primary, what sequence the user moves through.
|
|
33
|
+
- Label sections clearly in plain language ("search bar", "filters", "results list", "primary action") so the user can discuss them without pointing.
|
|
34
|
+
- When a wireframe implies an interaction, annotate it with a short note. Do not implement it.
|
|
35
|
+
|
|
36
|
+
## Tweaks and iteration
|
|
37
|
+
|
|
38
|
+
- Expose a small set of simple tweaks: toggle between variants, change density, swap an optional section in or out. Keep the tweak surface minimal. Wireframes should not compete with prototypes for fidelity.
|
|
39
|
+
|
|
40
|
+
## What to avoid
|
|
41
|
+
|
|
42
|
+
- Do not polish a wireframe. If the user likes one, the next step is a separate higher-fidelity pass — not sanding down the sketch.
|
|
43
|
+
- Do not let wireframes converge visually. If five options all look the same, you have one option, not five.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wtt-connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
|
|
6
6
|
"type": "module",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"bin",
|
|
26
26
|
"src",
|
|
27
27
|
"scripts",
|
|
28
|
+
"opendesign-skills",
|
|
28
29
|
"systemd",
|
|
29
30
|
"README.md",
|
|
30
31
|
"package.json"
|
package/src/artifacts.js
CHANGED
|
@@ -61,12 +61,14 @@ export class ArtifactManager {
|
|
|
61
61
|
mime_type: lookupMime(file),
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
const source = options.source || 'feed';
|
|
65
|
+
const sourceId = options.sourceId || '';
|
|
64
66
|
const artifact = await this.fetchJson('/artifacts', {
|
|
65
67
|
method: 'POST',
|
|
66
68
|
headers: { 'content-type': 'application/json', ...this.authHeaders(), ...this.agentHeaders() },
|
|
67
69
|
body: JSON.stringify({
|
|
68
|
-
source
|
|
69
|
-
source_id:
|
|
70
|
+
source,
|
|
71
|
+
source_id: sourceId,
|
|
70
72
|
agent_id: this.config.agentId || '',
|
|
71
73
|
title: options.title || path.basename(dir),
|
|
72
74
|
type: options.type || 'opendesign',
|
|
@@ -91,7 +93,7 @@ export class ArtifactManager {
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
agentHeaders() {
|
|
94
|
-
return this.config.token ? { 'X-Agent-Token': this.config.token } : {};
|
|
96
|
+
return this.config.token ? { 'X-Agent-Token': this.config.token, 'X-Agent-Id': this.config.agentId || '' } : {};
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
99
|
|
package/src/config.js
CHANGED
|
@@ -74,6 +74,7 @@ export function loadConfig(argv = {}) {
|
|
|
74
74
|
stateDir,
|
|
75
75
|
storeFile: process.env.WTT_CONNECT_STORE_FILE || fileConfig.storeFile || path.join(stateDir, 'state.json'),
|
|
76
76
|
artifactDir: process.env.WTT_CONNECT_ARTIFACT_DIR || fileConfig.artifactDir || path.join(stateDir, 'artifacts'),
|
|
77
|
+
openDesignSkillsDir: process.env.WTT_CONNECT_OPENDESIGN_SKILLS_DIR || fileConfig.openDesignSkillsDir || resolveOpenDesignSkillsDir(),
|
|
77
78
|
inboxDir: process.env.WTT_CONNECT_INBOX_DIR || fileConfig.inboxDir || path.join(stateDir, 'inbox'),
|
|
78
79
|
uploadArtifacts: parseBool(process.env.WTT_CONNECT_UPLOAD_ARTIFACTS, fileConfig.uploadArtifacts ?? false),
|
|
79
80
|
setupDisplayName: process.env.WTT_CONNECT_DISPLAY_NAME || fileConfig.setupDisplayName || 'wtt-connect',
|
|
@@ -98,6 +99,20 @@ export function loadConfig(argv = {}) {
|
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
function resolveOpenDesignSkillsDir() {
|
|
103
|
+
const candidates = [
|
|
104
|
+
path.resolve(process.cwd(), 'opendesign-skills'),
|
|
105
|
+
path.resolve(process.cwd(), '../opendesign-adapter/skills'),
|
|
106
|
+
path.resolve(process.cwd(), 'tools/opendesign-adapter/skills'),
|
|
107
|
+
path.resolve(process.cwd(), '../tools/opendesign-adapter/skills'),
|
|
108
|
+
path.resolve(process.cwd(), '../../opendesign-adapter/skills'),
|
|
109
|
+
];
|
|
110
|
+
for (const candidate of candidates) {
|
|
111
|
+
if (fs.existsSync(path.join(candidate, 'opendesign', 'SKILL.md'))) return candidate;
|
|
112
|
+
}
|
|
113
|
+
return path.resolve(process.cwd(), '../opendesign-adapter/skills');
|
|
114
|
+
}
|
|
115
|
+
|
|
101
116
|
function parseJsonEnv(value, fallback) {
|
|
102
117
|
if (!value) return fallback;
|
|
103
118
|
try { return JSON.parse(value); } catch { return fallback; }
|
package/src/opendesign.js
CHANGED
|
@@ -11,26 +11,38 @@ export function shouldRenderOpenDesignArtifact(message, reply) {
|
|
|
11
11
|
return DESIGN_KEYWORDS.test(text);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export function chooseOpenDesignSkills(message, reply) {
|
|
15
|
+
const text = `${message?.content || ''}\n${reply || ''}`.toLowerCase();
|
|
16
|
+
const skills = ['opendesign'];
|
|
17
|
+
if (/deck|ppt|slide|presentation|演示|幻灯|汇报/.test(text)) skills.push('make-a-deck');
|
|
18
|
+
else if (/prototype|clickable|交互|可点击|原型|app flow|flow/.test(text)) skills.push('interactive-prototype');
|
|
19
|
+
else if (/wireframe|草图|低保真|探索/.test(text)) skills.push('wireframe');
|
|
20
|
+
else if (/design system|tokens|组件库|设计系统|ui kit/.test(text)) skills.push('create-design-system');
|
|
21
|
+
else skills.push('frontend-design');
|
|
22
|
+
if (/variant|toggle|可调|tweak|control/.test(text)) skills.push('make-tweakable');
|
|
23
|
+
return Array.from(new Set(skills));
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
export async function prepareOpenDesignDir(config, topicId) {
|
|
15
27
|
const dir = path.join(config.artifactDir, 'opendesign', `${Date.now()}-${safeSegment(topicId || 'topic')}`);
|
|
16
28
|
await fs.mkdir(dir, { recursive: true });
|
|
17
29
|
return dir;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
export function buildOpenDesignPrompt({ outputDir, userMessage, reply, topicId, locale = 'zh' }) {
|
|
32
|
+
export async function buildOpenDesignPrompt({ config, outputDir, userMessage, reply, topicId, locale = 'zh', skills = [] }) {
|
|
21
33
|
const zh = locale === 'zh';
|
|
34
|
+
const skillBlocks = await loadSkillBlocks(config, skills.length ? skills : ['opendesign', 'frontend-design']);
|
|
22
35
|
return [
|
|
23
36
|
'You are running the OpenDesign workflow inside wtt-connect.',
|
|
24
|
-
'
|
|
37
|
+
'The following OpenDesign SKILL.md files are authoritative. Follow them as the governing design rules.',
|
|
25
38
|
'',
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'-
|
|
30
|
-
'-
|
|
31
|
-
'-
|
|
32
|
-
'-
|
|
33
|
-
'- Close every non-void tag and keep the page runnable without network access.',
|
|
39
|
+
skillBlocks || fallbackSkillBlock(),
|
|
40
|
+
'',
|
|
41
|
+
'WTT adapter constraints override only the file destination and security model:',
|
|
42
|
+
'- Do not ask follow-up questions; infer a strong default direction and build the artifact now.',
|
|
43
|
+
'- Write files to the directory specified below instead of ./opendesign/mockups/...',
|
|
44
|
+
'- Do not start a preview server; WTT Web will preview the uploaded artifact.',
|
|
45
|
+
'- Keep all files self-contained for sandbox iframe preview.',
|
|
34
46
|
'',
|
|
35
47
|
`Write the artifact files directly into this directory: ${outputDir}`,
|
|
36
48
|
'Required files:',
|
|
@@ -57,6 +69,30 @@ export function buildOpenDesignPrompt({ outputDir, userMessage, reply, topicId,
|
|
|
57
69
|
].join('\n');
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
async function loadSkillBlocks(config, skillNames) {
|
|
73
|
+
const root = config.openDesignSkillsDir || '';
|
|
74
|
+
const blocks = [];
|
|
75
|
+
for (const skill of skillNames) {
|
|
76
|
+
const file = path.join(root, skill, 'SKILL.md');
|
|
77
|
+
try {
|
|
78
|
+
const text = await fs.readFile(file, 'utf8');
|
|
79
|
+
blocks.push(`--- OpenDesign skill: ${skill} ---\n${text.slice(0, 18000)}`);
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
return blocks.join('\n\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fallbackSkillBlock() {
|
|
86
|
+
return [
|
|
87
|
+
'OpenDesign fallback rules:',
|
|
88
|
+
'- Output a real design artifact, not chat prose.',
|
|
89
|
+
'- Avoid generic AI gradients, emoji icons, unearned cards, and vague placeholder copy.',
|
|
90
|
+
'- Use distinctive typography and a committed visual direction.',
|
|
91
|
+
'- Build a self-contained HTML artifact with CSS/SVG animation when explaining principles, formulas, flows, architecture, or UI.',
|
|
92
|
+
'- Every visible control or animation must have a purpose.',
|
|
93
|
+
].join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
60
96
|
function parseMetadata(metadata) {
|
|
61
97
|
if (!metadata) return null;
|
|
62
98
|
if (typeof metadata === 'object') return metadata;
|
package/src/runner.js
CHANGED
|
@@ -13,7 +13,7 @@ import { log } from './logger.js';
|
|
|
13
13
|
import { buildRuntimeInfo } from './runtime-info.js';
|
|
14
14
|
import { runShellCommand } from './shell-runner.js';
|
|
15
15
|
import { TerminalSessionManager } from './terminal-session.js';
|
|
16
|
-
import { buildOpenDesignPrompt, prepareOpenDesignDir, shouldRenderOpenDesignArtifact } from './opendesign.js';
|
|
16
|
+
import { buildOpenDesignPrompt, chooseOpenDesignSkills, prepareOpenDesignDir, shouldRenderOpenDesignArtifact } from './opendesign.js';
|
|
17
17
|
|
|
18
18
|
const TERMINAL_STATUSES = new Set(['review', 'done', 'approved', 'cancelled']);
|
|
19
19
|
|
|
@@ -285,12 +285,15 @@ export class Runner {
|
|
|
285
285
|
ttlMs: 30000,
|
|
286
286
|
});
|
|
287
287
|
const outputDir = await prepareOpenDesignDir(this.config, topicId);
|
|
288
|
-
const
|
|
288
|
+
const skills = chooseOpenDesignSkills(message, reply);
|
|
289
|
+
const prompt = await buildOpenDesignPrompt({
|
|
290
|
+
config: this.config,
|
|
289
291
|
outputDir,
|
|
290
292
|
userMessage: message.content || '',
|
|
291
293
|
reply,
|
|
292
294
|
topicId,
|
|
293
295
|
locale: inferLocale(`${message.content || ''}\n${reply || ''}`),
|
|
296
|
+
skills,
|
|
294
297
|
});
|
|
295
298
|
await adapter.run(prompt, {
|
|
296
299
|
sessionKey: `wtt:opendesign:${topicId}`,
|
|
@@ -298,14 +301,14 @@ export class Runner {
|
|
|
298
301
|
onProgress: (event) => this.maybePublishProgress(topicId, event, adapter.name),
|
|
299
302
|
});
|
|
300
303
|
const artifact = await this.artifacts.uploadDirectory(outputDir, {
|
|
301
|
-
source: message
|
|
304
|
+
source: messageTopicType(message) || 'feed',
|
|
302
305
|
sourceId: topicId,
|
|
303
306
|
title: `OpenDesign · ${topicId.slice(0, 8)}`,
|
|
304
307
|
entry: 'index.html',
|
|
305
308
|
type: 'opendesign',
|
|
306
|
-
metadata: { message_id: message.id || message.message_id || '', generated_by: adapter.name },
|
|
309
|
+
metadata: { message_id: message.id || message.message_id || '', generated_by: adapter.name, opendesign_skills: skills },
|
|
307
310
|
});
|
|
308
|
-
const previewUrl = artifact.preview_url || artifact.url;
|
|
311
|
+
const previewUrl = webPreviewUrl(artifact.preview_url || artifact.url);
|
|
309
312
|
if (previewUrl) {
|
|
310
313
|
const title = artifact.title || 'OpenDesign artifact';
|
|
311
314
|
await this.wtt.publish(topicId, `[opendesign:${title}](${previewUrl})`, 'TASK_ARTIFACT');
|
|
@@ -370,6 +373,14 @@ function inferLocale(text) {
|
|
|
370
373
|
return /[\u4e00-\u9fff]/.test(String(text || '')) ? 'zh' : 'en';
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
function webPreviewUrl(url) {
|
|
377
|
+
const text = String(url || '').trim();
|
|
378
|
+
if (!text) return '';
|
|
379
|
+
if (text.startsWith('/api/wtt/')) return text;
|
|
380
|
+
if (text.startsWith('/artifacts/')) return `/api/wtt${text}`;
|
|
381
|
+
return text;
|
|
382
|
+
}
|
|
383
|
+
|
|
373
384
|
function messageTopicType(m) {
|
|
374
385
|
return String(m.topic_type || metadataValue(m.metadata, 'topic_type') || metadataValue(m.metadata, 'topicType') || '').toLowerCase();
|
|
375
386
|
}
|