create-pmos 0.130.0 → 0.132.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.
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-one-home-test.js +4 -2
- package/workspace/c3-work-test.js +200 -0
- package/workspace/fixtures/one-home-baseline.json +0 -91
- package/workspace/guide/console-calendar.md +65 -0
- package/workspace/guide/data-freshness.md +40 -0
- package/workspace/guide/index.md +2 -0
- package/workspace/guide/my-work.md +76 -1
- package/workspace/lib/calendar-cli.js +84 -0
- package/workspace/lib/calendar-layers.js +372 -0
- package/workspace/lib/facts-async.js +144 -35
- package/workspace/lib/freshness.js +167 -0
- package/workspace/lib/offloop.js +94 -0
- package/workspace/lib/pm-items.js +71 -6
- package/workspace/lib/pmos-verbs.js +3 -0
- package/workspace/lib/report-needs.js +1 -1
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-board.js +3 -1
- package/workspace/lib/routes-calendar-views.js +112 -3
- package/workspace/lib/routes-my-work.js +27 -9
- package/workspace/lib/routes-pm-items.js +123 -13
- package/workspace/lib/routes-sources.js +64 -0
- package/workspace/lib/work-focus.js +106 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-calendar.js +392 -0
- package/workspace/public/pm/c3-demo-cal4.js +114 -0
- package/workspace/public/pm/c3-demo-data.js +79 -77
- package/workspace/public/pm/c3-demo-misc2.js +4 -16
- package/workspace/public/pm/c3-demo-work-ix.js +19 -67
- package/workspace/public/pm/c3-demo.js +10 -3
- package/workspace/public/pm/c3-shell.js +5 -2
- package/workspace/public/pm/calendar-views.js +10 -3
- package/workspace/public/pm/components.css +34 -0
- package/workspace/public/pm/console3.css +155 -0
- package/workspace/public/pm/dest-settings.js +10 -1
- package/workspace/public/pm/freshness-line.js +123 -0
- package/workspace/public/pm/pm-work-view.js +364 -172
- package/workspace/public/pm/work-place.js +58 -0
- package/workspace/server.js +39 -2
- package/workspace/skills/calendar-sync.skill +30 -0
- package/workspace/test-files.json +2 -0
- package/workspace/test-lists/calendar-layers.json +5 -0
- package/workspace/test-lists/calendar-time-page.json +5 -0
- package/workspace/test-lists/console3-work-page.json +9 -0
- package/workspace/test-lists/workspace-data-freshness.json +6 -0
- package/workspace/public/pm/c3-demo-work2.js +0 -13
|
@@ -491,6 +491,117 @@
|
|
|
491
491
|
.c3 .sgb[aria-pressed="true"]{background:var(--accw);border-color:var(--acc);color:var(--foreground)}
|
|
492
492
|
.c3 .vday .vb.past{opacity:.45}
|
|
493
493
|
.c3 .vday .vb.skip{background:repeating-linear-gradient(135deg,var(--muted) 0 6px,transparent 6px 12px);color:var(--muted-foreground);text-decoration:line-through;box-shadow:none;outline:1px dashed var(--ring)}
|
|
494
|
+
/* console3.css addition: the Calendar page (c3-calendar.js, initiative calendar-time-page). Classes start with cal3-, colours are tokens.
|
|
495
|
+
* The controls are primitives (button()), so each rule below sets the box itself: it wins over components.css when that sheet is loaded. */
|
|
496
|
+
.c3 .cal3{display:grid;gap:var(--sp-3);min-width:0}
|
|
497
|
+
.c3 .cal3-head{display:flex;gap:4px 12px;align-items:baseline;flex-wrap:wrap}
|
|
498
|
+
.c3 .cal3-head h2{margin:0;font:600 var(--fs-5) var(--sans)}
|
|
499
|
+
.c3 .cal3 .calbar{margin-bottom:0}
|
|
500
|
+
.c3 .cal3-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
|
|
501
|
+
.c3 .cal3 button.cal3-chip{display:inline-flex;align-items:center;gap:6px;height:auto;min-height:30px;padding:3px 10px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--muted-foreground);font:500 var(--fs-2) var(--sans);cursor:pointer;white-space:nowrap}
|
|
502
|
+
.c3 .cal3 button.cal3-chip>span{display:inline-flex;align-items:center;gap:6px}
|
|
503
|
+
.c3 .cal3 button.cal3-chip[aria-pressed="true"]{background:var(--accw);border-color:var(--acc);color:var(--foreground)}
|
|
504
|
+
.c3 .cal3-chip .cal3-n{font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
505
|
+
.c3 .cal3-sep{width:1px;align-self:stretch;background:var(--border);margin:0 4px}
|
|
506
|
+
.c3 .cal3-sw{display:inline-block;flex:none;width:10px;height:10px;border-radius:0;background:var(--muted-foreground)}
|
|
507
|
+
.c3 .cal3-sw-meetings{background:var(--acc)}
|
|
508
|
+
.c3 .cal3-sw-milestones{background:var(--foreground)}
|
|
509
|
+
.c3 .cal3-sw-releases{background:transparent;border:2px solid var(--acc)}
|
|
510
|
+
.c3 .cal3-sw-reports{background:var(--muted-foreground)}
|
|
511
|
+
.c3 .cal3-sw-mine{background:transparent;border:1px dashed var(--foreground)}
|
|
512
|
+
.c3 .cal3-wrap{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--sp-3);align-items:start}
|
|
513
|
+
@container (min-width:1100px){.c3 .cal3-wrap{grid-template-columns:minmax(0,1fr) 340px}}
|
|
514
|
+
.c3 .cal3-main,.c3 .cal3-side{display:grid;gap:var(--sp-3);min-width:0;align-content:start}
|
|
515
|
+
.c3 .cal3-scroll{overflow-x:auto;min-width:0}
|
|
516
|
+
/* a marker: glyph and short title; the left edge carries the tone */
|
|
517
|
+
.c3 .cal3 button.cal3-mk{--cal3-tc:var(--muted-foreground);display:flex;align-items:center;width:100%;min-width:0;height:auto;min-height:22px;padding:1px 6px;border:0;border-left:3px solid var(--cal3-tc);border-radius:6px;background:var(--muted);color:var(--foreground);font:500 var(--fs-1)/1.4 var(--sans);text-align:left;cursor:pointer;white-space:nowrap;overflow:hidden;justify-content:flex-start}
|
|
518
|
+
.c3 .cal3 button.cal3-mk>span{display:flex;gap:4px;align-items:center;min-width:0;overflow:hidden}
|
|
519
|
+
.c3 .cal3-mk .ic{flex:none;color:var(--cal3-tc)}
|
|
520
|
+
.c3 .cal3-mk .cal3-mk-t{overflow:hidden;text-overflow:ellipsis;min-width:0}
|
|
521
|
+
.c3 .cal3 button.cal3-k-milestone,.c3 .cal3 button.cal3-k-due{--cal3-tc:var(--foreground)}
|
|
522
|
+
.c3 .cal3 button.cal3-k-release{--cal3-tc:var(--acc)}
|
|
523
|
+
.c3 .cal3 button.cal3-k-report{--cal3-tc:var(--muted-foreground)}
|
|
524
|
+
.c3 .cal3 button.cal3-k-own{background:transparent;box-shadow:inset 0 0 0 1px var(--border)}
|
|
525
|
+
.c3 .cal3 button.cal3-tone-warn{--cal3-tc:var(--warning)}
|
|
526
|
+
.c3 .cal3 button.cal3-tone-bad{--cal3-tc:var(--destructive)}
|
|
527
|
+
.c3 .cal3 button.cal3-mk.sel{outline:2px solid var(--foreground);outline-offset:1px}
|
|
528
|
+
.c3 .tag.cal3-tone-warn{color:var(--warning)}
|
|
529
|
+
.c3 .tag.cal3-tone-bad{color:var(--destructive)}
|
|
530
|
+
.c3 .tag.cal3-up{color:var(--acc)}
|
|
531
|
+
.c3 .cal3-more,.c3 .cal3-fcnt{font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
532
|
+
.c3 .cal3-cnt{font:var(--fs-1) var(--mono);color:var(--acc)}
|
|
533
|
+
/* Day and Week: the timeline, its day heads and the all-day marker strip */
|
|
534
|
+
.c3 .cal3-tg{min-width:0}
|
|
535
|
+
.c3 .cal3-tg:not(.one){min-width:620px}
|
|
536
|
+
.c3 .cal3-row{display:grid;grid-template-columns:44px repeat(var(--cal3-n),minmax(0,1fr))}
|
|
537
|
+
.c3 .cal3-dhead,.c3 .cal3-strip{border-bottom:1px solid var(--border)}
|
|
538
|
+
.c3 .cal3-dh{padding:4px;border-left:1px solid var(--border);min-width:0;font:500 var(--fs-2) var(--mono)}
|
|
539
|
+
.c3 .cal3-dh.today b,.c3 .cal3-dh.today button.cal3-dbtn{color:var(--acc)}
|
|
540
|
+
.c3 .cal3-gut{font:var(--fs-1) var(--mono);color:var(--muted-foreground);padding:6px 4px}
|
|
541
|
+
.c3 .cal3-sc{border-left:1px solid var(--border);padding:3px;display:grid;gap:2px;align-content:start;min-height:28px;min-width:0}
|
|
542
|
+
.c3 .cal3-none{font:var(--fs-1) var(--mono);color:var(--muted-foreground);padding:4px}
|
|
543
|
+
.c3 .cal3-hours{position:relative;height:var(--cal3-hgt)}
|
|
544
|
+
.c3 .cal3-hours span{position:absolute;top:var(--cal3-y);right:6px;font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
545
|
+
.c3 .vday.cal3-col{height:var(--cal3-hgt);margin-top:0;border-left:1px solid var(--border);background:repeating-linear-gradient(to bottom,var(--border) 0 1px,transparent 1px var(--cal3-pph))}
|
|
546
|
+
.c3 .vday.cal3-col.we{background-color:var(--muted)}
|
|
547
|
+
.c3 .vday.cal3-col.today{background-color:var(--accw)}
|
|
548
|
+
.c3 .vday .cal3-free{left:2px;right:2px;top:var(--cal3-y);height:var(--cal3-h);display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:4px 6px;overflow:hidden;border:1px dashed var(--ring);z-index:1}
|
|
549
|
+
.c3 .vday .cal3-free b{font:500 var(--fs-1) var(--mono);color:var(--foreground)}
|
|
550
|
+
.c3 .vday .cal3-free span{font:var(--fs-1)/1.3 var(--sans);color:var(--muted-foreground)}
|
|
551
|
+
.c3 .vday button.cal3-ev{top:var(--cal3-y);height:var(--cal3-h);min-height:0;left:calc(2px + (100% - 4px) * var(--cal3-l) / var(--cal3-lc));right:auto;width:calc((100% - 4px) / var(--cal3-lc) - 2px);display:block;padding:3px 8px;z-index:2;white-space:nowrap;cursor:pointer}
|
|
552
|
+
.c3 .vday button.cal3-ev>span{display:block;overflow:hidden}
|
|
553
|
+
.c3 .vday .cal3-ev b{display:block;overflow:hidden;text-overflow:ellipsis;font-weight:500}
|
|
554
|
+
.c3 .vday .cal3-now{left:0;top:var(--cal3-y)}
|
|
555
|
+
.c3 .cal3 button.cal3-allday{display:block;width:100%;height:auto;min-height:22px;padding:1px 6px;border:0;border-radius:6px;background:var(--acc);color:var(--on-accent);font:500 var(--fs-1)/1.4 var(--sans);text-align:left;overflow:hidden;white-space:nowrap;cursor:pointer}
|
|
556
|
+
.c3 .cal3 button.cal3-dbtn,.c3 .cal3 button.cal3-dn{display:inline-flex;align-items:center;height:auto;min-height:24px;padding:2px 6px;border:0;border-radius:6px;background:transparent;color:var(--foreground);font:500 var(--fs-2) var(--mono);cursor:pointer;justify-content:flex-start}
|
|
557
|
+
/* Month */
|
|
558
|
+
.c3 .cal3-month{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px}
|
|
559
|
+
.c3 .cal3-wd{font:500 var(--fs-1) var(--mono);color:var(--muted-foreground);text-align:center;padding:2px}
|
|
560
|
+
.c3 .cal3-mc{border:1px solid var(--border);border-radius:8px;background:var(--background);min-height:96px;padding:4px;display:grid;gap:2px;align-content:start;min-width:0;overflow:hidden}
|
|
561
|
+
.c3 .cal3-mc.out{border:0;background:transparent}
|
|
562
|
+
.c3 .cal3-mc.we{background:var(--muted)}
|
|
563
|
+
.c3 .cal3-mc.today{border-color:var(--acc);box-shadow:0 0 0 1px var(--acc)}
|
|
564
|
+
/* Quarter and Year */
|
|
565
|
+
.c3 .cal3-quarter{display:grid}
|
|
566
|
+
.c3 .cal3-qrow{display:grid;grid-template-columns:minmax(150px,auto) auto minmax(0,1fr);gap:8px;align-items:center;padding:6px 4px;border-bottom:1px solid var(--border);min-width:0}
|
|
567
|
+
.c3 .cal3-qrow.now,.c3 .cal3-ym.now{background:var(--accw)}
|
|
568
|
+
.c3 .cal3-qmk{display:flex;flex-wrap:wrap;gap:3px;min-width:0}
|
|
569
|
+
.c3 .cal3-qmk button.cal3-mk{width:auto;max-width:100%}
|
|
570
|
+
.c3 .cal3-counts{display:flex;gap:4px;flex-wrap:wrap}
|
|
571
|
+
.c3 .cal3-year{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px}
|
|
572
|
+
.c3 .cal3-ym{border:1px solid var(--border);border-radius:8px;padding:8px;display:grid;gap:6px;align-content:start;min-width:0}
|
|
573
|
+
@container (max-width:640px){
|
|
574
|
+
.c3 .cal3-mk.compact .cal3-mk-t{display:none}
|
|
575
|
+
.c3 .cal3-mc{min-height:64px;padding:2px}
|
|
576
|
+
.c3 .cal3-qrow{grid-template-columns:minmax(0,1fr)}
|
|
577
|
+
}
|
|
578
|
+
/* the detail column */
|
|
579
|
+
.c3 .cal3-dt{display:flex;align-items:center;gap:8px}
|
|
580
|
+
.c3 .cal3-dt h3{flex:1}
|
|
581
|
+
.c3 .cal3-title{font:600 var(--fs-4) var(--sans)}
|
|
582
|
+
.c3 .cal3-when{font-size:var(--fs-2)}
|
|
583
|
+
.c3 .cal3-ctx{display:grid;gap:6px}
|
|
584
|
+
.c3 .cal3-ctx h4{margin:0;font:600 var(--fs-2) var(--sans);color:var(--foreground)}
|
|
585
|
+
.c3 .cal3-cl{list-style:none;margin:0;padding:0;display:grid;gap:6px}
|
|
586
|
+
.c3 .cal3-ci{display:flex;flex-wrap:wrap;gap:4px 8px;align-items:baseline;font-size:var(--fs-2);padding-bottom:6px;border-bottom:1px solid var(--border)}
|
|
587
|
+
.c3 .cal3-ct{flex:1 1 160px;min-width:0}
|
|
588
|
+
.c3 .cal3-at{font-size:var(--fs-1)}
|
|
589
|
+
.c3 .cal3-note{margin:0;font-size:var(--fs-2)}
|
|
590
|
+
.c3 .cal3 a.cal3-ref{display:inline-flex;gap:4px;align-items:center;color:var(--acc);font:500 var(--fs-2) var(--sans);text-decoration:underline;text-underline-offset:2px}
|
|
591
|
+
.c3 .cal3-lane{display:grid;grid-template-columns:auto auto minmax(0,1fr);gap:8px;align-items:center;font-size:var(--fs-2)}
|
|
592
|
+
.c3 .cal3-why{text-align:right}
|
|
593
|
+
.c3 .cal3 button.cal3-close{margin-left:auto;height:auto;min-height:24px;padding:2px 8px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--muted-foreground);font:500 var(--fs-1) var(--mono);cursor:pointer}
|
|
594
|
+
.c3 .cal3 button:focus-visible,.c3 .cal3 a:focus-visible{outline:2px solid color-mix(in oklab, var(--ring) 50%, var(--foreground));outline-offset:2px}
|
|
595
|
+
@media (hover:hover){
|
|
596
|
+
.c3 .cal3 button.cal3-chip:hover{border-color:var(--ring)}
|
|
597
|
+
.c3 .cal3 button.cal3-mk:hover{background:var(--accw)}
|
|
598
|
+
}
|
|
599
|
+
@media (pointer:coarse){
|
|
600
|
+
.c3 .cal3 button.cal3-chip,.c3 .cal3 button.cal3-mk,.c3 .cal3 button.cal3-dn,.c3 .cal3 button.cal3-dbtn,.c3 .cal3 button.cal3-close,.c3 .cal3 button.cal3-allday,.c3 .cal3 a.cal3-ref{min-height:44px}
|
|
601
|
+
}
|
|
602
|
+
@media (prefers-reduced-motion:reduce){
|
|
603
|
+
.c3 .cal3 *{animation:none;transition:none}
|
|
604
|
+
}
|
|
494
605
|
.c3 .cmain{display:flex;flex-direction:column;gap:8px;min-height:0}
|
|
495
606
|
.c3 .pdots{display:flex;gap:6px;flex-wrap:wrap}
|
|
496
607
|
.c3 .pdots i{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;border:1px dashed var(--ring);color:var(--muted-foreground)}
|
|
@@ -1588,3 +1699,47 @@
|
|
|
1588
1699
|
.c3 .c3hold .hb.off{opacity:.55;cursor:not-allowed}
|
|
1589
1700
|
.c3 .c3hold .hint{font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
1590
1701
|
.c3 .c3hold .cq{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap;font:var(--fs-2) var(--mono)}
|
|
1702
|
+
/* ===== the Work page (pm/pm-work-view.js, initiative console3-work-page): Focus, Upcoming by day, the weekly Review, offline work and the
|
|
1703
|
+
* shared actions. Tokens only; corners 6px on a control and 8px on a container. */
|
|
1704
|
+
.c3 .wk-min{min-width:0}
|
|
1705
|
+
.c3 .wk-s{font-size:var(--fs-2)}
|
|
1706
|
+
.c3 .pill.pri{background:var(--foreground);color:var(--background);border-color:var(--foreground)}
|
|
1707
|
+
.c3 .wkq{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;margin-top:var(--sp-2)}
|
|
1708
|
+
.c3 .wkq-f{display:inline-flex;gap:6px;align-items:center;font-size:var(--fs-2)}
|
|
1709
|
+
.c3 .wkq input[type=date],.c3 .wkq-f input[type=text]{font:inherit;font-size:var(--fs-2);padding:4px 6px;border:1px solid var(--border);background:var(--background);color:var(--foreground);border-radius:6px;min-width:0;max-width:150px}
|
|
1710
|
+
.c3 .tag.wk-off{background:var(--accw);color:var(--acc)}
|
|
1711
|
+
.c3 .tag.wk-nb{background:var(--muted);color:var(--foreground)}
|
|
1712
|
+
.c3 .tag.wk-on{background:var(--acc);color:var(--background)}
|
|
1713
|
+
.c3 .chip2.wk-fl{color:var(--acc)}
|
|
1714
|
+
.c3 .wkoff{display:grid;gap:var(--sp-1);border:1px solid var(--border);border-radius:8px;padding:var(--sp-2) var(--sp-3);margin:var(--sp-2) 0}
|
|
1715
|
+
.c3 .wkoff-n{margin:0;display:flex;gap:6px;align-items:center;font-size:var(--fs-2);color:var(--muted-foreground)}
|
|
1716
|
+
.c3 .wkoff-o{flex:1 1 220px;min-width:0}
|
|
1717
|
+
.c3 .wkoff-o input[type=text]{width:100%}
|
|
1718
|
+
.c3 .wkev{display:flex;flex-wrap:wrap;gap:var(--sp-2);align-items:center;border:1px dashed var(--warning);border-radius:8px;padding:var(--sp-2) var(--sp-3);font-size:var(--fs-2)}
|
|
1719
|
+
.c3 .wkev>.wk-min{flex:1 1 200px}
|
|
1720
|
+
.c3 .wkfg{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:var(--sp-3)}
|
|
1721
|
+
.c3 .wkfc{border:1px solid var(--border);background:var(--card);border-radius:8px;padding:var(--sp-3);display:grid;gap:var(--sp-2);align-content:start;min-width:0}
|
|
1722
|
+
.c3 .wkfc.on{border-color:var(--acc);background:var(--accw)}
|
|
1723
|
+
.c3 .wkfc-t{font-size:var(--fs-3);line-height:1.3;overflow-wrap:anywhere}
|
|
1724
|
+
.c3 .wkfc-why{margin:0;font-size:var(--fs-2);color:var(--muted-foreground)}
|
|
1725
|
+
.c3 .wkfc-r{margin:0;padding:0;list-style:none;display:grid;gap:2px;font-size:var(--fs-2)}
|
|
1726
|
+
.c3 .wkfc-r li{display:flex;gap:4px;align-items:center;min-width:0}
|
|
1727
|
+
.c3 .wkfc .wkq{margin-top:0}
|
|
1728
|
+
.c3 .wkdays{display:grid}
|
|
1729
|
+
.c3 .wkday h3{margin:0;padding:var(--sp-2) 0 var(--sp-1);font:500 var(--fs-2) var(--mono);color:var(--foreground);display:flex;gap:var(--sp-2);align-items:baseline}
|
|
1730
|
+
.c3 .wkday h3 .dim{font-weight:400}
|
|
1731
|
+
.c3 .wkday-none{color:var(--muted-foreground);font-size:var(--fs-2);padding:0 0 var(--sp-2);border-bottom:1px solid var(--border)}
|
|
1732
|
+
.c3 .wkrv{gap:var(--sp-3)}
|
|
1733
|
+
.c3 .wkrv-p{display:flex;justify-content:space-between;font:var(--fs-2) var(--mono);color:var(--muted-foreground)}
|
|
1734
|
+
.c3 .wkrv-t{margin:0;font:600 var(--fs-4)/1.3 var(--sans);color:var(--foreground);overflow-wrap:anywhere}
|
|
1735
|
+
.c3 .wkrv-x{display:grid;gap:var(--sp-2)}
|
|
1736
|
+
@media (max-width:760px){
|
|
1737
|
+
.c3 .wkq input[type=date],.c3 .wkq-f input[type=text]{min-height:44px;max-width:none}
|
|
1738
|
+
.c3 .wkoff-o input[type=text],.c3 .wkfc input[type=text]{min-height:44px}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
.c3 .wk-sm{font-size:var(--fs-2)}
|
|
1742
|
+
.c3 .wk-bar{appearance:none;-webkit-appearance:none;width:100%;height:8px;border:0;border-radius:6px;overflow:hidden;background:var(--muted)}
|
|
1743
|
+
.c3 .wk-bar::-webkit-progress-bar{background:var(--muted)}
|
|
1744
|
+
.c3 .wk-bar::-webkit-progress-value{background:var(--acc);border-radius:6px}
|
|
1745
|
+
.c3 .wk-bar::-moz-progress-bar{background:var(--acc);border-radius:6px}
|
|
@@ -33,6 +33,7 @@ import { COPY } from './frame-copy.js';
|
|
|
33
33
|
import { askConfirm } from './dialogs.js';
|
|
34
34
|
import { icon } from './icons.js';
|
|
35
35
|
import { flagPicker, flagsLine, flagsMissingValue } from './flag-picker.js';
|
|
36
|
+
import { freshnessTable } from './freshness-line.js';
|
|
36
37
|
|
|
37
38
|
const P = COPY.page;
|
|
38
39
|
const roots = new WeakMap(); // host -> { root, sig }
|
|
@@ -542,6 +543,13 @@ function checkButton(scope) {
|
|
|
542
543
|
});
|
|
543
544
|
}
|
|
544
545
|
|
|
546
|
+
/* ── Data freshness (workspace-data-freshness R9): how old each source is, Refresh, and the change detector switch ── */
|
|
547
|
+
function freshnessSection(scope) {
|
|
548
|
+
const ws = wsOfRepo(scope === 'all' ? null : activeProduct());
|
|
549
|
+
if (!ws) return section('freshness', 'Data freshness', [empty({ text: 'Open a repository to see how old its data is.' })], null, 'refresh-cw');
|
|
550
|
+
return section('freshness', 'Data freshness', [freshnessTable(ws)], null, 'refresh-cw');
|
|
551
|
+
}
|
|
552
|
+
|
|
545
553
|
/* ── mount ────────────────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
546
554
|
|
|
547
555
|
function signature(scope) {
|
|
@@ -583,7 +591,8 @@ export function mountDestination(host, ctx) {
|
|
|
583
591
|
peopleSection(scope),
|
|
584
592
|
onlineSection(),
|
|
585
593
|
themeSection(),
|
|
586
|
-
updatesSection(scope)
|
|
594
|
+
updatesSection(scope),
|
|
595
|
+
freshnessSection(scope)),
|
|
587
596
|
]);
|
|
588
597
|
}
|
|
589
598
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/* pm/freshness-line.js — how old each source of the workspace is (workspace-data-freshness R9).
|
|
2
|
+
*
|
|
3
|
+
* freshnessLine(ws) → element the console header's short line: the sources that are old or never read, else
|
|
4
|
+
* "Up to date"; a Refresh button for the oldest one. Updates itself every 15 s.
|
|
5
|
+
* freshnessTable(ws) → element Settings › Data freshness: every source, when it was read, its state, its schedule,
|
|
6
|
+
* Refresh, and the repository's Watch for changes switch.
|
|
7
|
+
*
|
|
8
|
+
* Both read GET /api/sources (lib/routes-sources.js). The presses go through pmPost (the page nonce), so an agent cannot
|
|
9
|
+
* make them. When a workspace's data version moves, the line sends `pmos:data-changed` with { ws } on window, so a view
|
|
10
|
+
* that wants to read again only on a change can listen for it. One element per ws and kind, kept across redraws, so a
|
|
11
|
+
* page that rebuilds its DOM does not start a second poll.
|
|
12
|
+
*/
|
|
13
|
+
import { el, setChildren } from './dom.js';
|
|
14
|
+
import { pmGet, pmPost } from './http.js';
|
|
15
|
+
import { button, SEL } from './primitives.js';
|
|
16
|
+
|
|
17
|
+
const POLL_MS = 15000;
|
|
18
|
+
const live = new Map(); // kind|ws -> { node, timer, version }
|
|
19
|
+
|
|
20
|
+
const hhmm = (iso) => { const d = new Date(iso); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); };
|
|
21
|
+
function ago(iso) {
|
|
22
|
+
if (!iso) return 'never read';
|
|
23
|
+
const ms = Date.now() - Date.parse(iso);
|
|
24
|
+
if (ms < 60000) return 'just now';
|
|
25
|
+
if (ms < 3600000) return Math.round(ms / 60000) + ' min ago';
|
|
26
|
+
if (ms < 24 * 3600000) return 'today ' + hhmm(iso);
|
|
27
|
+
return Math.round(ms / (24 * 3600000)) + ' d ago';
|
|
28
|
+
}
|
|
29
|
+
const STATE_WORD = { fresh: 'Up to date', stale: 'Old', never: 'Never read', building: 'Reading…', failed: 'Failed', off: 'Off' };
|
|
30
|
+
// the colour of a state: a class in components.css (.fr-fresh__state--<state>), coloured from the tokens
|
|
31
|
+
const tone = (state) => 'fr-fresh__state fr-fresh__state--' + (STATE_WORD[state] ? state : 'never');
|
|
32
|
+
const label = (b, t) => { const l = b.querySelector(SEL.btnLabel); (l || b).textContent = t; };
|
|
33
|
+
|
|
34
|
+
async function read(ws) {
|
|
35
|
+
const r = await pmGet('/api/sources' + (ws ? '?ws=' + encodeURIComponent(ws) : ''));
|
|
36
|
+
return r && r.ok ? r : null;
|
|
37
|
+
}
|
|
38
|
+
async function refresh(ws, source, btn) {
|
|
39
|
+
if (btn) { btn.disabled = true; label(btn, 'Starting…'); }
|
|
40
|
+
const r = await pmPost('/api/sources/refresh', { ws, source });
|
|
41
|
+
if (btn) label(btn, r && r.ok ? (r.started === 'session' ? 'Agent started' : 'Reading…') : 'Could not start');
|
|
42
|
+
return r;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function poll(key, ws, draw) {
|
|
46
|
+
let cur = live.get(key);
|
|
47
|
+
if (cur) return cur.node;
|
|
48
|
+
cur = { node: null, timer: null, version: null };
|
|
49
|
+
live.set(key, cur);
|
|
50
|
+
const tick = async () => {
|
|
51
|
+
const d = await read(ws).catch(() => null);
|
|
52
|
+
if (d) {
|
|
53
|
+
if (cur.version != null && d.version !== cur.version) {
|
|
54
|
+
try { window.dispatchEvent(new CustomEvent('pmos:data-changed', { detail: { ws: d.ws } })); } catch { /* no window */ }
|
|
55
|
+
}
|
|
56
|
+
cur.version = d.version;
|
|
57
|
+
}
|
|
58
|
+
draw(cur.node, d);
|
|
59
|
+
};
|
|
60
|
+
cur.tick = tick;
|
|
61
|
+
cur.node = draw(null, null);
|
|
62
|
+
tick();
|
|
63
|
+
cur.timer = setInterval(() => { if (!cur.node.isConnected && cur.node.dataset.mounted === '1') { clearInterval(cur.timer); live.delete(key); return; } if (cur.node.isConnected) cur.node.dataset.mounted = '1'; tick(); }, POLL_MS);
|
|
64
|
+
return cur.node;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The header line: quiet when everything is fresh, amber names when a source is old or never read. */
|
|
68
|
+
export function freshnessLine(ws) {
|
|
69
|
+
return poll('line|' + ws, ws, (node, d) => {
|
|
70
|
+
const n = node || el('div', { class: 'fr-fresh', 'data-c3pg': 'freshness' });
|
|
71
|
+
if (!d) { if (!node) n.textContent = ''; return n; }
|
|
72
|
+
const watched = d.sources.filter((s) => s.state !== 'off');
|
|
73
|
+
const behind = watched.filter((s) => s.state === 'stale' || s.state === 'failed' || (s.state === 'never' && s.schedule));
|
|
74
|
+
const building = watched.find((s) => s.state === 'building');
|
|
75
|
+
const kids = [];
|
|
76
|
+
if (!behind.length) kids.push(el('span', { class: tone('fresh') }, building ? 'Reading the repository…' : 'Data up to date'));
|
|
77
|
+
for (const s of behind.slice(0, 3)) kids.push(el('span', { class: tone(s.state), title: STATE_WORD[s.state] }, s.label + ' ' + ago(s.as_of)));
|
|
78
|
+
if (behind.length) {
|
|
79
|
+
const first = behind[0];
|
|
80
|
+
const btn = button({ label: 'Refresh ' + first.label, size: 'sm', attrs: { 'data-c3pg': 'freshness-refresh' },
|
|
81
|
+
onClick: () => refresh(d.ws, first.id, btn) });
|
|
82
|
+
kids.push(btn);
|
|
83
|
+
}
|
|
84
|
+
setChildren(n, kids);
|
|
85
|
+
return n;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The Settings table: every source of the workspace, with Refresh, and the change detector switch. */
|
|
90
|
+
export function freshnessTable(ws) {
|
|
91
|
+
return poll('table|' + ws, ws, (node, d) => {
|
|
92
|
+
const n = node || el('div', { class: 'fr-fresh-table', 'data-fr': 'freshness' });
|
|
93
|
+
if (!d) { if (!node) n.textContent = 'Reading…'; return n; }
|
|
94
|
+
const th = (t) => el('th', null, t);
|
|
95
|
+
const td = (...k) => el('td', null, ...k);
|
|
96
|
+
const rows = d.sources.map((s) => {
|
|
97
|
+
const btn = button({ label: 'Refresh', size: 'sm', disabled: s.state === 'off' || s.state === 'building',
|
|
98
|
+
attrs: { 'data-fr': 'freshness-refresh-' + s.id }, onClick: () => refresh(d.ws, s.id, btn) });
|
|
99
|
+
const sched = s.schedule ? [s.schedule.cadence, s.schedule.day, s.schedule.time].filter(Boolean).join(' ') : (s.skill ? 'no schedule' : 'on every change');
|
|
100
|
+
return el('tr', { 'data-fr-source': s.id },
|
|
101
|
+
td(s.label), td(s.id === 'repository' && s.state === 'fresh' ? 'watching' : s.as_of ? ago(s.as_of) : 'never'), td(el('span', { class: tone(s.state) }, STATE_WORD[s.state] || s.state)),
|
|
102
|
+
td(sched), td(btn));
|
|
103
|
+
});
|
|
104
|
+
const repo = d.sources.find((s) => s.id === 'repository');
|
|
105
|
+
const on = !repo || repo.state !== 'off';
|
|
106
|
+
// a button, not a checkbox: on a phone every control is 44px high (R27), and a checkbox box is not
|
|
107
|
+
const sw = button({ label: on ? 'Stop watching this repository' : 'Watch this repository for changes', size: 'sm',
|
|
108
|
+
attrs: { 'data-fr': 'freshness-watch', 'aria-pressed': on ? 'true' : 'false', class: 'fr-fresh-table__watch' },
|
|
109
|
+
onClick: async () => {
|
|
110
|
+
sw.disabled = true;
|
|
111
|
+
const r = await pmPost('/api/sources/watch', { ws: d.ws, on: !on });
|
|
112
|
+
if (r && r.ok) { const c = live.get('table|' + ws); if (c) c.tick(); } else { sw.disabled = false; label(sw, 'Could not save'); }
|
|
113
|
+
} });
|
|
114
|
+
setChildren(n, [
|
|
115
|
+
el('table', { class: 'fr-fresh-table__grid' },
|
|
116
|
+
el('thead', null, el('tr', null, th('Source'), th('Read'), th('State'), th('Schedule'), th(''))),
|
|
117
|
+
el('tbody', null, ...rows)),
|
|
118
|
+
sw,
|
|
119
|
+
el('p', { class: 'fr-set__note' }, 'The workspace holds no password or token. Calendar, Jira and My work are read by an agent session with your own connection.'),
|
|
120
|
+
]);
|
|
121
|
+
return n;
|
|
122
|
+
});
|
|
123
|
+
}
|