claude-dev-env 8.56.2 → 8.57.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.
@@ -16,6 +16,7 @@
16
16
  | Readable text | Text is 16px or larger and uses the full text color. Gray stays on lines and borders only. Text holds a 7:1 contrast ratio against its background. |
17
17
  | Touch targets | Buttons and chips are 40px or taller, with a visible focus ring. |
18
18
  | Copy fallback | The copy button uses the clipboard API and falls back to a hidden text area, then says when the copy is blocked. |
19
+ | Review rounds | Parts changed since the viewer's last look carry a thin bar on their left edge, and changed cards a "Changed" label. [review-rounds.md](review-rounds.md) holds the data fields and behavior. |
19
20
 
20
21
  ## House rules for a page built from it
21
22
 
@@ -0,0 +1,26 @@
1
+ # Marking what changed between review rounds
2
+
3
+ A page that a reviewer reads more than once shows what changed since their last look. The template marks each changed card and each changed part with a thin bar on its left edge, gives each changed card a small "Changed" label, and gives each changed board chip a dot in the same color. There is no diff and no list of changes.
4
+
5
+ ## Data
6
+
7
+ | Field | Where | Meaning |
8
+ |---|---|---|
9
+ | `round` | top of the data block | The review round this revision answers. Add one each time the page is republished after feedback. |
10
+ | `changed_round` | an item | The round in which this item last changed. |
11
+ | `changed_parts` | an item, optional | The parts that changed: `title`, `next`, `summary`, `steps`, `links`. With no list, only the card is marked. |
12
+
13
+ When a revision answers feedback, set `round` to the new number and stamp each item it changed with that number and the parts it touched. Leave older stamps in place; they stop showing once the reviewer has seen their round.
14
+
15
+ ## Behavior
16
+
17
+ - The page keeps the last round each viewer saw in `localStorage`. Parts stamped after that round are marked.
18
+ - A first visit, or a browser that blocks storage, marks the parts stamped in the current round.
19
+ - The marks hold through a reload in the same tab, through `sessionStorage`, and clear on the next visit.
20
+ - A changed summary opens by itself.
21
+
22
+ ## Other pages
23
+
24
+ Any element with `data-changed-round="N"` takes the bar. An element that holds a `data-change-slot` child gets the "Changed" label in that child; other elements get a screen reader label.
25
+
26
+ The `--change` and `--change-ink` tokens hold the mark colors in light and dark. Keep the label text at 7:1 or better against `--change`, and the bar at 3:1 or better against `--surface` and `--bg`.
@@ -7,6 +7,7 @@
7
7
  --accent: #2245a2; --accent-soft: #e6ecfb; --accent-ink: #ffffff;
8
8
  --ok: #165737; --ok-soft: #e3f3ea; --bad: #8b2621; --bad-soft: #fbe5e3; --warn: #6f4100; --warn-soft: #fbf0dc;
9
9
  --violet: #553b95; --violet-soft: #efe9fa;
10
+ --change: #8a1f6b; --change-ink: #ffffff;
10
11
  --font-body: "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
11
12
  }
12
13
  @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
@@ -14,12 +15,14 @@
14
15
  --accent: #a8bfff; --accent-soft: #1f2a45; --accent-ink: #0d1424;
15
16
  --ok: #6fd3a0; --ok-soft: #173327; --bad: #ffa8a0; --bad-soft: #3a1c1a; --warn: #f0b860; --warn-soft: #362a14;
16
17
  --violet: #c5b1f2; --violet-soft: #251f38;
18
+ --change: #f5a3dc; --change-ink: #1f0a19;
17
19
  color-scheme: dark } }
18
20
  :root[data-theme="dark"] {
19
21
  --bg: #12151b; --surface: #1a1f28; --fg: #e6e9ef; --edge: #6d7891; --line: #2b3240;
20
22
  --accent: #a8bfff; --accent-soft: #1f2a45; --accent-ink: #0d1424;
21
23
  --ok: #6fd3a0; --ok-soft: #173327; --bad: #ffa8a0; --bad-soft: #3a1c1a; --warn: #f0b860; --warn-soft: #362a14;
22
24
  --violet: #c5b1f2; --violet-soft: #251f38;
25
+ --change: #f5a3dc; --change-ink: #1f0a19;
23
26
  color-scheme: dark }
24
27
  * { box-sizing: border-box }
25
28
  body { background: var(--bg); color: var(--fg); font-family: var(--font-body); font-size: 17px; line-height: 1.4; padding-inline: 16px; padding-block: 24px 48px; margin: 0 }
@@ -95,6 +98,15 @@ details.what[open] summary::before { content: "▾" }
95
98
  .steps .sh { font-weight: 800 }
96
99
  .step { display: flex; gap: 10px; align-items: flex-start }
97
100
  .step .num { flex: none; width: 30px; height: 30px; border-radius: 50%; background: var(--warn); color: var(--surface); display: grid; place-items: center; font-weight: 800 }
