create-pmos 0.527.0 → 0.529.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.
Files changed (58) hide show
  1. package/package.json +57 -29
  2. package/payload/KIT-VERSION +1 -1
  3. package/payload/planning/evals/eval-calibration.md +1 -1
  4. package/payload/planning/okrs/health-budgets.md +1 -1
  5. package/payload/state/discoveries.md +1 -1
  6. package/workspace/adler-moments-test.js +4 -3
  7. package/workspace/briefing-premium-calendar-3-test.js +11 -9
  8. package/workspace/briefing-premium-calendar-test.js +1 -1
  9. package/workspace/changelog.json +56 -0
  10. package/workspace/ev-everywhere-test.js +2 -6
  11. package/workspace/fixtures/premium-viewer-fixtures.js +4 -2
  12. package/workspace/guide/home.md +7 -6
  13. package/workspace/guide/idle-lab.md +1 -1
  14. package/workspace/guide/notes.md +19 -7
  15. package/workspace/guide/reports-set-up.md +4 -1
  16. package/workspace/lib/bg-queue.js +10 -3
  17. package/workspace/lib/meeting-replay.js +11 -2
  18. package/workspace/lib/meeting-suggest.js +105 -0
  19. package/workspace/lib/meeting-terms.js +57 -17
  20. package/workspace/lib/okr-goals.js +23 -3
  21. package/workspace/lib/report-chat.js +3 -3
  22. package/workspace/lib/routes-lab.js +44 -2
  23. package/workspace/lib/routes-meeting-points.js +27 -3
  24. package/workspace/lib/routes-morning-goals.js +5 -1
  25. package/workspace/lib/routes-report-flow.js +35 -5
  26. package/workspace/news-viewers-test.js +2 -1
  27. package/workspace/package-lock.json +2 -2
  28. package/workspace/package.json +1 -1
  29. package/workspace/premium-vw-meet-test.js +17 -11
  30. package/workspace/product-lane-test.js +1 -1
  31. package/workspace/program-charter-test.js +2 -2
  32. package/workspace/public/pm/bd-ini-panel.js +1 -1
  33. package/workspace/public/pm/bd-span.js +10 -7
  34. package/workspace/public/pm/cozy-board.css +1 -0
  35. package/workspace/public/pm/cozy-lab-ins.js +13 -4
  36. package/workspace/public/pm/cozy-search.js +10 -17
  37. package/workspace/public/pm/cozy-settings.js +8 -2
  38. package/workspace/public/pm/cozy-today.css +30 -14
  39. package/workspace/public/pm/cozy-vw-mtg.css +122 -24
  40. package/workspace/public/pm/good-morning.js +30 -17
  41. package/workspace/public/pm/home-app.js +11 -1
  42. package/workspace/public/pm/home-data.js +4 -1
  43. package/workspace/public/pm/home-day.js +12 -5
  44. package/workspace/public/pm/premium-board.js +7 -5
  45. package/workspace/public/pm/premium-initiative-head.js +9 -1
  46. package/workspace/public/pm/premium-insights.js +5 -3
  47. package/workspace/public/pm/premium-meeting.js +2 -1
  48. package/workspace/public/pm/premium-mtg-replay.js +149 -73
  49. package/workspace/public/pm/premium-report-read.js +11 -4
  50. package/workspace/public/pm/premium-report-tab.js +33 -4
  51. package/workspace/public/pm/premium-vw-mtg.js +176 -53
  52. package/workspace/public/pm/program-model.js +2 -1
  53. package/workspace/public/pm/report-chat.js +1 -0
  54. package/workspace/public/pm/report-flow.js +7 -3
  55. package/workspace/public/premium-board.html +4 -4
  56. package/workspace/server.js +1 -1
  57. package/workspace/task-milestones-test.js +2 -2
  58. package/workspace/timeline-holds-test.js +3 -3
@@ -235,7 +235,7 @@
235
235
  .vzr { container-type: inline-size; padding: 0; background: none; gap: 14px; }
236
236
  .vzr-mbonly { display: none; }
237
237
  .vzr-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
