@ammduncan/easel 0.2.23 → 0.2.25
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/CHANGELOG.md +14 -0
- package/dist/client/viewer.js +67 -29
- package/dist/http-server.js +10 -0
- package/dist/mcp.js +3 -1
- package/package.json +1 -1
- package/skills/using-easel/SKILL.md +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 0.2.25 — 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Docs
|
|
8
|
+
- **Documented the `.window` primitive and the "build mockups fluid" rule.** Added the `.window` / `.window.desktop` chrome to the skill's helpers section (it shipped in 0.2.24 with CSS but wasn't written up), and a guidance rule: lay desktop mockups out with flex/`%`/`fr` widths, not hardcoded `width: 1440px` columns. 1440 is a *max*, not a target — a fluid mockup reflows to fit when the viewer's window is narrowed, so there's no horizontal scroll, nothing clipped, and PNG/PDF exports stay complete. (Answers "should desktop mockups scroll horizontally when squeezed?" — no; build fluid so they reflow.) Also corrected the now-stale `.full-bleed` description to match 0.2.24 (fills the content column from the shared left edge, doesn't bleed to the card's physical edge). Skill + inline `push` tool description.
|
|
9
|
+
|
|
10
|
+
## 0.2.24 — 2026-05-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`.window` primitive — skeuomorphic macOS window chrome for mockups.** `<div class="window" data-title="App name">…</div>` draws a 40px title bar with the three traffic-light dots (red/yellow/green) and a centred title from `data-title`; content sits below. Add the `desktop` class (`class="window desktop"`) for the 1440×900 (16:10) standard design canvas via `min-height: 900px` — so a full desktop-screen mockup looks like a real window with viewport breathing room, while a dialog/component in the same chrome (no `desktop`) stays content-sized. Pairs with `.full-bleed` to fill the content column.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Reading column is now left-aligned with a shared left edge, instead of per-element centred.** 0.2.22 centred each prose element independently, which produced a ragged left edge when elements had different `max-width`s (e.g. a deck capped narrower than the heading). Reverted to left-aligned prose; `.full-bleed` now fills the content column from the *same* left edge (dropped the viewport-breakout/centring) — so prose and mockups share one left margin down the card, and neither touches the card edge (the body padding stays as a gutter).
|
|
17
|
+
- **Client JS/CSS now sent with `Cache-Control: no-cache`** so a normal reload revalidates and picks up updates, instead of serving a stale cached `viewer.js` that required a hard reload (⌘⇧R). Takes effect after the MCP server restarts.
|
|
18
|
+
|
|
5
19
|
## 0.2.23 — 2026-05-23
|
|
6
20
|
|
|
7
21
|
### Changed
|
package/dist/client/viewer.js
CHANGED
|
@@ -692,46 +692,84 @@ body {
|
|
|
692
692
|
@media (min-width: 2000px) {
|
|
693
693
|
body { max-width: 1600px; }
|
|
694
694
|
}
|
|
695
|
-
/* Constrain prose to a comfortable reading length
|
|
696
|
-
(cards, grids,
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
.
|
|
695
|
+
/* Constrain prose to a comfortable reading length and LEFT-ALIGN it to the
|
|
696
|
+
content column's left edge. Visual blocks (cards, grids, mockups, .full-bleed)
|
|
697
|
+
fill the wider content column from the SAME left edge — so prose and mockups
|
|
698
|
+
share one left margin down the card. Match prose both as direct body children
|
|
699
|
+
AND one level deep through a .wrap container (the skill recommends wrapping
|
|
700
|
+
content in div.wrap; without the .wrap branch the cap silently misses). */
|
|
701
701
|
body > p, body > .deck, body > .lede, body > ul, body > ol, body > blockquote,
|
|
702
702
|
body > h1, body > h2, body > h3, body > h4,
|
|
703
703
|
body > .wrap > p, body > .wrap > .deck, body > .wrap > .lede,
|
|
704
704
|
body > .wrap > ul, body > .wrap > ol, body > .wrap > blockquote,
|
|
705
705
|
body > .wrap > h1, body > .wrap > h2, body > .wrap > h3, body > .wrap > h4 {
|
|
706
706
|
max-width: 880px;
|
|
707
|
-
/* Centre the reading column in the card so it stays balanced whether or not
|
|
708
|
-
a .full-bleed sibling has widened the body. Without auto margins the
|
|
709
|
-
capped prose sits flush-left with a large empty right gutter next to any
|
|
710
|
-
full-bleed block. !important is required because authors routinely set an
|
|
711
|
-
inline margin shorthand like 0 0 16px on prose — that sets the left/right
|
|
712
|
-
margins to 0 and would otherwise win over the stylesheet, leaving that one
|
|
713
|
-
element flush-left while its siblings centre (inconsistent column edge).
|
|
714
|
-
We only force the horizontal margins; inline top/bottom spacing is kept. */
|
|
715
|
-
margin-left: auto !important;
|
|
716
|
-
margin-right: auto !important;
|
|
717
707
|
}
|
|
718
708
|
body > *:first-child { margin-top: 0 !important; }
|
|
719
709
|
body > *:last-child { margin-bottom: 0 !important; }
|
|
720
|
-
/*
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
then centres, rather than growing without limit. */
|
|
710
|
+
/* Wide-content escape for embedded mockups mid-presentation. Wrap a section in
|
|
711
|
+
<div class="full-bleed"> and it fills the content column's full width —
|
|
712
|
+
wider than the 880px prose cap — while sharing the prose's LEFT edge. It does
|
|
713
|
+
NOT break out to the card's physical edge: the body's horizontal padding
|
|
714
|
+
stays as a gutter, so neither the mockup nor the surrounding text ever touches
|
|
715
|
+
the card border. (The name is historical — it's "full content width", not
|
|
716
|
+
"bleed to the card edge".) Capped at 100% of the content column, which the
|
|
717
|
+
body's max-width already limits to desktop-realistic proportions. */
|
|
729
718
|
.full-bleed {
|
|
730
|
-
width:
|
|
719
|
+
width: 100%;
|
|
720
|
+
max-width: 100% !important;
|
|
721
|
+
margin-left: 0;
|
|
722
|
+
margin-right: 0;
|
|
723
|
+
}
|
|
724
|
+
/* Skeuomorphic macOS-style window chrome for UI mockups. Usage:
|
|
725
|
+
<div class="window" data-title="App name"> …mockup content… </div>
|
|
726
|
+
Draws a 40px title bar with the three traffic-light dots (red/yellow/green)
|
|
727
|
+
and an optional centred title from data-title. Content sits below the bar.
|
|
728
|
+
Add the desktop class for a full desktop-screen canvas — min-height 900px,
|
|
729
|
+
i.e. the 1440x900 (16:10) standard design canvas — so a screen mockup looks
|
|
730
|
+
like a real window with viewport breathing room rather than a short strip.
|
|
731
|
+
Omit desktop for dialogs / small components so they stay content-sized. */
|
|
732
|
+
.window {
|
|
731
733
|
position: relative;
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
734
|
+
padding-top: 40px;
|
|
735
|
+
border-radius: 12px;
|
|
736
|
+
border: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
737
|
+
box-shadow: 0 14px 48px rgba(0, 0, 0, 0.16);
|
|
738
|
+
overflow: hidden;
|
|
739
|
+
background: light-dark(#ffffff, #161616);
|
|
740
|
+
}
|
|
741
|
+
.window::before {
|
|
742
|
+
content: "";
|
|
743
|
+
position: absolute;
|
|
744
|
+
top: 0;
|
|
745
|
+
left: 0;
|
|
746
|
+
right: 0;
|
|
747
|
+
height: 40px;
|
|
748
|
+
background-color: light-dark(#f1f1f1, #1f1f1f);
|
|
749
|
+
border-bottom: 1px solid light-dark(#e2e2e2, #2a2a2a);
|
|
750
|
+
background-image:
|
|
751
|
+
radial-gradient(circle at 19px 20px, #ff5f57 6px, transparent 6.5px),
|
|
752
|
+
radial-gradient(circle at 39px 20px, #febc2e 6px, transparent 6.5px),
|
|
753
|
+
radial-gradient(circle at 59px 20px, #28c840 6px, transparent 6.5px);
|
|
754
|
+
background-repeat: no-repeat;
|
|
755
|
+
}
|
|
756
|
+
.window::after {
|
|
757
|
+
content: attr(data-title);
|
|
758
|
+
position: absolute;
|
|
759
|
+
top: 0;
|
|
760
|
+
left: 0;
|
|
761
|
+
right: 0;
|
|
762
|
+
height: 40px;
|
|
763
|
+
display: flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
justify-content: center;
|
|
766
|
+
font-size: 13px;
|
|
767
|
+
font-weight: 500;
|
|
768
|
+
color: light-dark(#6b6b6b, #9b9b9b);
|
|
769
|
+
pointer-events: none;
|
|
770
|
+
}
|
|
771
|
+
.window.desktop {
|
|
772
|
+
min-height: 900px;
|
|
735
773
|
}
|
|
736
774
|
.wrap { display: block; }
|
|
737
775
|
.kicker {
|
package/dist/http-server.js
CHANGED
|
@@ -51,6 +51,16 @@ export function startHttpServer() {
|
|
|
51
51
|
app.use("/static", express.static(CLIENT_DIR, {
|
|
52
52
|
fallthrough: false,
|
|
53
53
|
maxAge: "0",
|
|
54
|
+
etag: true,
|
|
55
|
+
lastModified: true,
|
|
56
|
+
// Force the browser to revalidate the client JS/CSS on every load (via
|
|
57
|
+
// ETag) instead of serving a stale cached copy. Without this a plain
|
|
58
|
+
// reload after an easel update keeps the old viewer.js — the user has to
|
|
59
|
+
// hard-reload (⌘⇧R) to pick up new styles, which has bitten us. `no-cache`
|
|
60
|
+
// means "cached copy is fine ONLY after revalidating it's unchanged".
|
|
61
|
+
setHeaders: (res) => {
|
|
62
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
63
|
+
},
|
|
54
64
|
}));
|
|
55
65
|
app.get("/health", (_req, res) => {
|
|
56
66
|
res.json({ ok: true, pid: process.pid, port });
|
package/dist/mcp.js
CHANGED
|
@@ -139,7 +139,9 @@ export async function main() {
|
|
|
139
139
|
"═══ LAYOUT ═══\n" +
|
|
140
140
|
"• Stack desktop mockups VERTICALLY with labels ('Now', 'Proposed') — don't squeeze them side-by-side. The iframe is ~900px wide; two desktop screens at half-width crush columns, wrap headings to 3 lines, and turn tables unreadable.\n" +
|
|
141
141
|
"• Side-by-side is fine only for narrow mobile mockups, small cards, or short text columns that genuinely fit in half-width.\n" +
|
|
142
|
-
"• Mockup embedded mid-explanation? Prose
|
|
142
|
+
"• Mockup embedded mid-explanation? Prose is left-aligned and capped ~880px; wrap JUST the mockup in <div class=\"full-bleed\">…</div> and it fills the content column from the SAME left edge (wider than the prose, sharing one left margin; the body padding stays as a gutter so nothing touches the card edge). (If the WHOLE push is a UI recreation, use kind:'mockup'/'app' instead.)\n" +
|
|
143
|
+
"• Window chrome: wrap a mockup in <div class=\"window\" data-title=\"App name\">…</div> for a macOS window frame (title bar + red/yellow/green traffic-light dots + centred title). Add the `desktop` class (class=\"window desktop\") for the 1440x900 (16:10) desktop canvas via min-height:900px; omit it so dialogs/components size to content. Combine with .full-bleed to fill the column.\n" +
|
|
144
|
+
"• BUILD MOCKUPS FLUID, not fixed-width. Lay the inside out with flex / % / fr widths, not hardcoded width:1440px columns. 1440 is a MAX, not a target. A fluid mockup reflows to fit when the viewer's window is squeezed — no horizontal scroll, nothing clipped, exports stay complete. A fixed-pixel-width mockup gets cut off or needs an awkward horizontal scrollbar when narrowed.\n" +
|
|
143
145
|
"• MATCH THE SOURCE'S REAL FRAME — faithful height, not minimal, in both directions. Mocking a COMPONENT (card, modal, row, toolbar)? Size to content — do NOT pad with min-height:560px / height:100vh to feel 'desktop-y'; that floats content in dead whitespace. Mocking a FULL DESKTOP SCREEN (login page, dashboard)? Give it realistic viewport proportions (e.g. height:760px or 16:10) and lay content out inside as the real screen does (centred form, top nav) — cropping a real screen down to just its content height misrepresents it just as much. Either way copy the source's exact height if it has one. Test: cropped the same way, would your mock look like a screenshot of the real thing? Empty bands = over-padded; a screen squashed to a strip = under-sized.\n" +
|
|
144
146
|
"• When recreating real app UI, hug the source — pull exact colors, spacing, sizing, radii, fonts from the component/theme/Figma/DevTools. A close-but-wrong recreation misleads more than no recreation. If you can't reach the actuals, say so in chat and don't pass the mock off as accurate.\n" +
|
|
145
147
|
"• One accent color, 3–4 instances max per card. Status colors (red/amber/green) only when state genuinely maps to status.\n\n" +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ammduncan/easel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.25",
|
|
4
4
|
"description": "A live browser tab for every Claude Code (and MCP) session. The push MCP tool appends HTML cards to a scrolling feed you keep open in split-screen.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -293,12 +293,29 @@ Most mockups appear *inside* an explanation push — prose intro, embedded UI mo
|
|
|
293
293
|
<p>The left panel uses the existing brand assets…</p>
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
-
`.full-bleed` is injected into every presentation push.
|
|
296
|
+
`.full-bleed` is injected into every presentation push. Prose is left-aligned and capped at ~880px; `.full-bleed` fills the **content column's full width from the same left edge** — wider than the prose, sharing one left margin down the card. It does *not* bleed to the card's physical edge: the body padding stays as a gutter, so neither the mockup nor the text ever touches the card border.
|
|
297
297
|
|
|
298
298
|
Two cases, two tools:
|
|
299
299
|
- **Whole push is a mockup / app recreation** → `kind: "mockup"` (or `"app"`) on the push. Strips the entire presentation frame; content owns the canvas.
|
|
300
300
|
- **Mockup embedded in an explanation** → leave the push as-is and wrap the mockup section in `<div class="full-bleed">`.
|
|
301
301
|
|
|
302
|
+
### Window chrome for UI mockups
|
|
303
|
+
|
|
304
|
+
Wrap a mockup in `.window` to give it a macOS window frame — a title bar with the three traffic-light dots and a centred title:
|
|
305
|
+
|
|
306
|
+
```html
|
|
307
|
+
<div class="full-bleed">
|
|
308
|
+
<div class="window desktop" data-title="DVLA Self Service — Login">
|
|
309
|
+
<!-- mockup content fills the window body, below the 40px title bar -->
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
- `data-title` sets the centred title text (omit for a blank bar).
|
|
315
|
+
- Add the **`desktop`** class for a full desktop-screen canvas — `min-height: 900px`, the standard 1440×900 (16:10) design canvas — so a screen mockup looks like a real window with viewport breathing room. **Omit `desktop`** for a dialog or small component so the chrome sizes to its content (don't pad a small thing to screen height).
|
|
316
|
+
|
|
317
|
+
**Build the mockup fluid, not fixed-width.** Lay the inside out with flex / `%` / `fr` widths, not hardcoded `width: 1440px` columns. The content column caps at a desktop-realistic width, but when the viewer's window is narrower (a "squeezed" screen) a fluid mockup simply **reflows to fit** — no horizontal scroll, nothing clipped, and PNG/PDF export still captures the whole thing. A fixed-pixel-width mockup gets cut off or needs an awkward horizontal scrollbar when squeezed; a fluid one never does. The 1440 is a *max*, not a target.
|
|
318
|
+
|
|
302
319
|
### Semantic chips
|
|
303
320
|
|
|
304
321
|
```html
|