@ammduncan/easel 0.2.23 → 0.2.24

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## 0.2.24 — 2026-05-23
6
+
7
+ ### Added
8
+ - **`.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.
9
+
10
+ ### Changed
11
+ - **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).
12
+ - **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.
13
+
5
14
  ## 0.2.23 — 2026-05-23
6
15
 
7
16
  ### 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, but let visual blocks
696
- (cards, grids, tables, mockups) use the full card width. Match prose both
697
- as direct body children AND one level deep through a .wrap container
698
- the skill recommends wrapping content in div.wrap, and without the
699
- .wrap branch the cap silently misses and prose stretches full width.
700
- .full-bleed still escapes via viewport units regardless of nesting. */
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
- /* Full-bleed escape hatch for embedded mockups mid-presentation. Wrap a
721
- section in <div class="full-bleed"> and it breaks out of the body padding +
722
- prose max-width to span the card, while the prose around it stays in the
723
- reading column. Inside the iframe, 100vw === the card's width; left:50% +
724
- translateX(-50%) re-centres on the card.
725
- Capped at 1440px: on a wide monitor the card itself can be ~2000px, but a
726
- real desktop screen tops out around 1440 letting a mockup stretch past
727
- that looks unnaturally wide. So full-bleed fills the card up to 1440 and
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: min(100vw, 1440px);
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
- left: 50%;
733
- transform: translateX(-50%);
734
- max-width: 100vw;
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 {
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ammduncan/easel",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
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",