@nadicodeai/design-system 0.10.0 → 0.10.1

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/AGENTS.md CHANGED
@@ -76,6 +76,7 @@ Generated-artifact and runtime-JS prohibitions live in the Forbidden Patterns ta
76
76
  | Command | When |
77
77
  | --- | --- |
78
78
  | `npm run lint:design` | After any `DESIGN.md` edit |
79
+ | `npm run lint` | After editing JavaScript or TypeScript tooling, tests, or the chat enhancer |
79
80
  | `npm run build` | After any `DESIGN.md` edit; regenerates `dist/tokens/nadicode.dtcg.json` and `dist/tailwind/nadicode.tailwind.json` |
80
81
  | `npm run generate:icons` | After bumping `lucide-static`; rebakes `dist/icons/` (also runs inside `npm run build`) |
81
82
  | `npm run test` | Before handoff. Browser-free by design — safe for agents and CI |
@@ -86,7 +87,7 @@ Generated-artifact and runtime-JS prohibitions live in the Forbidden Patterns ta
86
87
  Before claiming work complete (from this directory, or with `-w design-system` from the repo root):
87
88
 
88
89
  ```bash
89
- npm run lint:design && npm run build && npm run test
90
+ npm run lint:design && npm run lint && npm run build && npm run test
90
91
  ```
91
92
 
92
93
  `npm run test` MUST stay browser-free: browser-driven tests live in `tests/visual/*.visual.ts` (outside the default `*.test.ts` glob) and run only via `npm run test:visual`. Never add a browser test to the default suite — parallel headless sessions inside the gate are what orphaned Chrome and hung it; that is the real constraint, not `agent-browser` itself.
@@ -176,10 +176,10 @@
176
176
  };
177
177
 
178
178
  ChatThreadPlayer.prototype._sleep = function (ms) {
179
- var self = this;
179
+ var timers = this.timers;
180
180
  return new Promise(function (resolve) {
181
181
  var t = setTimeout(resolve, ms);
182
- self.timers.push(t);
182
+ timers.push(t);
183
183
  });
184
184
  };
185
185
 
@@ -272,34 +272,33 @@
272
272
  // continuation can detect that the player was stopped-and-re-armed underneath
273
273
  // it (see the `generation` note in the constructor).
274
274
  ChatThreadPlayer.prototype._runLoop = function () {
275
- var self = this;
276
275
  var myGen = this.generation;
277
- var stale = function () {
278
- return self.cancelled || self.generation !== myGen;
279
- };
280
- return (function loop() {
281
- if (stale()) return Promise.resolve();
282
- self._hideAllMessages();
283
- self._hideTyping();
284
- self._setMeter(0);
285
- return self
276
+ function stale(player) {
277
+ return player.cancelled || player.generation !== myGen;
278
+ }
279
+ function loop(player) {
280
+ if (stale(player)) return Promise.resolve();
281
+ player._hideAllMessages();
282
+ player._hideTyping();
283
+ player._setMeter(0);
284
+ return player
286
285
  ._sleep(START_DELAY)
287
286
  .then(function () {
288
287
  var chain = Promise.resolve();
289
- self.messages.forEach(function (msg, i) {
288
+ player.messages.forEach(function (msg, i) {
290
289
  chain = chain.then(function () {
291
- if (stale()) return;
290
+ if (stale(player)) return;
292
291
  var role = roleOf(msg);
293
292
  var t = defaultTiming(role, (msg.textContent || "").trim().length);
294
293
  var step = function () {
295
- if (stale()) return;
296
- self._hideTyping();
297
- self._reveal(msg, i);
298
- return self._sleep(t.holdMs);
294
+ if (stale(player)) return;
295
+ player._hideTyping();
296
+ player._reveal(msg, i);
297
+ return player._sleep(t.holdMs);
299
298
  };
300
299
  if (t.typingMs > 0) {
301
- self._showTyping();
302
- return self._sleep(t.typingMs).then(step);
300
+ player._showTyping();
301
+ return player._sleep(t.typingMs).then(step);
303
302
  }
304
303
  return step();
305
304
  });
@@ -307,17 +306,18 @@
307
306
  return chain;
308
307
  })
309
308
  .then(function () {
310
- if (stale()) return;
309
+ if (stale(player)) return;
311
310
  // Mark completion before the hold so the observer entry branch can
312
311
  // show the final state on re-entry without replaying from blank.
313
- self.completedOnce = true;
314
- return self._sleep(LOOP_HOLD);
312
+ player.completedOnce = true;
313
+ return player._sleep(LOOP_HOLD);
315
314
  })
316
315
  .then(function () {
317
- if (stale()) return;
318
- return loop();
316
+ if (stale(player)) return;
317
+ return loop(player);
319
318
  });
320
- })();
319
+ }
320
+ return loop(this);
321
321
  };
322
322
 
323
323
  ChatThreadPlayer.prototype.play = function () {
@@ -363,7 +363,6 @@
363
363
  // Never called in reduced-motion mode (contract (b): no button for a path
364
364
  // that plays nothing).
365
365
  ChatThreadPlayer.prototype._injectPauseButton = function () {
366
- var self = this;
367
366
  var thread = this.thread;
368
367
  var subject = thread.getAttribute("data-nc-autoplay-name") || "demonstration";
369
368
  var btn = document.createElement("button");
@@ -371,22 +370,25 @@
371
370
  btn.className = "nc-chat-pause";
372
371
  btn.setAttribute("aria-pressed", "false");
373
372
  btn.textContent = "Pause " + subject;
374
- btn.addEventListener("click", function () {
375
- if (self.userPaused) {
376
- // Resume: clear the latch, rearm and play.
377
- self.userPaused = false;
378
- btn.setAttribute("aria-pressed", "false");
379
- btn.textContent = "Pause " + subject;
380
- self.rearm();
381
- self.play();
382
- } else {
383
- // Pause: set the latch, stop and reveal full content.
384
- self.userPaused = true;
385
- btn.setAttribute("aria-pressed", "true");
386
- btn.textContent = "Resume " + subject;
387
- self.stop();
388
- }
389
- });
373
+ btn.addEventListener(
374
+ "click",
375
+ function () {
376
+ if (this.userPaused) {
377
+ // Resume: clear the latch, rearm and play.
378
+ this.userPaused = false;
379
+ btn.setAttribute("aria-pressed", "false");
380
+ btn.textContent = "Pause " + subject;
381
+ this.rearm();
382
+ this.play();
383
+ } else {
384
+ // Pause: set the latch, stop and reveal full content.
385
+ this.userPaused = true;
386
+ btn.setAttribute("aria-pressed", "true");
387
+ btn.textContent = "Resume " + subject;
388
+ this.stop();
389
+ }
390
+ }.bind(this),
391
+ );
390
392
  // Insert before the body so the button sits above the scrolling content.
391
393
  var body = thread.querySelector(".nc-chat-thread-body");
392
394
  if (body) {
@@ -23,8 +23,26 @@
23
23
  * built-in where a brand curve exists.
24
24
  * - State feedback completes in 150–200 ms; press <= 160 ms; only the
25
25
  * demonstration (Type) may run longer.
26
+ * - DURATION TIERS: UI feedback stays under 300 ms. Medium-frequency
27
+ * product motion (list reflow, rolling numbers) uses --nc-duration-reflow
28
+ * (250 ms); --nc-duration-msg-in (360 ms) is reserved for the one-time
29
+ * Settle entrance, never for motion the operator meets on every refetch.
30
+ * - ASYMMETRIC TIMING: a deliberate action opens slower than it dismisses.
31
+ * Popovers/menus/dialogs open at --nc-duration-open (220 ms) and close at
32
+ * --nc-duration-close (140 ms); the open invites, the close gets out of the
33
+ * way.
34
+ * - ORIGIN-AWARE: a surface that springs from a trigger scales FROM that
35
+ * trigger (transform-origin at the anchor), not from its own centre — a
36
+ * popover grows out of the button that opened it.
37
+ * - INTERRUPTIBLE: gesture- or toast-driven motion retargets from its
38
+ * current position; it is never restarted from a fixed keyframe mid-flight.
26
39
  * - Nothing enters from scale(0) or from nothing.
27
- * - Compositor-only (opacity/transform); no will-change; zero CLS.
40
+ * - PROPERTY DISCIPLINE: movement is compositor-only (opacity/transform).
41
+ * The Confirm move may additionally transition background-color,
42
+ * border-color, and color at --nc-duration-confirm — colour is the state
43
+ * change itself, and a paint-only transition triggers no reflow. Never
44
+ * animate a property that shifts surrounding layout; no will-change;
45
+ * zero CLS.
28
46
  * - FREQUENCY RULE: motion a user meets repeatedly (product UI, keyboard-
29
47
  * triggered actions) does not animate. Delight is reserved for rarely-
30
48
  * seen surfaces (marketing pages, first runs). Product surfaces that
@@ -32,7 +50,9 @@
32
50
  *
33
51
  * BANNED: parallax, scroll-jacking, floating/bobbing idle loops, gradient
34
52
  * shimmer, staggered letter reveals, anything that moves a seam or hairline
35
- * after it settles, more than one theatrical moment per page.
53
+ * after it settles, more than one theatrical moment per page, and
54
+ * `transition: all` / `transition-property: all` (name the properties you
55
+ * animate — a blanket transition drags unintended props off the compositor).
36
56
  *
37
57
  * REDUCED MOTION: prefers-reduced-motion removes movement entirely — Settle
38
58
  * and Type loops gone, Confirm instant, press still. The page must read
@@ -68,6 +88,24 @@
68
88
  --nc-duration-pulse: 1.6s;
69
89
  --nc-duration-confirm: 180ms;
70
90
 
91
+ /* Frequency-tiered UI cadence (craft rule, not a prototype extraction).
92
+ --nc-duration-reflow : 250ms ← medium-frequency product motion (list
93
+ reflow, rolling numbers) — kept under the
94
+ 300ms UI-feedback ceiling so it never feels
95
+ sluggish on a surface met on every refetch.
96
+ 360ms (--nc-duration-msg-in) stays reserved
97
+ for the one-time Settle entrance. */
98
+ --nc-duration-reflow: 250ms;
99
+
100
+ /* Asymmetric open/close cadence (craft-rule tokens for the ASYMMETRIC TIMING
101
+ rule above — a deliberate action opens slower than it dismisses).
102
+ --nc-duration-open : 220ms drives a popover/menu/dialog opening; the open
103
+ invites.
104
+ --nc-duration-close: 140ms drives its dismissal; the close gets out of the
105
+ way. Both stay under the 300ms UI-feedback ceiling. */
106
+ --nc-duration-open: 220ms;
107
+ --nc-duration-close: 140ms;
108
+
71
109
  /* Easings — extracted from chat-anim.jsx.
72
110
  --nc-ease-emphasized : cubic-bezier(.3,.7,.2,1) ← footer width transition;
73
111
  the brand's settle curve.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/design-system",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -46,6 +46,7 @@
46
46
  "generate:icons": "tsx scripts/generate-icons.ts",
47
47
  "generate:favicons": "tsx scripts/generate-favicons.ts",
48
48
  "lint:design": "design.md lint DESIGN.md",
49
+ "lint": "eslint --max-warnings=0",
49
50
  "test": "vitest run",
50
51
  "test:visual": "vitest run --config vitest.visual.config.ts",
51
52
  "prepublishOnly": "npm run build"
@@ -54,10 +55,12 @@
54
55
  "@google/design.md": "0.1.1",
55
56
  "@types/node": "^22",
56
57
  "agent-browser": "^0.31.1",
58
+ "eslint": "^9.39.4",
57
59
  "lucide-static": "^1.17.0",
58
60
  "style-dictionary": "^5.4.3",
59
61
  "tsx": "^4.21.0",
60
62
  "typescript": "^5",
63
+ "typescript-eslint": "^8.46.0",
61
64
  "vitest": "^4.1.6"
62
65
  }
63
66
  }