101
+ .lchip.changed::before { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--change); flex: none }
102
+ .changed { box-shadow: inset 4px 0 0 var(--change); padding-left: 12px; border-radius: 4px }
103
+ .card.changed { box-shadow: inset 6px 0 0 var(--change); padding-left: 0; border-radius: 16px }
104
+ .card.changed .band, .card.changed > .body { padding-left: 18px }
105
+ .card.changed .body .changed { padding-left: 12px }
106
+ .next.changed { border-radius: 12px }
107
+ .lchip.changed { box-shadow: none; border-radius: 999px; padding-left: 10px; border-color: var(--change); display: inline-flex; align-items: center; gap: 6px }
108
+ .pill-changed { display: inline-flex; align-items: center; min-height: 28px; padding: 0 10px; border-radius: 999px; background: var(--change); color: var(--change-ink); font-size: 16px; font-weight: 800 }
109
+ .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap }
98
110
  .empty { background: var(--surface); border: 2px dashed var(--edge); border-radius: 14px; padding: 20px; font-weight: 600 }
99
111
  @media (hover: hover) { .lchip:hover, .fchip:hover, .tab:hover, .copy:hover { border-color: var(--accent) } }
100
112
  </style>
@@ -117,6 +129,7 @@ details.what[open] summary::before { content: "▾" }
117
129
  <script type="application/json" id="tracker-data">