238
- @container (min-width: 860px) { .vzr-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 20px; } .vzr-ev { position: sticky; top: 0; } }
238
+ @container (min-width: 860px) { .vzr-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: 22px; } .vzr-np:not(.vzr-ph1) { position: sticky; top: 0; } }
239
239
  .vzr-l { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
240
240
  .vzr-sum .czs-p { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; font-size: var(--fs-body); line-height: 1.55; margin: 0; }
241
241
  .vzr-sum.all .czs-p { display: block; }
@@ -275,40 +275,138 @@
275
275
  .vzr-ch[aria-expanded="true"] { background: var(--cz-ink) !important; color: var(--cz-card); }
276
276
  .vzr-ch[aria-expanded="true"] b, .vzr-ch[aria-expanded="true"] span { color: var(--cz-card); }
277
277
  .vzr-panel { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: 18px; border: 1px solid var(--cz-hair); background: var(--cz-card); }
278
- .vzr-ev { display: flex; flex-direction: column; gap: 12px; padding: 14px; border-radius: 20px; border: 1px solid var(--cz-hair); background: var(--cz-card); min-width: 0; }
279
- .vzr-shot { aspect-ratio: 16 / 9; background: var(--cz-well) !important; }
280
278
  .vzr-noshot { display: grid; place-items: center; min-height: 120px; border-radius: 12px; background: var(--cz-well); padding: 12px; text-align: center; }
281
- .vzr-acts { display: flex; align-items: center; gap: 10px; }
282
- .vzr-pri { width: 40px !important; height: 40px !important; background: var(--cz-blueink) !important; color: var(--cz-card) !important; }
283
- .vzr-at { font-size: var(--fs-row); font-weight: var(--fw-strong); color: var(--cz-blueink); font-variant-numeric: tabular-nums; line-height: var(--lh-row); }
284
- .vzr-q { margin: 0; font-size: var(--fs-body); line-height: 1.5; color: var(--cz-ink2); }
285
- .vzr-film { display: flex; gap: 6px; align-items: center; overflow: hidden; }
286
- .vzr-film.all { flex-wrap: wrap; }
287
- .vzr-film .vzp-th { width: 72px; height: 44px; }
288
- .vzr-more { height: 44px; min-width: 44px; padding: 0 8px !important; border-radius: 6px; background: var(--cz-well) !important; color: var(--cz-ink2); font-size: var(--fs-label); font-weight: var(--fw-strong); display: inline-grid !important; place-items: center; line-height: var(--lh-label); }
289
- .vzr-dock { position: sticky; bottom: 0; z-index: 2; flex-direction: row !important; align-items: center; gap: 14px !important; padding: 10px 16px !important; border-radius: 22px; background: var(--cz-card) !important; box-shadow: 0 18px 44px -18px color-mix(in oklab, var(--cz-ink) 40%, transparent), 0 0 0 1px var(--cz-hair); }
290
- .vzr-dock .vzp-ctl { flex: none; }
291
- .vzr-dock .vzp-ctl .vzp-mut { flex: none; }
292
- .vzr-tl { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
293
- .vzr-tops { align-items: flex-end; }
294
- .vzr-tops .vzp-top { white-space: normal; text-overflow: clip; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-height: 1.2; font-weight: var(--fw-strong); color: var(--cz-ink2); padding: 0 6px 0 0 !important; text-align: left !important; }
295
- .vzr-tops .vzp-top[aria-current="true"] { color: var(--cz-ink); font-weight: var(--fw-strong); }
296
279
  .vzp-kd[data-k="decision"] { fill: var(--cz-ok); }
297
280
  .vzp-kd[data-k="action"] { fill: var(--cz-blue); }
298
281
  .vzp-kd[data-k="question"] { fill: var(--cz-late); }
299
282
  /* a word fixed in place (MvF): fixed in green, a guess underlined dotted, the field in place */
300
- .vzr-fx { background: var(--cz-okbg); border-radius: 5px; padding: 0 3px; font-weight: var(--fw-strong); color: var(--cz-ink); }
283
+ .vzr-pts svg, .vzr-cfall svg, .vzr-panel .vzm-ib svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
301
284
  .vzr-fl { display: inline !important; padding: 0 !important; font: inherit; color: var(--cz-ink); font-weight: var(--fw-strong); border-bottom: 2px dotted var(--cz-late) !important; border-radius: 0 !important; cursor: pointer; }
302
- .vzr-fe { display: inline-flex; align-items: center; gap: 4px; padding: 0 2px 0 8px; border-radius: 10px; background: var(--cz-card); box-shadow: 0 0 0 2px var(--cz-blue); vertical-align: middle; }
303
- .vzr-fe input { width: 12ch; height: 28px; border: 0; outline: 0; background: transparent; font: inherit; font-weight: var(--fw-strong); color: var(--cz-ink); }
304
- .vzr-ok { width: 26px; height: 26px; border-radius: 50%; display: inline-grid !important; place-items: center; background: var(--cz-blueink) !important; color: var(--cz-card); }
305
- .vzr-ok svg, .vzr-ev .vzp-ib svg, .vzr-pts svg, .vzr-cfall svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
306
- .vzr-ev .vzp-fill, .vzr-pri svg { fill: currentColor; stroke: none; }
307
285
  @media (max-width: 640px) {
308
286
  .vzr-mbonly { display: block; }
309
287
  .vzr-mbonly > .czs-secs { margin-top: 0; }
310
288
  }
311
289
 
290
+ /* ── the player (batch 27 item 12: MpD1 now playing, MpD2 chapters, MpD4 captures shown, MpD3 phone) ── */
291
+ .vzr-np { position: relative; display: flex; flex-direction: column; gap: 14px; min-width: 0; }
292
+ .vzr-np svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; flex: none; }
293
+ .vzr-np svg.vzr-fill, .vzr-np .vzr-big svg { fill: currentColor; stroke: none; }
294
+ .vzr-art { position: relative; border-radius: 20px; overflow: hidden; background: var(--cz-well); box-shadow: 0 0 0 1px var(--cz-hair), 0 18px 40px -24px color-mix(in oklab, var(--cz-ink) 40%, transparent); }
295
+ .vzr-crop { position: relative; display: block; width: 100%; aspect-ratio: 16 / 9; overflow: hidden; }
296
+ .vzr-crop img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
297
+ .vzr-crop.boxed img { inset: auto; object-fit: fill; max-width: none; }
298
+ .vzr-art .vzr-crop { --mh: 340px; max-height: var(--mh); }
299
+ .vzr-art .vzr-crop.boxed { max-height: none; width: min(100%, calc(var(--mh) * var(--ar, 1.78))); margin: 0 auto; }
300
+ .vzr-tag { position: absolute; left: 12px; bottom: 12px; padding: 3px 10px; border-radius: 999px; background: color-mix(in oklab, var(--cz-card) 92%, transparent); color: var(--cz-ink2); font-size: var(--fs-label); font-weight: var(--fw-strong); line-height: var(--lh-label); box-shadow: 0 0 0 1px var(--cz-hair); }
301
+ .vzr-full { position: absolute !important; right: 10px; top: 10px; width: 36px; height: 36px; border-radius: 50%; display: inline-grid !important; place-items: center; background: color-mix(in oklab, var(--cz-card) 92%, transparent) !important; color: var(--cz-ink2); box-shadow: 0 0 0 1px var(--cz-hair); }
302
+ .vzr-noshot { display: grid; place-items: center; min-height: 120px; border-radius: 20px; background: var(--cz-well); padding: 12px; text-align: center; }
303
+ .vzr-nt { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
304
+ .vzr-nt b { font-size: var(--fs-panel); font-weight: var(--fw-strong); letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--cz-ink); line-height: 1.3; }
305
+ .vzr-nt span { font-size: var(--fs-meta); color: var(--cz-ink3); white-space: nowrap; line-height: var(--lh-meta); }
306
+ .vzr-barw { position: relative; padding-top: 2px; }
307
+ .vzr-bar { display: block; height: 26px; }
308
+ .vzr-bar svg { overflow: visible; }
309
+ .vzr-sg { stroke: none !important; fill: color-mix(in oklab, var(--cz-ink) 13%, var(--cz-card)); }
310
+ .vzr-sg[data-k="past"] { fill: color-mix(in oklab, var(--cz-blue) 50%, var(--cz-card)); }
311
+ .vzr-sg[data-k="cur"] { fill: color-mix(in oklab, var(--cz-blue) 20%, var(--cz-card)); }
312
+ .vzr-prog { stroke: none !important; fill: var(--cz-blue); }
313
+ .vzr-head { stroke: none !important; fill: var(--cz-blue); }
314
+ .vzr-mk { color: var(--cz-ink2); stroke-width: 3; }
315
+ .vzr-mk[data-k="question"] { color: var(--cz-late); }
316
+ .vzr-mk.on { color: var(--cz-ink); }
317
+ .vzr-md { fill: var(--cz-ok); stroke: none !important; }
318
+ .vzr-hov { position: absolute; bottom: 34px; z-index: 6; width: 160px; padding: 6px; border-radius: 14px; background: var(--cz-card); box-shadow: 0 16px 36px -14px color-mix(in oklab, var(--cz-ink) 50%, transparent), 0 0 0 1px var(--cz-hair); pointer-events: none; }
319
+ .vzr-hov[hidden] { display: none; }
320
+ .vzr-hth { display: block; border-radius: 9px; overflow: hidden; box-shadow: 0 0 0 1px var(--cz-hair); background: var(--cz-well); }
321
+ .vzr-hth .vzr-crop { aspect-ratio: 16 / 9; }
322
+ .vzr-hov b { display: block; margin: 6px 4px 0; font-size: var(--fs-meta); font-weight: var(--fw-strong); line-height: 1.25; color: var(--cz-ink); }
323
+ .vzr-hov small { display: block; margin: 0 4px 2px; font-size: var(--fs-badge); font-weight: var(--fw-strong); color: var(--cz-ink3); }
324
+ .vzr-times { display: flex; justify-content: space-between; margin-top: -6px; font-size: var(--fs-meta); font-weight: var(--fw-strong); color: var(--cz-ink3); font-variant-numeric: tabular-nums; line-height: var(--lh-meta); }
325
+ .vzr-tr { position: relative; display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 60px; }
326
+ .vzr-L { position: absolute; left: 0; }
327
+ .vzr-R { position: absolute; right: 0; display: flex; gap: 6px; }
328
+ .vzr-speed { height: 28px; padding: 0 8px !important; border-radius: 999px; color: var(--cz-ink2); font-size: var(--fs-label); font-weight: var(--fw-strong); display: inline-flex !important; align-items: center; line-height: var(--lh-label); }
329
+ .vzr-ib { width: 36px; height: 36px; border-radius: 50%; display: inline-grid !important; place-items: center; color: var(--cz-ink2); flex: none; }
330
+ .vzr-ib.vzr-o { box-shadow: inset 0 0 0 1px var(--cz-hair2, var(--cz-hair)); }
331
+ .vzr-ib.on, .vzr-ib[aria-pressed="true"], .vzr-ib[aria-expanded="true"] { background: var(--cz-bluebg) !important; color: var(--cz-blueink); box-shadow: none; }
332
+ .vzr-skip { width: 46px; height: 46px; border-radius: 50%; display: inline-grid !important; place-items: center; color: var(--cz-ink); }
333
+ .vzr-skip text { font-family: var(--cz-sans); font-size: var(--fs-label); font-weight: var(--fw-strong); }
334
+ .vzr-big { width: 52px; height: 52px; border-radius: 50%; display: inline-grid !important; place-items: center; color: var(--cz-ink); }
335
+ .vzr-big svg { width: 22px; height: 22px; }
336
+ .vzr-big:hover { background: var(--cz-well) !important; }
337
+ .vzr-ib:hover, .vzr-skip:hover, .vzr-speed:hover { background: var(--cz-well) !important; }
338
+ .vzr-cap { display: flex; flex-direction: column; gap: 3px; padding: 2px 2px 0; }
339
+ .vzr-cp, .vzr-cx { font-size: var(--fs-body); color: var(--cz-ink3); line-height: var(--lh-body); }
340
+ .vzr-cn { font-size: var(--fs-panel); font-weight: var(--fw-strong); color: var(--cz-ink); line-height: 1.35; }
341
+ .vzr-cw { display: inline-block; margin-right: 8px; padding: 1px 9px; border-radius: 999px; background: var(--cz-bluebg); color: var(--cz-blueink); font-size: var(--fs-badge); font-weight: var(--fw-strong); vertical-align: 1px; line-height: var(--lh-badge); }
342
+ .vzr-film { display: flex; gap: 8px; align-items: center; overflow: hidden; }
343
+ .vzr-film.all { flex-wrap: wrap; }
344
+ .vzr-th { width: 84px !important; height: 52px !important; border-radius: 9px !important; border: 0 !important; box-shadow: 0 0 0 1px var(--cz-hair); background: var(--cz-well) !important; overflow: hidden; }
345
+ .vzr-th .vzr-crop { height: 100%; aspect-ratio: auto !important; }
346
+ .vzr-th.on { box-shadow: 0 0 0 2.5px var(--cz-blue); }
347
+ .vzr-more { min-height: 44px; padding: 0 6px !important; font-size: var(--fs-meta); font-weight: var(--fw-strong); color: var(--cz-ink3); line-height: var(--lh-meta); }
348
+ .vzr-chw { position: relative; height: 0; z-index: 7; }
349
+ .vzr-chs { position: absolute; right: 0; top: -46px; width: min(520px, 100%); max-height: 352px; overflow: auto; display: flex; flex-direction: column; gap: 1px; padding: 8px; border-radius: 22px; background: var(--cz-card); box-shadow: 0 24px 60px -24px color-mix(in oklab, var(--cz-ink) 50%, transparent), 0 0 0 1px var(--cz-hair); }
350
+ .vzr-chh { display: flex; align-items: center; gap: 8px; padding: 6px 12px; }
351
+ .vzr-lbl { font-size: var(--fs-badge); font-weight: var(--fw-strong); color: var(--cz-ink3); line-height: var(--lh-badge); }
352
+ .vzr-hint0 { font-size: var(--fs-meta); color: var(--cz-ink3); line-height: var(--lh-meta); }
353
+ .vzr-chr { display: grid !important; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; align-items: center; min-height: 34px; padding: 7px 10px !important; border-radius: 12px; font-size: var(--fs-body); text-align: left !important; line-height: var(--lh-body); }
354
+ .vzr-chr b { font-weight: var(--fw-strong); color: var(--cz-ink); }
355
+ .vzr-chr.cur { background: var(--cz-bluebg) !important; }
356
+ .vzr-chr.cur b { font-weight: var(--fw-strong); }
357
+ .vzr-chr:hover { background: var(--cz-well) !important; }
358
+ .vzr-chm { display: inline-flex; gap: 5px; align-items: center; min-width: 30px; justify-content: flex-end; }
359
+ .vzr-chm .vzr-k { width: 14px; height: 14px; }
360
+ .vzr-chm .vzr-k svg { width: 14px; height: 14px; }
361
+ .vzr-cht { font-size: var(--fs-meta); color: var(--cz-ink3); font-variant-numeric: tabular-nums; line-height: var(--lh-meta); }
362
+ .vzr-ra { grid-column: 2 / 5; display: flex; justify-content: flex-end; gap: 6px; margin-top: 6px; }
363
+ .vzr-edit { grid-column: 1 / -1; margin-top: 8px; }
364
+ .vzr-row .vzp-kt { cursor: pointer; user-select: text; -webkit-user-select: text; }
365
+ .vzr-row .vzp-kt:focus-visible { outline: 2px solid var(--cz-blue); outline-offset: 2px; border-radius: 6px; }
366
+ .vzr-hint { margin: -4px 6px 0; font-size: var(--fs-meta); color: var(--cz-ink3); line-height: var(--lh-meta); }
367
+ .vzr-phk { display: flex; flex-direction: column; gap: 4px; }
368
+ .vzr-phk .vzr-cf { display: none !important; }
369
+ .vzr-phk .vzp-kr.vzr-row { grid-template-columns: 20px minmax(0, 1fr) auto; }
370
+
371
+ /* ── word fix A (MpW1 popup, MpW2 after, MpWPhone sheet): a selected word, the popup, the toast, Names and terms ── */
372
+ .vzr-fx { color: var(--cz-ok); font-weight: var(--fw-strong); border-bottom: 2px dotted color-mix(in oklab, var(--cz-ok) 50%, transparent); }
373
+ .vzr-selw { background: color-mix(in oklab, var(--cz-blue) 28%, transparent); border-radius: 4px; padding: 0 1px; box-shadow: 0 0 0 1px color-mix(in oklab, var(--cz-blue) 60%, transparent); }
374
+ .vzr-wpop { position: fixed; z-index: 40; display: flex; flex-direction: column; gap: 9px; width: 380px; padding: 12px 12px 12px 14px; border-radius: 20px; background: var(--cz-card); color: var(--cz-ink); box-shadow: 0 24px 60px -24px color-mix(in oklab, var(--cz-ink) 50%, transparent), 0 0 0 1px var(--cz-hair); font-size: var(--fs-body); line-height: var(--lh-body); }
375
+ .vzr-wpop svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
376
+ .vzr-wpop p { margin: 0; }
377
+ .vzr-grab { display: none; }
378
+ .vzr-wl { font-size: var(--fs-badge); font-weight: var(--fw-strong); color: var(--cz-ink3); line-height: var(--lh-badge); }
379
+ .vzr-wf { display: flex; align-items: center; gap: 8px; height: 46px; padding: 4px 4px 4px 14px; border-radius: 14px; background: var(--cz-well); }
380
+ .vzr-wf input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font: inherit; font-size: var(--fs-field); font-weight: var(--fw-strong); color: var(--cz-ink); }
381
+ .vzr-go { width: 38px; height: 38px; border-radius: 50%; display: inline-grid !important; place-items: center; flex: none; color: var(--cz-ink); border: 0 !important; box-shadow: none !important; }
382
+ .vzr-go:hover { background: var(--cz-card) !important; }
383
+ .vzr-sug { display: flex; gap: 8px; align-items: flex-start; font-size: var(--fs-meta); color: var(--cz-ink2); line-height: var(--lh-meta); }
384
+ .vzr-star { color: var(--cz-blueink); font-weight: var(--fw-strong); font-size: var(--fs-section); line-height: 1.2; }
385
+ .vzr-alt { display: flex; gap: 6px; flex-wrap: wrap; }
386
+ .vzr-alt button { min-height: 28px; padding: 0 11px !important; border-radius: 999px; background: var(--cz-well) !important; color: var(--cz-ink2); font-size: var(--fs-meta); font-weight: var(--fw-strong); display: inline-flex !important; align-items: center; line-height: var(--lh-meta); border: 0 !important; box-shadow: none !important; }
387
+ .vzr-alt button[aria-pressed="true"] { background: var(--cz-bluebg) !important; color: var(--cz-blueink); }
388
+ .vzr-scope { font-size: var(--fs-meta); color: var(--cz-ink2); line-height: var(--lh-meta); }
389
+ .vzr-scope b { font-weight: var(--fw-strong); color: var(--cz-ink); }
390
+ .vzr-brk { display: flex; gap: 6px; flex-wrap: wrap; }
391
+ .vzr-brk span { padding: 2px 9px; border-radius: 999px; background: var(--cz-well); font-size: var(--fs-badge); font-weight: var(--fw-strong); color: var(--cz-ink3); line-height: var(--lh-badge); }
392
+ .vzr-arr { position: absolute; left: var(--arr, 34px); top: -6px; width: 12px; height: 12px; background: var(--cz-card); transform: rotate(45deg); box-shadow: -1px -1px 0 0 var(--cz-hair); }
393
+ .vzr-wpop.up .vzr-arr { top: auto; bottom: -6px; box-shadow: 1px 1px 0 0 var(--cz-hair); }
394
+ .vzr-wpop.sheet { left: 0 !important; right: 0; bottom: 0; top: auto !important; width: auto !important; padding: 10px 16px 26px; border-radius: 28px 28px 0 0; box-shadow: 0 -20px 50px -20px color-mix(in oklab, var(--cz-ink) 35%, transparent), 0 0 0 100vmax color-mix(in oklab, var(--cz-ink) 12%, transparent); }
395
+ .vzr-wpop.sheet .vzr-grab { display: block; align-self: center; width: 40px; height: 5px; border-radius: 3px; background: var(--cz-hair); }
396
+ .vzr-wpop.sheet .vzr-arr { display: none; }
397
+ .vzr-toastw { position: sticky; bottom: 16px; z-index: 30; display: flex; justify-content: center; pointer-events: none; margin-top: 8px; }
398
+ .vzr-toast { pointer-events: auto; display: inline-flex; align-items: center; gap: 12px; margin: 0; padding: 6px 6px 6px 18px; border-radius: 999px; background: var(--cz-ink); color: var(--cz-card); font-size: var(--fs-body); white-space: nowrap; box-shadow: 0 10px 30px -10px color-mix(in oklab, var(--cz-ink) 60%, transparent); line-height: var(--lh-body); }
399
+ .vzr-toast:not(:has(button)) { padding-right: 18px; }
400
+ .vzr-toast button { min-height: 34px; padding: 0 14px !important; border-radius: 999px; background: color-mix(in oklab, var(--cz-card) 16%, transparent) !important; color: var(--cz-card); font-weight: var(--fw-strong); }
401
+ .vzr-trow { align-items: center; }
402
+ .vzr-fxn { color: var(--cz-ok); }
403
+ .vzr-ask { display: flex; align-items: center; gap: 10px; padding: 9px 10px 9px 14px; border-radius: 16px; background: var(--cz-bluebg); font-size: var(--fs-body); line-height: var(--lh-body); }
404
+ .vzr-ask .vzr-star { font-size: var(--fs-panel); }
405
+ .vzr-ask svg, .vzr-trow svg { fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
406
+ .vzr-a { width: 34px; height: 34px; border-radius: 50%; display: inline-grid !important; place-items: center; flex: none; background: var(--cz-card) !important; color: var(--cz-ink2); box-shadow: 0 0 0 1px var(--cz-hair); }
407
+ .vzr-a.y { background: var(--cz-blueink) !important; color: var(--cz-card); box-shadow: none; }
408
+ @media (max-width: 640px) { .vzr-art .vzr-crop { --mh: 240px; } .vzr-big { width: 48px; height: 48px; } }
409
+
312
410
  /* one Replay at a time: the phone's below 640px, else the desk's */
313
411
  .vzp.vzp-mb { display: none; }
314
412
  @media (max-width: 640px) {
@@ -96,9 +96,10 @@ export function quarterOf(Q, state = 'morning', now = Date.now()) {
96
96
  const ok = okOf(c), line = Number.isFinite(Q.line) && Q.line > 0 && Q.line <= total ? Q.line : null;
97
97
  const m = Q.morning && Q.morning.counts && totalOf(Q.morning.counts) ? okOf(Q.morning.counts) : Number.isFinite(Q.morningOk) ? Q.morningOk : null;
98
98
  const moved = m != null && m !== ok;
99
- if (state === 'soon' && !moved) return null;
99
+ if ((state === 'soon' || state === 'evening') && !moved) return null; // later in the day the strip says only how it moved (G2, G3)
100
100
  const short = line ? line - ok : 0;
101
- const verdict = plural(ok, 'goal', 'goals') + ' on track or better.';
101
+ const noneRead = (c.reading || 0) === total; // nothing read yet: say that, not "0 goals on track" (W2: no signal is not a state)
102
+ const verdict = noneRead ? (total === 1 ? 'Your one goal has no reading yet.' : plural(total, 'goal', 'goals') + ', none read yet.') : plural(ok, 'goal', 'goals') + ' on track or better.';
102
103
  const vline = !line ? '' : short <= 0 ? 'At or above your line of ' + line + '.' : state === 'morning' || !moved ? 'You set the line at ' + line + '.'
103
104
  : (short === 1 ? 'One' : String(short)) + ' short of your line.';
104
105
  // why: the morning names the product that makes most of the gap; later the day says how it moved
@@ -135,9 +136,14 @@ const tagOf = (tags, ws, ini) => { const g = tags && ini ? tags[(ws || '') + ':'
135
136
  /** The compressed agenda: [{ kind: 'now'|'meet'|'pick'|'gap', time, title, sub, past, key, meeting, notes }]. Not to scale: the now line,
136
137
  * each meeting with its prep note, ✦ picks in the free time after now, and a free stretch of 90 minutes or more as "3 h free".
137
138
  * o: { key: the meeting id the call is decided in, picks: [{ title, sub, yd, key }] (one per free stretch, in order), notes: rows, nowLine } */
139
+ /** An all-day entry (leave, a holiday, an offsite): 12 hours or more. One line over the day, never a 00:00 meeting; it never counts as a meeting. */
140
+ export const allDay = (m) => { const s = span(m); return s.b - s.a >= 12 * 60 * MIN; };
138
141
  export function agendaOf(t, now, o = {}) {
139
- const L = (t || []).filter((m) => m && m.start && !Number.isNaN(Date.parse(m.start))).sort((x, y) => Date.parse(x.start) - Date.parse(y.start));
140
- const out = []; const picks = (o.picks || []).slice();
142
+ const all = (t || []).filter((m) => m && m.start && !Number.isNaN(Date.parse(m.start))).sort((x, y) => Date.parse(x.start) - Date.parse(y.start));
143
+ // an all-day event (leave, a holiday, an offsite: 12 hours or more) is one line on top, never a 00:00 meeting that fills the day
144
+ // (Wawan, 2026-11-04: "On Annual Leave" took the whole rail and left no free time)
145
+ const L = all.filter((m) => !allDay(m));
146
+ const out = all.filter(allDay).map((m) => ({ kind: 'allday', title: m.title || 'All day' })); const picks = (o.picks || []).slice();
141
147
  const d1 = new Date(now); d1.setHours(18, 0, 0, 0);
142
148
  let cur = Math.max(now, (() => { const d = new Date(now); d.setHours(9, 0, 0, 0); return d.getTime(); })());
143
149
  let nowShown = !o.nowLine;
@@ -193,7 +199,7 @@ export function notesOf(rows, now, o = {}) {
193
199
  /** The page's model. Y: dayModel's answer (Y.rank from W3); D: Home's data; o: { quarter, tags, notes, picks, today: meetings }. */
194
200
  export function gmModel(Y, D, now = Date.now(), o = {}) {
195
201
  D = D || {};
196
- const t = (o.today || []).filter((m) => m && m.start && !Number.isNaN(Date.parse(m.start))).sort((x, y) => Date.parse(x.start) - Date.parse(y.start));
202
+ const t = (o.today || []).filter((m) => m && m.start && !Number.isNaN(Date.parse(m.start)) && !allDay(m)).sort((x, y) => Date.parse(x.start) - Date.parse(y.start));
197
203
  const state = stateOf(t, now), d = new Date(now), h = d.getHours();
198
204
  const name = D.today && D.today.me ? String(D.today.me) : '';
199
205
  const hello = (h < 12 ? 'Good morning' : h < 17 ? 'Good afternoon' : 'Good evening') + (name ? ', ' + name : '');
@@ -213,20 +219,23 @@ export function gmModel(Y, D, now = Date.now(), o = {}) {
213
219
  const move = ranked.slice(0, 3).map((r) => ({ ...r, goal: tagOf(o.tags, r.ws, r.ini), late: r.rank <= 2 && r.days != null && r.days > 0 }));
214
220
  const more = ranked.length - move.length + (Y.rank ? Y.rank.older.length : 0);
215
221
 
216
- // the lead: honest first; a call that moves a goal ties today to the quarter; else Your day's own line
217
- let line = Y.line, sub = Y.sub;
218
- if (state === 'soon' && Y.mood !== 'concerned') {
222
+ // the lead: honest first; a call that moves a goal ties today to the quarter; else Your day's own line. A failed source is one quiet line
223
+ // under the lead, never the lead itself (G1, G3), and the face is never happier than the data
224
+ // a day read from a stale source cannot say "That was everything": it says what is on record (its sub), with a calm face
225
+ const B = Y.failLead && Y.calm ? (Y.calm.mood === 'delighted' ? { mood: 'calm', line: Y.calm.sub, sub: '' } : { ...Y.calm, mood: Y.calm.mood === 'glad' ? 'calm' : Y.calm.mood }) : Y;
226
+ let line = B.line, sub = B.sub;
227
+ if (state === 'soon' && B.mood !== 'concerned') {
219
228
  const nx = t.find((m) => m.state !== 'past' && m.state !== 'now' && span(m).a > now);
220
229
  const n = Math.max(1, Math.round((span(nx).a - now) / MIN));
221
230
  line = (nx.title || 'Your next meeting') + ' in ' + plural(n, 'minute', 'minutes') + '.';
222
231
  sub = Y.chips.length ? '' : Y.sub && !/meeting/.test(Y.sub) ? Y.sub : '';
223
- } else if (state === 'morning' && Y.mood !== 'concerned' && quarter && quarter.below && fx && better(fx.from, fx.to) && meet) {
232
+ } else if (state === 'morning' && B.mood !== 'concerned' && quarter && quarter.below && fx && better(fx.from, fx.to) && meet) {
224
233
  line = 'Today can close part of the gap: your call is decided at ' + hm(meet.start) + '.'; // the call itself is said once, below
225
234
  }
226
235
  // the header already greets: the lead never says "Morning, Wawan." again (say it once); a festival's wish stays
227
236
  const g = /^(?:Morning|Afternoon|Evening)(?:, [^.]+)?\.\s*/;
228
237
  if (g.test(line)) { line = line.replace(g, ''); if (!line) { line = sub || ''; sub = ''; } }
229
- const lead = { ...Y, line, sub, did: Y.chips.length ? 'Done for you: ' + Y.chips.join(', ') : '' };
238
+ const lead = { ...Y, mood: B.mood, face: B === Y ? Y.face : B.mood, failLead: false, line, sub, did: Y.chips.length ? 'Done for you: ' + Y.chips.join(', ') : '' };
230
239
 
231
240
  // the next meeting's prep card (G2): what to agree, its bullets, Record as an icon
232
241
  let prep = null;
@@ -249,17 +258,17 @@ export function gmModel(Y, D, now = Date.now(), o = {}) {
249
258
  const freeMins = gapsOf(t, now).reduce((n, g) => n + Math.round((g.b - g.a) / MIN), 0);
250
259
  const tm = Y.tomorrow || (state === 'evening' && D.phoneDay && D.phoneDay.tomorrow && Array.isArray(D.phoneDay.tomorrow.meetings) ? { label: D.phoneDay.tomorrow.label || 'Tomorrow', meetings: D.phoneDay.tomorrow.meetings } : null);
251
260
  const rail = state === 'evening'
252
- ? { title: 'Tomorrow', sub: tm && tm.meetings.length ? (tm.label && tm.label !== 'Tomorrow' ? tm.label + ' · ' : '') + plural(tm.meetings.length, 'meeting', 'meetings') : '', rows: tm ? agendaOf(tm.meetings, dayStart(now) + DAY + 8 * 3600e3, {}) : [] }
261
+ ? { title: 'Tomorrow', sub: tm && tm.meetings.length ? (tm.label && tm.label !== 'Tomorrow' ? tm.label + ' · ' : '') + (tm.meetings.some((m) => !allDay(m)) ? plural(tm.meetings.filter((m) => !allDay(m)).length, 'meeting', 'meetings') : 'no meetings') : '', rows: tm ? agendaOf(tm.meetings, dayStart(now) + DAY + 8 * 3600e3, {}) : [] }
253
262
  : { title: 'Today', sub: state === 'soon' ? (() => { const nx = t.find((m) => m.state !== 'past' && span(m).a > now); return nx ? 'next at ' + hm(nx.start) : ''; })()
254
263
  : [t.length ? plural(t.length, 'meeting', 'meetings') : '', freeMins >= 60 ? dur(freeMins) + ' free' : ''].filter(Boolean).join(' · '),
255
264
  rows: agendaOf(t, now, { key: meet && meet.id, picks: state === 'morning' ? picks : picks.slice(1), notes: o.notes, nowLine: true }) };
256
- if (!rail.rows.some((r) => r.kind === 'meet' || r.kind === 'pick')) rail.rows = [];
265
+ if (!rail.rows.some((r) => r.kind === 'meet' || r.kind === 'pick' || r.kind === 'allday')) rail.rows = [];
257
266
  const notes = state === 'soon' ? [] : notesOf(o.notes, now, { today: state === 'evening', picks: o.picks });
258
267
 
259
268
  // Since yesterday: up to 3 of New for you, neutral dots; orange only for a real rise (an item that says so)
260
269
  const since = state === 'soon' ? [] : (Y.news || []).slice(0, 3); // the hour before a meeting shows no Since yesterday at all (G2)
261
270
  return { state, date, hello, quarter, lead, call, move, more, made, prep, rail, notes, since, newsN: state === 'soon' ? 0 : (Y.news || []).length + (Y.digests || 0),
262
- drafts: Y.drafts.length, wait: Y.wait.length, concerned: Y.mood === 'concerned' };
271
+ drafts: Y.drafts.length, wait: Y.wait.length, concerned: B.mood === 'concerned', confirm: state === 'evening' && rc && rc.to_confirm > 0 ? rc.to_confirm : 0 };
263
272
  }
264
273
 
265
274
  /* ── markup ── */
@@ -286,6 +295,7 @@ function rowHtml(r) {
286
295
  const doneHtml = (r) => `<div class="gm-r done"><span class="gm-tc dn" aria-hidden="true">${svg(CHECK, 13, 2.6)}</span><span class="gm-rt"><b>${esc(r.title)}</b>${r.goal ? `<span class="gm-gt"><i class="d-o"></i>${esc(r.goal)}</span>` : r.sub ? `<em>${esc(r.sub)}</em>` : ''}</span></div>`;
287
296
  function agendaHtml(rows) {
288
297
  return rows.map((x) => {
298
+ if (x.kind === 'allday') return `<div class="gm-ar"><span class="gm-tm">All day</span><div class="gm-ad">${esc(x.title)}</div></div>`;
289
299
  if (x.kind === 'now') return `<div class="gm-now"><span>${esc(x.time)}</span><i></i></div>`;
290
300
  if (x.kind === 'gap') return `<div class="gm-ar"><span></span><div class="gm-gap">${esc(x.title)}</div></div>`;
291
301
  if (x.kind === 'pick') return `<div class="gm-ar"><span class="gm-tm">${esc(x.time)}</span><button type="button" class="gm-ab sp" data-yd="${esc(x.yd)}" data-key="${esc(x.key)}"${x.yd === 'gm-fold' ? ` data-fold="${esc(x.key)}"` : ''}><b>${esc(x.title)}</b><em>${esc(x.sub)}</em></button></div>`;
@@ -301,17 +311,20 @@ function notesHtml(N) {
301
311
  export function gmHtml(G, openHtml, o = {}) {
302
312
  const head = `<header class="gm-hd"><span class="gm-k">${esc(G.date)}</span><h1>${esc(G.hello)}</h1></header>`;
303
313
  const q = G.quarter ? quarterHtml(G.quarter) : '';
304
- let a = `<div class="gm-lead">${openHtml(G.lead, { ...o, gm: true })}</div>`;
314
+ let a = `<div class="gm-lead">${openHtml(G.lead, { ...o, gm: true, scene: !!o.scene })}</div>`;
305
315
  if (G.prep) {
306
316
  const P = G.prep;
307
317
  a += `<section class="gm-prep"><span class="gm-k">${esc(P.k)}</span>${P.head ? `<h2>${esc(P.head)}</h2>` : ''}${P.bullets.length ? `<ul>${P.bullets.map((b) => `<li>${esc(b)}</li>`).join('')}</ul>` : ''}`
308
318
  + `<div class="gm-pr"><button type="button" class="gm-rec" data-yd="gm-rec" data-id="${esc(P.meeting.id || '')}" aria-label="Record this meeting" title="Record">${svg(MIC, 18, 2)}</button>${P.files ? `<em>${esc(P.files)}</em>` : ''}</div></section>`;
309
319
  }
310
- if (G.call && G.state !== 'evening') {
320
+ if (G.call) { // a call still open in the evening is still the PM's: it stays (G3 shows none only because it was decided)
311
321
  a += `<section class="gm-sec"><h3 class="gm-sh">Your call</h3><div class="gm-call yd-call${G.call.blocked ? ' am' : ''}" data-id="${esc(G.call.id)}"><div><b>${esc(G.call.name)}</b>${G.call.why ? `<em>${esc(G.call.why)}</em>` : ''}</div>`
312
322
  + `<button type="button" class="yd-alt" data-yd="sel" aria-label="${esc('Open: ' + G.call.name)}" title="Open">${svg(CHEV, 18, 2)}</button></div></section>`;
313
323
  }
314
- if (G.made.length) a += `<section class="gm-sec"><h3 class="gm-sh">Today made possible</h3>${G.made.map(doneHtml).join('')}</section>`;
324
+ // a decision caught in today's notes counts only once the PM confirms it (GET /api/phone-day recap.to_confirm): until then it is one quiet line
325
+ const conf = G.confirm ? `<div class="gm-more"><button type="button" data-yd="gm-notes"><b>${G.confirm}</b> ${G.confirm === 1 ? 'decision' : 'decisions'} from today’s notes to confirm${svg(CHEV, 14, 2)}</button></div>` : '';
326
+ if (G.made.length) a += `<section class="gm-sec"><h3 class="gm-sh">Today made possible</h3>${G.made.map(doneHtml).join('')}${conf}</section>`;
327
+ else a += conf;
315
328
  const quiet = [G.more > 0 && G.state !== 'evening' ? `<button type="button" data-yd="gm-fold" data-fold="more"><b>${G.more}</b> more in Your move${svg(CHEV, 14, 2)}</button>` : '',
316
329
  G.drafts ? `<button type="button" data-yd="gm-fold" data-fold="drafts"><b>${G.drafts}</b> drafted for you${svg(CHEV, 14, 2)}</button>` : '',
317
330
  G.wait ? `<button type="button" data-yd="gm-fold" data-fold="wait"><b>${G.wait}</b> can wait${svg(CHEV, 14, 2)}</button>` : ''].filter(Boolean).join('');
@@ -321,7 +334,7 @@ export function gmHtml(G, openHtml, o = {}) {
321
334
  + `<div class="gm-sl">${G.since.map((it) => `<button type="button" class="gm-si${it.rise ? ' hot' : ''}" data-yd="news" data-id="${esc(it.id)}"><i aria-hidden="true"></i><span>${esc(it.kind === 'task' ? String(it.title || '').replace(/^New task: /, '') : it.title)}</span>${it.at ? `<em>${esc(at(it.at))}</em>` : ''}</button>`).join('')}</div>`
322
335
  + `<button type="button" class="gm-lnk" data-yd="news-all">New for you · ${G.newsN}</button></section>` : '';
323
336
  const R = G.rail;
324
- const rail = R.rows.length || G.notes.length ? `<aside class="gm-rail"><div class="gm-rh"><b>${esc(R.title)}</b>${R.sub ? `<span>${esc(R.sub)}</span>` : ''}</div>${R.rows.length ? `<div class="gm-ag">${agendaHtml(R.rows)}</div>` : ''}`
337
+ const rail = R.rows.length || G.notes.length ? `<aside class="gm-rail"><div class="gm-rh"><b>${esc(R.title)}</b>${R.sub ? `<span>${esc(R.sub)}</span>` : ''}<button type="button" class="gm-lnk" data-yd="gm-cal">Calendar</button></div>${R.rows.length ? `<div class="gm-ag">${agendaHtml(R.rows)}</div>` : ''}`
325
338
  + (G.notes.length ? `<div class="gm-notes"><h3 class="gm-sh">${G.state === 'evening' ? 'Notes today' : 'Notes since yesterday'}<button type="button" class="gm-lnk" data-yd="gm-notes">All notes</button></h3>${notesHtml(G.notes)}</div>` : '') + '</aside>' : '';
326
339
  return `<div class="gm gm-${esc(G.state)}">${head}${q}<div class="gm-x${rail ? '' : ' solo'}"><div class="gm-a">${a}</div>${rail}${b ? `<div class="gm-b">${b}</div>` : ''}</div></div>`;
327
340
  }
@@ -477,8 +477,10 @@ async function readNotePicks() {
477
477
  async function gmPress(b) {
478
478
  const act = b.dataset.yd, body = b.closest('.yd');
479
479
  if (act === 'gm-intel') { location.href = insightsHref(deskWs()); return; }
480
+ if (act === 'gm-cal') { openCalendar(); return; }
480
481
  if (act === 'gm-fold') {
481
482
  const sec = body && body.querySelector('.czs-sec[data-czs-id="' + b.dataset.fold + '"]'); if (!sec) return;
483
+ sec.classList.add('gm-on'); // the fold the quiet line names is drawn only once it is pressed (say it once)
482
484
  const f = sec.querySelector('[data-czs-fold]'); if (f && f.getAttribute('aria-expanded') !== 'true') f.click();
483
485
  sec.scrollIntoView({ behavior: 'smooth', block: 'start' }); return;
484
486
  }
@@ -1258,6 +1260,13 @@ function programTarget(id, task) {
1258
1260
  empty: 'Ask about ' + pr.title + '. Claude reads the program and its initiatives in a real Claude Code session.',
1259
1261
  context: 'Program ' + pr.title + ' (' + id + ')' + (inis.length ? ', with the initiatives ' + inis.join(', ') : '') + '.' + (task ? ' The PM opened its task "' + task.name + '"' + (task.hint ? ' (' + task.hint + ')' : '') + '.' : ''), also: null };
1260
1262
  }
1263
+ /** A chat Intelligence asks for (?ask=insights|verdict): titled from the goal it names (?label), scoped to the product's goals, never "ins:insights"
1264
+ * and never "All work" (workspace grill W4). */
1265
+ function insTarget(key) {
1266
+ const prod = (S.D && H.productName(S.D)) || 'Product', label = (P.get('label') || '').slice(0, 80) || prod + ' goals';
1267
+ return { ...homeTarget(), key, title: label, sub: key === 'ins:verdict' ? 'Outcome check' : 'Goals', scope: { text: prod + ' goals', icon: I.chart },
1268
+ placeholder: 'Message Claude about ' + label, context: 'The PM asked from Intelligence about ' + label + ', one of the goals of ' + prod + '. Answer from that product\'s goals and readings.' };
1269
+ }
1261
1270
  function homeTarget() {
1262
1271
  return { key: 'chat:main', ws: WS || undefined, title: 'Home', scope: { text: 'All work', icon: I.home }, rich: false, placeholder: 'Message Claude', role: CLAUDE,
1263
1272
  empty: 'Ask anything about your work. Claude reads your repositories and works in a real Claude Code session.', also: alsoOf(null),
@@ -2182,7 +2191,7 @@ drawList(); drawTop();
2182
2191
  const a = P.get('about') || '', early = /^(decision|blocked|waited|src|ev|work|board|doc|ins|chat|pop):[A-Za-z0-9][A-Za-z0-9._:-]{0,100}$/.test(a) && !P.get('open') && !P.get('session') ? a : null;
2183
2192
  const sidAt = !early && !P.get('open') && /^[A-Za-z0-9._:-]{1,120}$/.test(P.get('session') || ''); // a session link: read nothing until its session is known, so Home's own chat never shows first
2184
2193
  if (early || sidAt) { S.phoneChat = isPhone(); drawList(); } // a phone: a conversation the address names is the page, not the list (HmPhone2)
2185
- openTarget(early ? { ...homeTarget(), key: early, title: (P.get('label') || '').slice(0, 80) || early }
2194
+ openTarget(early ? (/^ins:(insights|verdict)$/.test(early) ? insTarget(early) : { ...homeTarget(), key: early, title: (P.get('label') || '').slice(0, 80) || early })
2186
2195
  : sidAt ? { ...homeTarget(), key: '', title: 'Reading', empty: 'Reading the session.' }
2187
2196
  : S.ini ? (S.ini === 'product' ? { ...homeTarget(), title: 'Reading' } : iniTarget(S.ini)) : homeTarget());
2188
2197
  }
@@ -2218,6 +2227,7 @@ load().then(() => {
2218
2227
  // the Board's program panel Discuss in chat names board:program:<id>: the program's own conversation, the one its tasks open (bug 2026-10-24)
2219
2228
  else if (about && /^board:program:[A-Za-z0-9][A-Za-z0-9._-]{0,100}$/.test(about)) openTarget(programTarget(about.slice(14)));
2220
2229
  else if (handed) chatAbout(handed); // a report's chat icon on another page
2230
+ else if (about && /^ins:(insights|verdict)$/.test(about)) openTarget(insTarget(about));
2221
2231
  else if (about) { openTarget({ ...homeTarget(), key: about, title: (P.get('label') || '').slice(0, 80) || about }); discussed(about, P.get('vid'), P.get('label')); }
2222
2232
  else if (P.get('view') === 'metrics') openMetrics(P.get('ws') || ''); // ?view=metrics: an old link lands on Intelligence › Delivery trends
2223
2233
  else if (S.ini) goIni(S.ini);
@@ -122,6 +122,9 @@ export const boardRows = (D) => (D.board && D.board.initiatives && D.board.initi
122
122
  export const live = (s) => s.shown !== 'exited' && s.state !== 'exited';
123
123
  /** A session Home lists: a live one, or a chat that rests until its next message. One the PM put away or finished from the clean-up
124
124
  * (batch 13 item 3: s.away) is not listed until the PM's next message in it; Search still finds it. */
125
+ /** The idle shell the workspace opens at startup (lib/first-shell.js) is the terminal's, not the PM's work: Home never lists it under
126
+ * "Not in an initiative" (workspace grill W4). A shell the PM named, or one PMOS started for a job, keeps its row. */
127
+ export const startShell = (s) => !!(s && s.agent === 'shell' && (s.origin === 'first-shell' || (!s.origin && String(s.name || '') === 'shell')) && s.name_by !== 'pm');
125
128
  export const listed = (s) => (live(s) || resting(s)) && !(s && s.away);
126
129
  /** The kids of one initiative: its needs, its live sessions and today's meetings about it, the loudest first. */
127
130
  export function kidsOf(D, id) {
@@ -276,7 +279,7 @@ export function homeLists(D, { allNeeds = false } = {}) {
276
279
  const inis = boardRows(D).filter(OPEN).map((r) => iniRow(D, r));
277
280
  inis.sort((a, b) => (RANK[b.state] || 0) - (RANK[a.state] || 0));
278
281
  const prod = boardRows(D).length ? productRow(D) : null;
279
- const loose = (D.sessions || []).filter((s) => listed(s) && !iniOfSession(s) && s.shown !== 'needs' && s.shown !== 'blocked').map((s) => ({ ...sessionRow(s, D.desk && D.desk.roles), loose: true }))
282
+ const loose = (D.sessions || []).filter((s) => listed(s) && !iniOfSession(s) && !startShell(s) && s.shown !== 'needs' && s.shown !== 'blocked').map((s) => ({ ...sessionRow(s, D.desk && D.desk.roles), loose: true }))
280
283
  .sort((a, b) => (RANK[b.state] || 0) - (RANK[a.state] || 0));
281
284
  return { needs: { rows: allNeeds ? needs : needs.slice(0, 3), more: Math.max(0, needs.length - 3), all: needs.length }, initiatives: prod ? [prod, ...inis] : inis, loose, needN: needs.length };
282
285
  }
@@ -160,11 +160,12 @@ export function dayModel(M, D, R, now = Date.now(), o = {}) {
160
160
  // a source that could not be read (AdP5): said plainly; it leads the card unless Claude's own miss or a stuck call already does
161
161
  const fail = syncFailOf(o.sources, now);
162
162
  let failLead = false;
163
+ const calm = { mood, line, sub }; // Good morning keeps the day's own line and says a failed source in one quiet line (G1, G3)
163
164
  if (fail && mood !== 'concerned') { const w = failWords(fail); mood = 'concerned'; line = w.line; sub = w.sub; failLead = true; }
164
165
  // no prep run today: say so, and offer it (Prepare my day); never claim work that did not happen. No meeting, no prep line (W3)
165
166
  if (!run && h < 17 && t.length) offer = { kind: 'prep', text: 'I haven’t prepared today yet.', sub: 'Shall I prep your meetings and draft what I can?' };
166
167
  return {
167
- mood, face: run && run.working && mood === 'calm' ? 'working' : mood, line, sub, chips, misses, offer, calls, drafts, wait, done, thanks, fail, failLead, ask: o.ask || null,
168
+ mood, face: run && run.working && mood === 'calm' ? 'working' : mood, line, sub, chips, misses, offer, calls, drafts, wait, done, thanks, fail, failLead, calm, ask: o.ask || null,
168
169
  rank: o.rank ? rankMove({ ...o.rank, calls, now }) : null,
169
170
  news: newsOf(o.news), digests: digestsOf(D), tomorrow: tomorrowOf(D, now),
170
171
  next: next ? { meeting: next, now: next.state === 'now', time: hm(next.start), title: next.title || 'A meeting' } : null,
@@ -181,7 +182,8 @@ const alt = (act, label, id, icon) => `<button type="button" class="yd-alt" data
181
182
  /** The opening card: face, line, sub, the chips of real work, and Claude's own misses with a ✓ to run them again. */
182
183
  export function openHtml(Y, o = {}) {
183
184
  // AdP5: a failed source that leads the card draws the small rain cloud in place of the morning scene (Face only: the honest face)
184
- const rain = Y.fail && Y.failLead ? adlerScene('rain', 'honest', { label: 'Adler, honest, under a small rain cloud', h: 170 }) : '';
185
+ // Good morning (o.gm, frames G1–G4): the lead is Adler's face beside the line; the art banner and the rain cloud are not drawn there
186
+ const rain = Y.fail && Y.failLead && !o.gm ? adlerScene('rain', 'honest', { label: 'Adler, honest, under a small rain cloud', h: 170 }) : '';
185
187
  if (rain) o = { ...o, scene: true };
186
188
  // a dressed day's scene is wider (460 x 170), so the card's 150 px shows its garland or lights, not only the ground (AsPlaces)
187
189
  const head = rain ? `<div class="yd-scene">${rain}</div>` : o.scene ? `<div class="yd-scene"${dress() ? ' data-ad-dress role="button" tabindex="0" aria-label="Why Adler is dressed"' : ''}>${adlerScene('morning', 'handled', { label: false, dress: true, ...(dress() ? { w: 460, h: 170 } : {}) })}</div>` : o.big ? '' : faceHtml(Y.face || Y.mood);
@@ -189,7 +191,8 @@ export function openHtml(Y, o = {}) {
189
191
  const chips = o.gm && Y.did ? `<div class="gm-did">${svg(CHECK, 14, 2.6)}${esc(Y.did)}</div>` : Y.chips.length ? `<div class="yd-chips">${Y.chips.map((c) => `<span class="yd-chip">${svg(CHECK, 14, 2.6)}${esc(c)}</span>`).join('')}</div>` : '';
190
192
  const miss = Y.misses.map((m) => `<div class="yd-miss" data-id="${esc(m.id)}"><div class="yd-mt"><b>My miss: ${esc(m.title)}</b>${m.why ? `<em>${esc(m.why)}</em>` : ''}</div>${ok('retry', 'Run it again', m.id)}</div>`).join('');
191
193
  // the failed source under Claude's own miss: one card, no second Adler; its icon reads the source again (Settings › Connectors' ↻)
192
- const sync = Y.fail ? (Y.failLead ? `<div class="yd-sync">${alt('sync', 'Read ' + Y.fail.name + ' again', Y.fail.id, AGAIN)}</div>`
194
+ const sync = Y.fail && o.gm ? (() => { const w = failWords(Y.fail); return `<div class="gm-did gm-src" data-id="${esc(Y.fail.id)}"><span>${esc(w.line)}${Y.fail.from ? ' What I show is ' + esc(Y.fail.from) + '.' : ''}</span>${alt('sync', 'Read ' + Y.fail.name + ' again', Y.fail.id, AGAIN)}</div>`; })()
195
+ : Y.fail ? (Y.failLead ? `<div class="yd-sync">${alt('sync', 'Read ' + Y.fail.name + ' again', Y.fail.id, AGAIN)}</div>`
193
196
  : (() => { const w = failWords(Y.fail); return `<div class="yd-miss yd-sync" data-id="${esc(Y.fail.id)}"><div class="yd-mt"><b>${esc(w.line)}</b><em>${esc(w.sub)}</em></div>${alt('sync', 'Read ' + Y.fail.name + ' again', Y.fail.id, AGAIN)}</div>`; })()) : '';
194
197
  // one ask that waits on the PM in Mentions (batch 17 item 6): who, where, Claude's gist; its icon opens that conversation in Mentions
195
198
  const A = Y.ask, ask = A ? `<div class="yd-miss yd-ask" data-id="${esc(A.id)}"><div class="yd-mt"><b>${esc(A.who)} asked you in ${esc(A.where)}${A.more ? ` · ${A.more} more` : ''}</b><em>${esc(A.gist)}</em></div>${alt('ask', 'Open in Mentions', A.id, CHEV)}</div>` : '';
@@ -248,6 +251,9 @@ const MARK2 = 'M2 12.5l4.5 4.5L15 8M9 15l2 2L22 7';
248
251
  const listOf = (a) => (a.length < 2 ? a.join('') : a.slice(0, -1).join(', ') + ' and ' + a[a.length - 1]);
249
252
  const SPARK = 'M12 3l1.8 4.7L18.5 9.5l-4.7 1.8L12 16l-1.8-4.7L5.5 9.5l4.7-1.8z';
250
253
  export const NEWS_SHOWN = 3;
254
+ const MO = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
255
+ /** A due date in New for you: "25 Nov", with the short year when it is not this year ("9 Jun ’27"), never the ISO date (workspace grill W4). */
256
+ export const dueWords = (iso, now = Date.now()) => { const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso || '')); if (!m) return String(iso || ''); return +m[3] + ' ' + MO[+m[2] - 1] + (+m[1] !== new Date(now).getFullYear() ? ' ’' + m[1].slice(2) : ''); };
251
257
  function newsRow(it, now) {
252
258
  const who = it.kind === 'task' ? (it.from || 'Someone') : (it.product || 'Claude');
253
259
  const when = it.at ? ago(new Date(it.at).toISOString(), now) : '';
@@ -256,7 +262,7 @@ function newsRow(it, now) {
256
262
  // workflow's initiatives (batch 20 item 3, WmB: "Shown under your day, in ✦")
257
263
  const sub = it.kind === 'wf-bridge' ? ['From ' + ((it.from && it.from.name) || 'the old workflow'), (it.groups || []).length ? 'into ' + it.groups.map((g) => g.name).join(', ') : ''].filter(Boolean).join(' · ')
258
264
  : it.kind === 'overlap' ? [it.a && it.a.title, it.b && it.b.title].filter(Boolean).join(' and ') || 'Two open initiatives'
259
- : it.kind === 'task' ? ['Given to you' + (it.from ? ' from ' + it.from : ''), it.due ? 'due ' + it.due : '', it.iniTitle ? 'filed under ' + it.iniTitle : ''].filter(Boolean).join(' · ')
265
+ : it.kind === 'task' ? ['Given to you' + (it.from ? ' from ' + it.from : ''), it.due ? 'due ' + dueWords(it.due, now) : '', it.iniTitle ? 'filed under ' + it.iniTitle : ''].filter(Boolean).join(' · ')
260
266
  : ['Finished while you were away', it.target, when].filter(Boolean).join(' · ');
261
267
  const av = it.kind === 'overlap' || it.kind === 'wf-bridge' ? `<span class="yd-av sp" aria-hidden="true">${svg(SPARK, 13, 2)}</span>` : `<span class="yd-av${it.kind === 'finished' ? ' b' : ''}" aria-hidden="true">${esc(initials(who))}</span>`;
262
268
  return `<button type="button" class="yd-nw" data-yd="news" data-id="${esc(it.id)}"><i class="yd-nd" aria-hidden="true"></i>${av}<span class="yd-mt"><b>${esc(head)}</b><em>${esc(sub)}</em></span></button>`;
@@ -325,7 +331,8 @@ export function gmParts(Y, G, o = {}) {
325
331
  if (o.moment) sections.push({ id: 'now', title: 'Now', open: true, html: '<div class="hd-now"></div>' });
326
332
  sections.push({ id: 'week', title: 'This week', count: Y.week.sub, html: `<div class="hv-card"><div class="hm-r hv-r two" data-id="review"><button type="button" class="hm-rm" data-hm-review aria-label="Weekly review: ${esc(Y.week.sub)}. Open"><span class="hv-mk">${svg(CAL, 16)}</span><span class="hm-rt"><b>Weekly review</b><em>${esc(Y.week.sub)}</em></span><span class="hv-cv">${svg(CHEV, 16)}</span></button></div></div>` });
327
333
  // one Adler per screen: the morning scene on the day's first open before noon, else his face on the lead
328
- const scene = !!o.first && Y.mood !== 'concerned' && G.state === 'morning' && new Date(now).getHours() < 12;
334
+ // the daily morning banner is gone (frames G1-G4: a face beside the line); a festival's own day keeps its scene once (AsPlaces, Deepavali)
335
+ const D = dress(), scene = !!o.first && !!D && !!dressDay(D, now) && Y.mood !== 'concerned' && G.state === 'morning' && new Date(now).getHours() < 12;
329
336
  const night = Y.mood !== 'concerned' && asleep(now) ? nightHtml(Y) : '';
330
337
  return { gm: o.html(G, openHtml, { scene }), sections, night };
331
338
  }
@@ -260,7 +260,7 @@ const sprintFilterOn = () => curView() !== 'time' || span === 'sprint';
260
260
  /** What needs a look (batch 24 item 7, canvas BbB): new asks, late, at risk, blocked and idle are ONE pill of dots and numbers. A press opens the
261
261
  * rows; a row opens what its chip opened (the asks, a metric's trend, whose "Show only" filters); a filter that is on turns off at once.
262
262
  * A row that is zero is not listed, and the pill is not drawn when every row is zero (zero is silence). */
263
- function needHtml(C, open) {
263
+ function needHtml(C, open, phone) {
264
264
  const rows = [];
265
265
  if (C.asks) rows.push({ k: 'asks', dot: 'you m-info', n: C.asks, name: plural(C.asks, 'New ask', 'New asks'), say: C.asks + ' new ' + plural(C.asks, 'ask', 'asks') });
266
266
  for (const [k, tone, mo, name] of CHIP_F) if (C[k]) rows.push({ k, dot: tone + (mo ? ' ' + mo : ''), n: C[k], name, say: C[k] + ' ' + name.toLowerCase() });
@@ -271,15 +271,17 @@ function needHtml(C, open) {
271
271
  const item = (r) => (r.k === 'asks'
272
272
  ? `<button type="button" class="bd-nr" id="chAsks" aria-haspopup="dialog"><i class="cz-dot ${r.dot}"></i>${esc(r.name)}<span class="k">${r.n}</span></button>`
273
273
  : `<button type="button" class="bd-nr${F.chips.has(r.k) ? ' on' : ''}" data-f="${r.k}" aria-pressed="${F.chips.has(r.k)}"><i class="cz-dot ${r.dot}"></i>${esc(r.name)}<span class="k">${r.n}</span></button>`);
274
+ // the words beside each dot: "3 late · 1 at risk" (workspace grill W4); a phone has room for the numbers only
275
+ const parts = rows.map((r) => `<span><i class="cz-dot ${r.dot}"></i>${esc(phone ? String(r.n) : r.say)}</span>`).concat(C.none ? [`<span><i class="cz-dot q"></i>${esc(phone ? String(C.none) : C.none + ' no signal')}</span>`] : []);
274
276
  return `<span class="bd-grpw2 bd-needw" id="bdNeedw"><button type="button" class="bd-chip bd-need${on ? ' on' : ''}" id="chNeed" aria-haspopup="true" aria-expanded="${open}" aria-controls="bdNeedMenu" aria-label="${esc(rows.map((r) => r.say).concat(C.none ? [C.none + ' no signal'] : []).join(', '))}. Show them" title="${esc(rows.map((r) => r.say).join(' · '))}">`
275
- + rows.map((r) => `<span><i class="cz-dot ${r.dot}"></i>${r.n}</span>`).join('') + (C.none ? `<span><i class="cz-dot q"></i>${C.none}</span>` : '') + SV(I.chev) + '</button>'
277
+ + parts.join(phone ? '' : '<span class="bd-nsep" aria-hidden="true">·</span>') + SV(I.chev) + '</button>'
276
278
  + `<span class="bd-grpm bd-needm" id="bdNeedMenu" role="group" aria-label="Needs a look"${open ? '' : ' hidden'}><span class="bd-mlbl">Needs a look</span>${rows.map(item).join('')}${nosig}</span></span>`;
277
279
  }
278
280
  const menuOpen = (id) => !!$('#' + id) && !$('#' + id).hidden;
279
281
  function drawChips() {
280
282
  const J = M.jira || {}, C = M.chips || {}, sp = J.sprint, rel = J.release, phone = small();
281
283
  const jira = J.readAt ? 'Jira · read ' + ago(J.readAt) : 'Jira · not read';
282
- const sprint = [sp ? sp.name : null, rel ? rel.name + ' ships ' + rel.at.replace(/^(\d+) (\w+)$/, '$2 $1') : null].filter(Boolean).join(' · ') || 'No sprint on record';
284
+ const sprint = [sp ? sp.name : null, rel ? rel.name + ' ships ' + rel.at.replace(/^(\d+) (\w+)$/, '$2 $1') : null].filter(Boolean).join(' · ') || ''; // no sprint and no release on record: no chip (workspace grill W4, zero is silence)
283
285
  const thisSprint = sp && (view === 'track' || view === 'time' || phone) && sprintFilterOn() ? `<button type="button" class="bd-chip${F.chips.has('sprint') ? ' on' : ''}" data-f="sprint" aria-pressed="${F.chips.has('sprint')}" title="${F.chips.has('sprint') ? 'Shows only the initiatives with work in ' + esc(sp.name) + '. Press to show all.' : 'Press to show only the initiatives with work in ' + esc(sp.name) + '.'}">${esc(sp.name)} only</button>` : ''; // it named no sprint and read like a heading (testing fixes)
284
286
  // All products (PgBoard): one Jira control with the oldest read of any product, and every product's new asks; the sprint stays per product
285
287
  // AdM2: Adler's S face carries the read's mood: calm when Jira was read within a day and a half, honest when it was not, glasses on while
@@ -288,7 +290,7 @@ function drawChips() {
288
290
  const jMood = jiraReading ? 'thinking' : J.readAt && Date.now() - Date.parse(J.readAt) < 36 * 3600000 ? 'handled' : 'honest';
289
291
  const jSay = jira + '. ' + (ALL ? 'Press to read Jira again for every product' : 'Press to read Jira again');
290
292
  const jiraChip = `<button type="button" class="bd-chip bd-jf" id="chJira" title="${esc(jSay)}" aria-label="${esc(jSay)}">${faceOr(jMood, 's', SV(I.refresh), { label: false })}${jMood === 'honest' ? '<i class="bd-pip" aria-hidden="true"></i>' : ''}<span class="vh">${esc(jira)}</span></button>`;
291
- const need = needHtml(C, menuOpen('bdNeedMenu'));
293
+ const need = needHtml(C, menuOpen('bdNeedMenu'), phone);
292
294
  if (phone) {
293
295
  $('#bdChipset').innerHTML = thisSprint + jiraChip + need + `<button type="button" class="bd-chip" id="chShared">Shared${SH_N ? ' ' + SH_N : ''}</button>`;
294
296
  return;
@@ -298,7 +300,7 @@ function drawChips() {
298
300
  const lo = menuOpen('bdLgdMenu');
299
301
  const lgd = view === 'time' && span !== 'sprint' ? `<span class="bd-grpw2 bd-lgdw"><button type="button" class="bd-ib bd-lgdb" id="bdLgdBtn" aria-haspopup="true" aria-expanded="${lo}" aria-controls="bdLgdMenu" aria-label="What the bars mean" title="What the bars mean">${SV(I.info)}</button>`
300
302
  + `<span class="bd-grpm bd-lgdm" id="bdLgdMenu"${lo ? '' : ' hidden'}><span class="bd-mlbl">What the bars mean</span>${legendHtml(!byProduct && !!groups(visible()), hasHold(visible()), { ...planKeys(visible()), ...gapKeys(visible()), range: visible().some((r) => fcRange(r)) })}</span></span>` : '';
301
- $('#bdChipset').innerHTML = (ALL ? '' : `<button type="button" class="bd-chip" id="chSprint" aria-haspopup="dialog">${SV(I.cal)}<span>${esc(sprint)}</span></button>`)
303
+ $('#bdChipset').innerHTML = (ALL || !sprint ? '' : `<button type="button" class="bd-chip" id="chSprint" aria-haspopup="dialog">${SV(I.cal)}<span>${esc(sprint)}</span></button>`)
302
304
  + thisSprint + '<span class="bd-sp"></span>' + need + lgd + jiraChip;
303
305
  }
304
306
  const closeMenu = (id, btn) => { const m = $('#' + id); if (m && !m.hidden) { m.hidden = true; const b = $('#' + btn); if (b) b.setAttribute('aria-expanded', 'false'); return true; } return false; };
@@ -70,6 +70,14 @@ export function gateOf(steps) {
70
70
  return parts.length ? { label: 'Gate to Build', parts } : null;
71
71
  }
72
72
  const STEP_SAY = { spec: 'PRD and rubric written', contract: 'Your go on the done criteria', review: 'The Review Gate failed: weigh its findings', accept: 'Your sign-off', route: 'Lane chosen', commit: 'Your Commit' };
73
+ /** What each "your turn" press sends, in the PM's words, not the step's internal name ("the contract step", workspace grill W4). */
74
+ const ASK_SAY = {
75
+ contract: 'Show me the done criteria for this initiative: what must be true for it to count as done, and how each one is checked. Say which ones I have not agreed yet, read the evidence in this repository, say what you could not find, and change nothing.',
76
+ review: 'Show me the review findings for this initiative: what passed, what did not, and the evidence for each. Change nothing.',
77
+ accept: 'Is this initiative ready for my sign-off? Show me what was built against its done criteria, say what you could not find, and change nothing.',
78
+ spec: 'Walk me through the PRD for this initiative: the problem, who it is for, and what is still open. Say what you could not find and change nothing.',
79
+ commit: 'Show me the Commit for this initiative: what we promise, by when, and what it depends on. Change nothing.',
80
+ };
73
81
  const CTA_SAY = { contract: 'Review the done criteria', review: 'Open the findings', accept: 'Give your sign-off', spec: 'Ask about the PRD', commit: 'Review the Commit' };
74
82
  const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
75
83
  const cap1 = (s) => (s ? String(s).charAt(0).toUpperCase() + String(s).slice(1) : '');
@@ -165,7 +173,7 @@ export function headModel(M, now = Date.now()) {
165
173
  // one CTA: your step when it waits on you, else a question for the read-only agent
166
174
  const cta = turn === 'you' ? (CTA_SAY[step] || ('Open ' + (step || 'the next step'))) : turn === 'stalled' ? 'Ask what is stuck' : turn === 'jira' ? 'Ask about the tickets' : 'Ask what is next';
167
175
  const ask = turn === 'you' && isFailedReview(m) ? 'The Review Gate failed for this initiative. Read its latest verdict and findings in planning/runs/evals.jsonl, list each failing dimension with the evidence, and say what would fix it. Change nothing.'
168
- : turn === 'you' ? 'What do I need to do for the ' + (step || 'next') + ' step of this initiative? Read the evidence in this repository, say what you could not find, and change nothing.'
176
+ : turn === 'you' ? ASK_SAY[step] || 'What do I need to do next on this initiative? Read the evidence in this repository, say what you could not find, and change nothing.'
169
177
  : turn === 'stalled' ? 'What is stuck in this initiative, and what would move it? Read this repository, say what you could not find, and change nothing.'
170
178
  : turn === 'jira' ? 'Where do this initiative\'s Jira tickets stand, from the last Jira read? Name the assignee of each where the read holds one, and change nothing.'
171
179
  : 'What happens next in this initiative, and who does it? Read this repository and change nothing.';
@@ -80,7 +80,7 @@ const I = {
80
80
  const byKey = (k) => (PF && PF.charts[k]) || (M && (M.charts || []).find((c) => c.key === k)) || null;
81
81
  const savedDash = () => (M.dashboards || []).filter((d) => d.saved);
82
82
  const wsQ = () => (M && M.board.ws ? 'ws=' + encodeURIComponent(M.board.ws) + '&' : '');
83
- const homeAsk = (kind, q) => '/premium.html?' + wsQ() + 'ask=' + encodeURIComponent(kind) + '&q=' + encodeURIComponent(q);
83
+ const homeAsk = (kind, q, label) => '/premium.html?' + wsQ() + 'ask=' + encodeURIComponent(kind) + '&q=' + encodeURIComponent(q) + (label ? '&label=' + encodeURIComponent(String(label).slice(0, 80)) : '');
84
84
  const iniHref = (id) => '/premium-initiative.html?' + wsQ() + 'id=' + encodeURIComponent(id);
85
85
  const SRC_NAME = { analytics: 'Product analytics', jira: 'Jira query', file: 'A file or export', typed: 'You type it in' };
86
86
  /** A goal's KR id as the design says it: "KR <objective>.<its place in the objective>", in the order of the records; '' with no objective. */
@@ -497,7 +497,9 @@ function drawStrip() {
497
497
  const sp = $('#insHero .sp');
498
498
  if (sp && crit) { sp.classList.remove('ad'); sp.innerHTML = OK.SV(OK.P.alert, 20); }
499
499
  else if (sp) { sp.classList.add('ad'); sp.innerHTML = AD.adler(f.report || (f.okr && f.tone === 'drafts') ? 'needs-you' : (f.okr ? f.tone === 'behind' : behind) || /^Straight answer/.test(f.text) || AD.moodOfText(f.text) === 'honest' ? 'honest' : 'handled', 40, { label: false }); }
500
- $('#askWhy').href = homeAsk('insights', 'Why? ' + f.text);
500
+ // Ask why opens a chat titled from the goal the line is about, else the product's goals, never "ins:insights" (workspace grill W4)
501
+ const goalOf = goalsShown().find((g) => g.title && (f.text || '').includes(g.title));
502
+ $('#askWhy').href = homeAsk('insights', 'Why? ' + f.text, goalOf ? goalOf.title : ((M.board && M.board.name) || 'Product') + ' goals');
501
503
  $('#askWhy').hidden = !!(f.report || f.impact); $('#askWhy').style.display = f.report || f.impact ? 'none' : ''; // a report Claude proposes carries its own presses: See the draft, Not now, ✓ Set it up
502
504
  if (f.report) $('#insAct').innerHTML = RI.stripActs(f.report);
503
505
  const mv = !f.report && f.move ? f.move : null;
@@ -1050,7 +1052,7 @@ function openKr(key) {
1050
1052
  + `<p class="note">${esc(k.decision && !k.need ? 'A decision: it is done when it is decided' + (k.due ? ', due ' + md(day(k.due)) : '') + '.' : k.need ? 'Nothing reads it yet: it needs ' + k.need + '. I will not guess a number.' : 'The OKR file names no source for it.')}</p></div>`
1051
1053
  + (k.source ? `<h3 class="ins-h3">How I would read it</h3><p class="note">${esc('The OKR file says: ' + k.source + '.')}</p>` : '')
1052
1054
  + (k.need && !k.decision ? `<div class="ins-krcon"><span class="sp" aria-hidden="true">${SV(I.spark)}</span><span>${esc('Connect ' + k.need + ' and I test a read before I keep it.')}</span><button type="button" class="cz-ib sm solid" data-krcon aria-label="Open Settings, Connectors" title="Open Connectors">${SV(I.chk)}</button></div>` : '')
1053
- + `<h3 class="ins-h3">Moved by</h3>${k.moves.length ? `<div class="ins-mv">${k.moves.map((r) => initRow(r)).join('')}</div>` : '<p class="note">No initiative is anchored to it yet.</p>'}</div>`;
1055
+ + `<h3 class="ins-h3">Moved by</h3>${k.moves.length ? `<div class="ins-mvl">${k.moves.map((r) => initRow(r)).join('')}</div>` : '<p class="note">No initiative is anchored to it yet.</p>'}</div>`;
1054
1056
  el.addEventListener('click', (e) => { if (e.target.closest('[data-krcon]')) import('./cozy-settings.js').then((S) => S.openSettings('connectors')).catch(() => toast('Open Settings › Connectors.')); });
1055
1057
  } });
1056
1058
  }