118
130
  {
119
131
  "updated": "2026-01-15T17:30:00Z",
132
+ "round": 3,
120
133
  "owners": [
121
134
  {
122
135
  "key": "web",
@@ -147,6 +160,8 @@ details.what[open] summary::before { content: "▾" }
147
160
  "needs_you": true,
148
161
  "card": true,
149
162
  "next": "choose between the two dates in the linked plan.",
163
+ "changed_round": 3,
164
+ "changed_parts": ["next"],
150
165
  "summary": [
151
166
  "One release date everyone plans around",
152
167
  "The launch checklist dated backward from it",
@@ -167,6 +182,8 @@ details.what[open] summary::before { content: "▾" }
167
182
  "status": "blocked",
168
183
  "card": true,
169
184
  "next": "wait for the staging database to come back online.",
185
+ "changed_round": 2,
186
+ "changed_parts": ["next"],
170
187
  "summary": [
171
188
  "The login test passes ten runs in a row",
172
189
  "The root cause written in the pull request"
@@ -186,6 +203,8 @@ details.what[open] summary::before { content: "▾" }
186
203
  "status": "doing",
187
204
  "card": true,
188
205
  "next": "finish the index change and rerun the timing check.",
206
+ "changed_round": 3,
207
+ "changed_parts": ["summary"],
189
208
  "summary": [
190
209
  "Search answers in under one second",
191
210
  "A timing check that guards it",
@@ -288,6 +307,8 @@ const STAGE_BY_KEY = Object.fromEntries(STAGES.map(s => [s.key, s]));
288
307
  const TRACK = ["Card", "Builder", "Pull request", "Merged"];
289
308
  const state = { tab: "open", owner: "all", query: "" };
290
309
  try { const saved = JSON.parse(localStorage.getItem("work-tracker-view") || "{}"); if (saved.tab) state.tab = saved.tab; if (saved.owner) state.owner = saved.owner; } catch (e) {}
310
+ const REVIEW_ROUND = Number(TRACKER.round) || 0;
311
+ const CHANGE_BASELINE = changeBaseline();
291
312
  const ownerByKey = Object.fromEntries(TRACKER.owners.map(o => [o.key, o]));
292
313
  const isDone = it => it.status === "done";
293
314
  function stageOf(it) {
@@ -300,6 +321,34 @@ function stageOf(it) {
300
321
  const slug = it => "item-" + it.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
301
322
  const fmt = iso => { if (!iso) return ""; try { return new Date(iso).toLocaleString(undefined, { month: "long", day: "numeric", hour: "numeric", minute: "2-digit" }); } catch (e) { return iso; } };
302
323
  const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
324
+ function changeBaseline() {
325
+ try {
326
+ const held = JSON.parse(sessionStorage.getItem("work-tracker-baseline") || "null");
327
+ if (held && held.round === REVIEW_ROUND) return held.baseline;
328
+ } catch (e) {}
329
+ let baseline = REVIEW_ROUND - 1;
330
+ try {
331
+ const seen = localStorage.getItem("work-tracker-seen-round");
332
+ if (seen !== null && Number.isFinite(Number(seen))) baseline = Math.min(Number(seen), REVIEW_ROUND);
333
+ } catch (e) {}
334
+ try { sessionStorage.setItem("work-tracker-baseline", JSON.stringify({ round: REVIEW_ROUND, baseline })); } catch (e) {}
335
+ try { localStorage.setItem("work-tracker-seen-round", String(REVIEW_ROUND)); } catch (e) {}
336
+ return baseline;
337
+ }
338
+ function stampChange(node, it, part) {
339
+ if (it.changed_round && (!part || [].concat(it.changed_parts || []).includes(part))) node.dataset.changedRound = it.changed_round;
340
+ return node;
341
+ }
342
+ function markChanges() {
343
+ for (const node of document.querySelectorAll("[data-changed-round]")) {
344
+ if (Number(node.dataset.changedRound) <= CHANGE_BASELINE || node.classList.contains("changed")) continue;
345
+ node.classList.add("changed");
346
+ const slot = node.querySelector("[data-change-slot]");
347
+ if (slot) { slot.prepend(el("span", "pill-changed", "Changed")); continue; }
348
+ const label = node.querySelector(":scope > summary") || node;
349
+ label.prepend(el("span", "sr", "Changed. "));
350
+ }
351
+ }
303
352
  function save() { try { localStorage.setItem("work-tracker-view", JSON.stringify({ tab: state.tab, owner: state.owner })); } catch (e) {} }
304
353
  function inView(it) {
305
354
  if ((state.tab === "done") !== isDone(it)) return false;
@@ -329,7 +378,7 @@ function renderBoard(items) {
329
378
  if (mine.length) {
330
379
  const chips = el("div", "lchips");
331
380
  for (const it of mine) {
332
- const b = el("button", "lchip", it.title);
381
+ const b = stampChange(el("button", "lchip", it.title), it);
333
382
  b.setAttribute("aria-label", "Go to " + it.title);
334
383
  b.onclick = () => jumpTo(it);
335
384
  chips.append(b);
@@ -389,25 +438,27 @@ function copyButton(it) {
389
438
  }
390
439
  function itemCard(it) {
391
440
  const key = stageOf(it), s = STAGE_BY_KEY[key], owner = ownerByKey[it.owner] || { name: it.owner };
392
- const card = el("article", "card " + key);
441
+ const card = stampChange(el("article", "card " + key), it);
393
442
  card.id = slug(it);
394
443
  const band = el("div", "band " + key);
444
+ band.dataset.changeSlot = "";
395
445
  band.append(el("span", null, s.icon), el("span", null, s.label));
396
446
  const proj = el("span", "proj");
397
447
  proj.append(el("span", null, (owner.icon || "📁")), el("span", null, owner.name));
398
448
  band.append(proj);
399
449
  card.append(band);
400
450
  const body = el("div", "body");
401
- body.append(el("h3", "title", it.title));
451
+ body.append(stampChange(el("h3", "title", it.title), it, "title"));
402
452
  body.append(trackFor(it));
403
453
  if (it.next) {
404
- const next = el("div", "next");
454
+ const next = stampChange(el("div", "next"), it, "next");
405
455
  next.append(el("span", "ic", isDone(it) ? "✅" : "👉"), el("span", null, it.next.charAt(0).toUpperCase() + it.next.slice(1)));
406
456
  body.append(next);
407
457
  }
408
458
  const points = [].concat(it.summary || []);
409
459
  if (points.length) {
410
- const d = el("details", "what");
460
+ const d = stampChange(el("details", "what"), it, "summary");
461
+ d.open = d.dataset.changedRound > CHANGE_BASELINE;
411
462
  d.append(el("summary", null, "What it delivers"));
412
463
  const ul = el("ul", "points");
413
464
  for (const p of points) ul.append(el("li", null, p));
@@ -415,12 +466,12 @@ function itemCard(it) {
415
466
  body.append(d);
416
467
  }
417
468
  if (it.proposal && !isDone(it) && it.owner_steps && it.owner_steps.length) {
418
- const st = el("div", "steps");
469
+ const st = stampChange(el("div", "steps"), it, "steps");
419
470
  st.append(el("div", "sh", "Your steps to create " + it.proposal.name));
420
471
  it.owner_steps.forEach((text, i) => { const row = el("div", "step"); row.append(el("span", "num", String(i + 1)), el("span", null, text)); st.append(row); });
421
472
  body.append(st);
422
473
  }
423
- const actions = el("div", "actions");
474
+ const actions = stampChange(el("div", "actions"), it, "links");
424
475
  for (const l of it.links || []) {
425
476
  const a = el("a", "link");
426
477
  a.append(el("span", null, "🔗"), el("span", null, l.label));
@@ -458,6 +509,7 @@ function render() {
458
509
  list.append(h);
459
510
  mine.forEach(i => list.append(itemCard(i)));
460
511
  }
512
+ markChanges();
461
513
  }
462
514
  document.querySelectorAll(".tab").forEach(t => t.onclick = () => { state.tab = t.dataset.tab; save(); render(); });
463
515
  document.getElementById("search").oninput = e => { state.query = e.target.value; render(); };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.56.2",
3
+ "version": "8.57.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {