@korso/shepherd-ui 0.1.1 → 0.2.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 (100) hide show
  1. package/README.md +47 -47
  2. package/dist/ShepherdProvider.d.ts +12 -0
  3. package/dist/ShepherdProvider.js +33 -0
  4. package/dist/ShepherdProvider.test.d.ts +1 -0
  5. package/dist/ShepherdProvider.test.js +47 -0
  6. package/dist/ShepherdRoot.d.ts +9 -0
  7. package/dist/ShepherdRoot.js +88 -0
  8. package/dist/ShepherdRoot.routing.test.d.ts +1 -0
  9. package/dist/ShepherdRoot.routing.test.js +61 -0
  10. package/dist/ShepherdRoot.test.d.ts +1 -0
  11. package/dist/ShepherdRoot.test.js +37 -0
  12. package/dist/app/assets/index-CDOCIg6s.js +11 -0
  13. package/dist/app/index.html +12 -0
  14. package/dist/client.d.ts +107 -0
  15. package/dist/client.js +238 -0
  16. package/dist/client.test.d.ts +1 -0
  17. package/dist/client.test.js +298 -0
  18. package/dist/components/ActiveList.d.ts +22 -0
  19. package/dist/components/ActiveList.js +59 -0
  20. package/dist/components/Chat.d.ts +30 -0
  21. package/dist/components/Chat.js +61 -0
  22. package/dist/components/Composer.d.ts +37 -0
  23. package/dist/components/Composer.js +146 -0
  24. package/dist/components/Crew.d.ts +23 -0
  25. package/dist/components/Crew.js +31 -0
  26. package/dist/components/Dashboard.d.ts +34 -0
  27. package/dist/components/Dashboard.js +209 -0
  28. package/dist/components/DoneList.d.ts +29 -0
  29. package/dist/components/DoneList.js +50 -0
  30. package/dist/components/RepoSelect.d.ts +38 -0
  31. package/dist/components/RepoSelect.js +56 -0
  32. package/dist/components/Territory.d.ts +21 -0
  33. package/dist/components/Territory.js +21 -0
  34. package/dist/config/ConnectAgent.d.ts +10 -0
  35. package/dist/config/ConnectAgent.js +119 -0
  36. package/dist/config/ConnectAgent.test.d.ts +1 -0
  37. package/dist/config/ConnectAgent.test.js +92 -0
  38. package/dist/config/EmptyState.d.ts +12 -0
  39. package/dist/config/EmptyState.js +4 -0
  40. package/dist/config/EmptyState.test.d.ts +1 -0
  41. package/dist/config/EmptyState.test.js +32 -0
  42. package/dist/config/Members.d.ts +10 -0
  43. package/dist/config/Members.js +83 -0
  44. package/dist/config/Members.test.d.ts +1 -0
  45. package/dist/config/Members.test.js +66 -0
  46. package/dist/config/Workspaces.d.ts +12 -0
  47. package/dist/config/Workspaces.js +96 -0
  48. package/dist/config/Workspaces.test.d.ts +1 -0
  49. package/dist/config/Workspaces.test.js +90 -0
  50. package/dist/config/index.d.ts +8 -0
  51. package/dist/config/index.js +6 -0
  52. package/dist/context.d.ts +28 -0
  53. package/dist/context.js +36 -0
  54. package/dist/index.cjs +5289 -0
  55. package/dist/index.cjs.map +1 -0
  56. package/dist/index.d.cts +149 -0
  57. package/dist/index.d.ts +18 -0
  58. package/dist/index.js +15 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/lib/Dashboard-CG6KeFQ3.js +1214 -0
  61. package/dist/lib/index.d.ts +438 -16
  62. package/dist/lib/index.js +455 -6
  63. package/dist/lib/selfhost.js +9 -9
  64. package/dist/lib/styles.css +209 -182
  65. package/dist/logic.d.ts +208 -0
  66. package/dist/logic.js +372 -0
  67. package/dist/main.d.ts +1 -0
  68. package/dist/main.js +11 -0
  69. package/dist/selfhost/assets/{index-tSyzirZa.css → index-BZmImfYZ.css} +1 -1
  70. package/dist/selfhost/assets/index-CS268KSw.js +40 -0
  71. package/dist/selfhost/index.html +13 -13
  72. package/dist/selfhost.d.ts +22 -0
  73. package/dist/selfhost.js +97 -0
  74. package/dist/test/mockClient.d.ts +6 -0
  75. package/dist/test/mockClient.js +45 -0
  76. package/dist/test/setup.d.ts +1 -0
  77. package/dist/test/setup.js +2 -0
  78. package/dist/useLandscapePolling.d.ts +63 -0
  79. package/dist/useLandscapePolling.js +129 -0
  80. package/dist/views/Chat.d.ts +5 -0
  81. package/dist/views/Chat.js +60 -0
  82. package/dist/views/Chat.test.d.ts +1 -0
  83. package/dist/views/Chat.test.js +92 -0
  84. package/dist/views/Config.d.ts +12 -0
  85. package/dist/views/Config.js +6 -0
  86. package/dist/views/Tasks.d.ts +5 -0
  87. package/dist/views/Tasks.js +75 -0
  88. package/dist/views/Tasks.test.d.ts +1 -0
  89. package/dist/views/Tasks.test.js +115 -0
  90. package/dist/views/useLandscape.d.ts +14 -0
  91. package/dist/views/useLandscape.js +74 -0
  92. package/dist/views/useLandscape.test.d.ts +1 -0
  93. package/dist/views/useLandscape.test.js +101 -0
  94. package/dist/views/wallboard.d.ts +46 -0
  95. package/dist/views/wallboard.js +209 -0
  96. package/dist/views/wallboard.test.d.ts +1 -0
  97. package/dist/views/wallboard.test.js +143 -0
  98. package/package.json +67 -67
  99. package/dist/lib/Dashboard-CB6SL-J2.js +0 -1064
  100. package/dist/selfhost/assets/index-BBx3vo2Y.js +0 -40
@@ -1,182 +1,209 @@
1
- :root {
2
- --bg:#ece0c6; --ink:#322c20; --ink2:#6f6450; --ink3:#9c917a;
3
- --line:#d7c9ac; --line2:#e0d4b9; --live:#3d8b58; --drop:#bd4f3a;
4
- --chip:#e3d6ba; --terr:#7a6a48; --card:#f5ecd9;
5
- }
6
- * { box-sizing:border-box; }
7
- html,body { height:100%; }
8
- body {
9
- margin:0; background:var(--bg); color:var(--ink);
10
- font:14px/1.45 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
11
- -webkit-font-smoothing:antialiased;
12
- }
13
- .wrap { max-width:1100px; margin:0 auto; padding:40px 44px; }
14
-
15
- header { display:flex; align-items:center; gap:12px; padding-bottom:18px; border-bottom:1px solid var(--line); }
16
- /* Chat view: the card supplies its own border, so the header rule is redundant. */
17
- #board:has(#panel-chat:not([hidden])) header { border-bottom:0; }
18
- .brand { font-size:17px; font-weight:600; }
19
- .vitals { color:var(--ink3); font-size:12.5px; }
20
- .grow { flex:1; }
21
- .status { font-size:12px; color:var(--ink3); }
22
- .status--ok { color:var(--live); }
23
- .status--warn { color:var(--terr); }
24
- .status--error { color:var(--drop); }
25
- .freshness { font-size:12px; color:var(--ink3); }
26
-
27
- /* Repo selector (hidden when <2 repos; populated by app.js) */
28
- .repo { position:relative; }
29
- .repo-trig { display:inline-flex; align-items:center; gap:6px; font-size:13px;
30
- font-family:inherit; color:var(--ink); appearance:none;
31
- border:1px solid var(--line); border-radius:8px; padding:4px 10px;
32
- background:rgba(255,250,238,.4); cursor:pointer; }
33
- .repo-trig .slash { color:var(--ink3); }
34
- .repo-menu { position:absolute; top:36px; left:0; min-width:200px; background:var(--card);
35
- border:1px solid var(--line); border-radius:10px; padding:5px;
36
- box-shadow:0 8px 24px rgba(50,40,20,.14); z-index:5; }
37
- .repo-mi { display:flex; justify-content:space-between; gap:14px; padding:7px 9px;
38
- width:100%; text-align:left; border:0; appearance:none; font-family:inherit; color:inherit;
39
- background:transparent; border-radius:7px; font-size:13px; cursor:pointer; }
40
- .repo-mi:hover { background:var(--chip); }
41
- .repo-mi.on { background:var(--chip); font-weight:600; }
42
- .repo-mi .ct { font-size:11px; color:var(--ink3); }
43
- .repo-mi.all { border-top:1px solid var(--line2); margin-top:4px; padding-top:9px; color:var(--ink2); }
44
-
45
- /* Tabs */
46
- .tabs { display:flex; gap:4px; margin-left:auto; }
47
- .tab { font-size:13px; color:var(--ink2); padding:5px 11px; border:0; border-radius:7px;
48
- background:transparent; cursor:pointer; font:inherit; }
49
- .tab--active { background:var(--ink); color:var(--bg); }
50
-
51
- /* Crew strip */
52
- .crew { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:26px 0 32px; }
53
- .person { display:inline-flex; align-items:center; gap:9px; padding:5px 13px 5px 5px;
54
- border:1px solid var(--line); border-radius:999px; background:var(--card); font-size:13px; }
55
- .person--idle { opacity:.55; }
56
- .avatar { width:24px; height:24px; border-radius:50%; display:grid; place-items:center;
57
- font-size:10px; font-weight:700; color:#fff; flex:none; }
58
- .person__name { font-weight:600; }
59
- .person__doing { color:var(--ink3); font-size:12px; }
60
-
61
- /* Board */
62
- .board { display:grid; grid-template-columns:1fr 1px 1fr; gap:0; }
63
- .board__rule { background:var(--line); }
64
- .col { padding:0 36px; } .col:first-child { padding-left:0; } .col:last-child { padding-right:0; }
65
- .colhead { display:flex; align-items:center; gap:8px; margin-bottom:6px; }
66
- .colhead h2 { font-size:12px; font-weight:600; letter-spacing:.03em; margin:0;
67
- text-transform:uppercase; color:var(--ink2); }
68
- .colhead .n { font-size:11px; color:var(--ink3); background:var(--chip);
69
- border-radius:999px; padding:1px 8px; }
70
-
71
- .task { padding:16px 0; border-bottom:1px solid var(--line2); }
72
- .task__r1 { display:flex; align-items:center; gap:8px; }
73
- .task__who { font-size:13.5px; font-weight:600; }
74
- .task__tag { font-size:11px; color:var(--ink3); }
75
- .task__repo { font-size:11px; color:var(--terr); background:rgba(122,106,72,.1);
76
- border-radius:4px; padding:0 6px; }
77
- .task__stat { margin-left:auto; font-size:11px; font-weight:500; color:var(--ink3); }
78
- .task__stat--drop { color:var(--drop); }
79
- .livedot { width:7px; height:7px; border-radius:50%; background:var(--live); margin-left:auto; }
80
- .task__intent { font-size:13.5px; margin:5px 0 0; line-height:1.45; }
81
- .terr { margin-top:8px; display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
82
- .terr__lbl { font-size:9.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--ink3); }
83
- .glob { font-family:ui-monospace, Menlo, monospace; font-size:11px; color:var(--terr);
84
- background:rgba(122,106,72,.12); border:1px solid rgba(122,106,72,.22);
85
- border-radius:5px; padding:1px 7px; }
86
- .task__meta { font-size:11.5px; color:var(--ink3); margin-top:6px; }
87
- /* expandable territory (2+ paths collapse to a one-line "N paths" toggle) */
88
- .terrx { margin-top:8px; }
89
- .terrx summary { list-style:none; cursor:pointer; display:flex; align-items:center;
90
- gap:6px; flex-wrap:wrap; user-select:none; }
91
- .terrx summary::-webkit-details-marker { display:none; }
92
- .terrx .terr--full { margin-top:8px; }
93
- .glob--more { cursor:pointer; color:var(--ink2); background:var(--chip);
94
- border:1px solid var(--line); }
95
- .glob--more .tw { color:var(--ink3); display:inline-block; transition:transform .15s; }
96
- .terrx[open] summary .glob--more .tw { transform:rotate(90deg); }
97
- /* grouped agent box: header + claims rail + collapsible "narrower" fold */
98
- .grp { padding:16px 0; border-bottom:1px solid var(--line2); }
99
- .grp__head { display:flex; align-items:center; gap:8px; }
100
- .grp__who { font-size:13.5px; font-weight:600; }
101
- .grp__tag { font-size:11px; color:var(--ink3); }
102
- .grp__count { font-size:11px; color:var(--ink3); }
103
- .grp__dot { width:7px; height:7px; border-radius:50%; background:var(--live); margin-left:auto; }
104
- .claims { margin:10px 0 0 6px; padding-left:14px; border-left:1px solid var(--line); }
105
- .claim { padding:6px 0; }
106
- .claim + .claim { border-top:1px solid var(--line2); margin-top:6px; }
107
- .fold { margin-top:10px; }
108
- .fold summary { list-style:none; cursor:pointer; font-size:12px; color:var(--ink2);
109
- display:inline-flex; align-items:center; gap:6px; user-select:none; }
110
- .fold summary::-webkit-details-marker { display:none; }
111
- .fold summary .tw { display:inline-block; color:var(--ink3); transition:transform .15s; }
112
- .fold[open] summary .tw { transform:rotate(90deg); }
113
- .fold__body { margin:8px 0 0 6px; padding-left:14px; border-left:1px dashed var(--line); }
114
- .covered { font-size:10.5px; color:var(--ink3); font-style:italic; margin-top:4px; }
115
- .day { font-size:11px; color:var(--ink3); padding:18px 0 2px; font-weight:600; }
116
- .more { margin-top:20px; font-size:12.5px; color:var(--ink2); padding:7px 13px;
117
- border:1px solid var(--line); border-radius:8px; background:rgba(255,250,238,.35);
118
- cursor:pointer; }
119
- .empty { color:var(--ink3); font-style:italic; padding:14px 0; }
120
-
121
- /* Chat (re-themed; structure unchanged) */
122
- .chat-wrap { background:var(--card); border:1px solid var(--line); border-radius:12px;
123
- display:flex; flex-direction:column; overflow:hidden; height:calc(100vh - 150px); min-height:320px; }
124
- .chat { flex:1; overflow-y:auto; padding:16px; }
125
- .chat { scrollbar-width:thin; scrollbar-color:var(--line) transparent; }
126
- .chat::-webkit-scrollbar { width:10px; }
127
- .chat::-webkit-scrollbar-track { background:transparent; }
128
- .chat::-webkit-scrollbar-thumb { background:var(--line); border-radius:999px;
129
- border:3px solid var(--card); background-clip:padding-box; }
130
- .chat::-webkit-scrollbar-thumb:hover { background:var(--ink3); background-clip:padding-box; }
131
- .chat__note { padding:9px 16px; font-size:11.5px; color:var(--ink3); }
132
- .msg { display:flex; gap:11px; padding:9px 0; }
133
- .msg__avatar { flex:none; width:32px; height:32px; border-radius:50%; display:grid;
134
- place-items:center; font-size:12px; font-weight:700; color:#fff; }
135
- .msg__body { min-width:0; flex:1; }
136
- .msg__head { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
137
- .msg__who { font-weight:600; }
138
- .msg__human { font-size:12px; color:var(--ink3); }
139
- .msg__to { font-size:11.5px; color:var(--terr); }
140
- .msg__time { font-size:11.5px; color:var(--ink3); margin-left:auto; }
141
- .msg__text { margin-top:3px; padding:8px 12px; border-radius:4px 12px 12px 12px;
142
- background:var(--chip); white-space:pre-wrap; word-break:break-word; }
143
- .msg--targeted .msg__text { box-shadow:inset 3px 0 0 var(--terr); }
144
-
145
- /* Operator's own messages — same group-chat structure, mirrored to the right. */
146
- .msg--me { flex-direction:row-reverse; }
147
- .msg--me .msg__head { flex-direction:row-reverse; }
148
- .msg--me .msg__time { margin-left:0; margin-right:auto; }
149
- .msg--me .msg__body { display:flex; flex-direction:column; align-items:flex-end; }
150
- .msg--me .msg__text { border-radius:12px 4px 12px 12px; background:#d4e7d2; }
151
- .msg--me.msg--targeted .msg__text { box-shadow:inset -3px 0 0 var(--terr); }
152
-
153
- /* Composer */
154
- .composer { position:relative; border-top:1px solid var(--line); padding:10px 12px; }
155
- .chat-form { display:flex; gap:8px; }
156
- .chat-input { flex:1; padding:9px 12px; border-radius:8px; border:1px solid var(--line);
157
- background:var(--bg); color:var(--ink); font:inherit; }
158
- .chat-input:focus { outline:none; border-color:var(--terr); }
159
- .chat-send { padding:9px 16px; border-radius:8px; border:1px solid var(--ink);
160
- background:var(--ink); color:var(--bg); font-weight:600; cursor:pointer; font:inherit; }
161
- .chat-send:disabled { opacity:.5; cursor:default; }
162
-
163
- /* @-mention autocomplete — floats above the input */
164
- .mention-pop { position:absolute; left:12px; right:12px; bottom:56px; z-index:6;
165
- background:var(--card); border:1px solid var(--line); border-radius:10px; padding:5px;
166
- box-shadow:0 8px 24px rgba(50,40,20,.16); max-height:200px; overflow-y:auto; }
167
- .mention-mi { display:flex; align-items:center; gap:9px; width:100%; text-align:left;
168
- border:0; appearance:none; font-family:inherit; color:inherit; background:transparent;
169
- border-radius:7px; padding:6px 8px; font-size:13px; cursor:pointer; }
170
- .mention-mi:hover, .mention-mi.on { background:var(--chip); }
171
- .mention-mi .ma { width:22px; height:22px; border-radius:50%; display:grid; place-items:center;
172
- font-size:9px; font-weight:700; color:#fff; flex:none; }
173
-
174
- /* Token gate */
175
- .gate { max-width:420px; margin:12vh auto 0; text-align:center; }
176
- .gate h1 { font-size:19px; margin:0 0 6px; }
177
- .gate p { color:var(--ink2); margin:0 0 20px; }
178
- .gate form { display:flex; gap:8px; }
179
- .gate input { flex:1; padding:10px 12px; border-radius:8px; border:1px solid var(--line);
180
- background:var(--card); color:var(--ink); }
181
- .gate button { padding:10px 16px; border-radius:8px; border:1px solid var(--ink);
182
- background:var(--ink); color:var(--bg); font-weight:600; cursor:pointer; }
1
+ :root {
2
+ --bg:#ece0c6; --ink:#322c20; --ink2:#6f6450; --ink3:#9c917a;
3
+ --line:#d7c9ac; --line2:#e0d4b9; --live:#3d8b58; --drop:#bd4f3a;
4
+ --chip:#e3d6ba; --terr:#7a6a48; --card:#f5ecd9;
5
+ }
6
+ * { box-sizing:border-box; }
7
+ html,body { height:100%; }
8
+ body {
9
+ margin:0; background:var(--bg); color:var(--ink);
10
+ font:14px/1.45 'Inter', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
11
+ -webkit-font-smoothing:antialiased;
12
+ }
13
+ .wrap { max-width:1100px; margin:0 auto; padding:40px 44px; }
14
+
15
+ header { display:flex; align-items:center; gap:12px; padding-bottom:18px; border-bottom:1px solid var(--line); }
16
+ /* Chat view: the card supplies its own border, so the header rule is redundant. */
17
+ #board:has(#panel-chat:not([hidden])) header { border-bottom:0; }
18
+ .brand { font-size:17px; font-weight:600; }
19
+ .vitals { color:var(--ink3); font-size:12.5px; }
20
+ .grow { flex:1; }
21
+ .status { font-size:12px; color:var(--ink3); }
22
+ .status--ok { color:var(--live); }
23
+ .status--warn { color:var(--terr); }
24
+ .status--error { color:var(--drop); }
25
+ .freshness { font-size:12px; color:var(--ink3); }
26
+
27
+ /* Repo selector (hidden when <2 repos; populated by app.js) */
28
+ .repo { position:relative; }
29
+ .repo-trig { display:inline-flex; align-items:center; gap:6px; font-size:13px;
30
+ font-family:inherit; color:var(--ink); appearance:none;
31
+ border:1px solid var(--line); border-radius:8px; padding:4px 10px;
32
+ background:rgba(255,250,238,.4); cursor:pointer; }
33
+ .repo-trig .slash { color:var(--ink3); }
34
+ .repo-menu { position:absolute; top:36px; left:0; min-width:200px; background:var(--card);
35
+ border:1px solid var(--line); border-radius:10px; padding:5px;
36
+ box-shadow:0 8px 24px rgba(50,40,20,.14); z-index:5; }
37
+ .repo-mi { display:flex; justify-content:space-between; gap:14px; padding:7px 9px;
38
+ width:100%; text-align:left; border:0; appearance:none; font-family:inherit; color:inherit;
39
+ background:transparent; border-radius:7px; font-size:13px; cursor:pointer; }
40
+ .repo-mi:hover { background:var(--chip); }
41
+ .repo-mi.on { background:var(--chip); font-weight:600; }
42
+ .repo-mi .ct { font-size:11px; color:var(--ink3); }
43
+ .repo-mi.all { border-top:1px solid var(--line2); margin-top:4px; padding-top:9px; color:var(--ink2); }
44
+
45
+ /* Tabs */
46
+ .tabs { display:flex; gap:4px; margin-left:auto; }
47
+ .tab { font-size:13px; color:var(--ink2); padding:5px 11px; border:0; border-radius:7px;
48
+ background:transparent; cursor:pointer; font:inherit; }
49
+ .tab--active { background:var(--ink); color:var(--bg); }
50
+
51
+ /* Crew strip */
52
+ .crew { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:26px 0 32px; }
53
+ .person { display:inline-flex; align-items:center; gap:9px; padding:5px 13px 5px 5px;
54
+ border:1px solid var(--line); border-radius:999px; background:var(--card); font-size:13px; }
55
+ .person--idle { opacity:.55; }
56
+ .avatar { width:24px; height:24px; border-radius:50%; display:grid; place-items:center;
57
+ font-size:10px; font-weight:700; color:#fff; flex:none; }
58
+ .person__name { font-weight:600; }
59
+ .person__doing { color:var(--ink3); font-size:12px; }
60
+
61
+ /* Board */
62
+ .board { display:grid; grid-template-columns:1fr 1px 1fr; gap:0; }
63
+ .board__rule { background:var(--line); }
64
+ .col { padding:0 36px; } .col:first-child { padding-left:0; } .col:last-child { padding-right:0; }
65
+ .colhead { display:flex; align-items:center; gap:8px; margin-bottom:6px; }
66
+ .colhead h2 { font-size:12px; font-weight:600; letter-spacing:.03em; margin:0;
67
+ text-transform:uppercase; color:var(--ink2); }
68
+ .colhead .n { font-size:11px; color:var(--ink3); background:var(--chip);
69
+ border-radius:999px; padding:1px 8px; }
70
+
71
+ .task { padding:16px 0; border-bottom:1px solid var(--line2); }
72
+ .task__r1 { display:flex; align-items:center; gap:8px; }
73
+ .task__who { font-size:13.5px; font-weight:600; }
74
+ .task__tag { font-size:11px; color:var(--ink3); }
75
+ .task__repo { font-size:11px; color:var(--terr); background:rgba(122,106,72,.1);
76
+ border-radius:4px; padding:0 6px; }
77
+ .task__stat { margin-left:auto; font-size:11px; font-weight:500; color:var(--ink3); }
78
+ .task__stat--drop { color:var(--drop); }
79
+ .livedot { width:7px; height:7px; border-radius:50%; background:var(--live); margin-left:auto; }
80
+ .task__intent { font-size:13.5px; margin:5px 0 0; line-height:1.45; }
81
+ .terr { margin-top:8px; display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
82
+ .terr__lbl { font-size:9.5px; text-transform:uppercase; letter-spacing:.05em; color:var(--ink3); }
83
+ .glob { font-family:ui-monospace, Menlo, monospace; font-size:11px; color:var(--terr);
84
+ background:rgba(122,106,72,.12); border:1px solid rgba(122,106,72,.22);
85
+ border-radius:5px; padding:1px 7px; }
86
+ .task__meta { font-size:11.5px; color:var(--ink3); margin-top:6px; }
87
+ /* expandable territory (2+ paths collapse to a one-line "N paths" toggle) */
88
+ .terrx { margin-top:8px; }
89
+ .terrx summary { list-style:none; cursor:pointer; display:flex; align-items:center;
90
+ gap:6px; flex-wrap:wrap; user-select:none; }
91
+ .terrx summary::-webkit-details-marker { display:none; }
92
+ .terrx .terr--full { margin-top:8px; }
93
+ .glob--more { cursor:pointer; color:var(--ink2); background:var(--chip);
94
+ border:1px solid var(--line); }
95
+ .glob--more .tw { color:var(--ink3); display:inline-block; transition:transform .15s; }
96
+ .terrx[open] summary .glob--more .tw { transform:rotate(90deg); }
97
+ /* grouped agent box: header + claims rail + collapsible "narrower" fold */
98
+ .grp { padding:16px 0; border-bottom:1px solid var(--line2); }
99
+ .grp__head { display:flex; align-items:center; gap:8px; }
100
+ .grp__who { font-size:13.5px; font-weight:600; }
101
+ .grp__tag { font-size:11px; color:var(--ink3); }
102
+ .grp__count { font-size:11px; color:var(--ink3); }
103
+ .grp__dot { width:7px; height:7px; border-radius:50%; background:var(--live); margin-left:auto; }
104
+ .claims { margin:10px 0 0 6px; padding-left:14px; border-left:1px solid var(--line); }
105
+ .claim { padding:6px 0; }
106
+ .claim + .claim { border-top:1px solid var(--line2); margin-top:6px; }
107
+ .fold { margin-top:10px; }
108
+ .fold summary { list-style:none; cursor:pointer; font-size:12px; color:var(--ink2);
109
+ display:inline-flex; align-items:center; gap:6px; user-select:none; }
110
+ .fold summary::-webkit-details-marker { display:none; }
111
+ .fold summary .tw { display:inline-block; color:var(--ink3); transition:transform .15s; }
112
+ .fold[open] summary .tw { transform:rotate(90deg); }
113
+ .fold__body { margin:8px 0 0 6px; padding-left:14px; border-left:1px dashed var(--line); }
114
+ .covered { font-size:10.5px; color:var(--ink3); font-style:italic; margin-top:4px; }
115
+ .day { font-size:11px; color:var(--ink3); padding:18px 0 2px; font-weight:600; }
116
+ .more { margin-top:20px; font-size:12.5px; color:var(--ink2); padding:7px 13px;
117
+ border:1px solid var(--line); border-radius:8px; background:rgba(255,250,238,.35);
118
+ cursor:pointer; }
119
+ .empty { color:var(--ink3); font-style:italic; padding:14px 0; }
120
+
121
+ /* Chat (re-themed; structure unchanged) */
122
+ .chat-wrap { background:var(--card); border:1px solid var(--line); border-radius:12px;
123
+ display:flex; flex-direction:column; overflow:hidden; height:calc(100vh - 150px); min-height:320px; }
124
+ .chat { flex:1; overflow-y:auto; padding:16px; }
125
+ .chat { scrollbar-width:thin; scrollbar-color:var(--line) transparent; }
126
+ .chat::-webkit-scrollbar { width:10px; }
127
+ .chat::-webkit-scrollbar-track { background:transparent; }
128
+ .chat::-webkit-scrollbar-thumb { background:var(--line); border-radius:999px;
129
+ border:3px solid var(--card); background-clip:padding-box; }
130
+ .chat::-webkit-scrollbar-thumb:hover { background:var(--ink3); background-clip:padding-box; }
131
+ .chat__note { padding:9px 16px; font-size:11.5px; color:var(--ink3); }
132
+ .msg { display:flex; gap:11px; padding:9px 0; }
133
+ .msg__avatar { flex:none; width:32px; height:32px; border-radius:50%; display:grid;
134
+ place-items:center; font-size:12px; font-weight:700; color:#fff; }
135
+ .msg__body { min-width:0; flex:1; }
136
+ .msg__head { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
137
+ .msg__who { font-weight:600; }
138
+ .msg__human { font-size:12px; color:var(--ink3); }
139
+ .msg__to { font-size:11.5px; color:var(--terr); }
140
+ .msg__time { font-size:11.5px; color:var(--ink3); margin-left:auto; }
141
+ .msg__text { margin-top:3px; padding:8px 12px; border-radius:4px 12px 12px 12px;
142
+ background:var(--chip); white-space:pre-wrap; word-break:break-word; }
143
+ .msg--targeted .msg__text { box-shadow:inset 3px 0 0 var(--terr); }
144
+
145
+ /* Operator's own messages — same group-chat structure, mirrored to the right. */
146
+ .msg--me { flex-direction:row-reverse; }
147
+ .msg--me .msg__head { flex-direction:row-reverse; }
148
+ .msg--me .msg__time { margin-left:0; margin-right:auto; }
149
+ .msg--me .msg__body { display:flex; flex-direction:column; align-items:flex-end; }
150
+ .msg--me .msg__text { border-radius:12px 4px 12px 12px; background:#d4e7d2; }
151
+ .msg--me.msg--targeted .msg__text { box-shadow:inset -3px 0 0 var(--terr); }
152
+
153
+ /* Composer */
154
+ .composer { position:relative; border-top:1px solid var(--line); padding:10px 12px; }
155
+ .chat-form { display:flex; gap:8px; }
156
+ .chat-input { flex:1; padding:9px 12px; border-radius:8px; border:1px solid var(--line);
157
+ background:var(--bg); color:var(--ink); font:inherit; }
158
+ .chat-input:focus { outline:none; border-color:var(--terr); }
159
+ .chat-send { padding:9px 16px; border-radius:8px; border:1px solid var(--ink);
160
+ background:var(--ink); color:var(--bg); font-weight:600; cursor:pointer; font:inherit; }
161
+ .chat-send:disabled { opacity:.5; cursor:default; }
162
+
163
+ /* @-mention autocomplete — floats above the input */
164
+ .mention-pop { position:absolute; left:12px; right:12px; bottom:56px; z-index:6;
165
+ background:var(--card); border:1px solid var(--line); border-radius:10px; padding:5px;
166
+ box-shadow:0 8px 24px rgba(50,40,20,.16); max-height:200px; overflow-y:auto; }
167
+ .mention-mi { display:flex; align-items:center; gap:9px; width:100%; text-align:left;
168
+ border:0; appearance:none; font-family:inherit; color:inherit; background:transparent;
169
+ border-radius:7px; padding:6px 8px; font-size:13px; cursor:pointer; }
170
+ .mention-mi:hover, .mention-mi.on { background:var(--chip); }
171
+ .mention-mi .ma { width:22px; height:22px; border-radius:50%; display:grid; place-items:center;
172
+ font-size:9px; font-weight:700; color:#fff; flex:none; }
173
+
174
+ /* Token gate */
175
+ .gate { max-width:420px; margin:12vh auto 0; text-align:center; }
176
+ .gate h1 { font-size:19px; margin:0 0 6px; }
177
+ .gate p { color:var(--ink2); margin:0 0 20px; }
178
+ .gate form { display:flex; gap:8px; }
179
+ .gate input { flex:1; padding:10px 12px; border-radius:8px; border:1px solid var(--line);
180
+ background:var(--card); color:var(--ink); }
181
+ .gate button { padding:10px 16px; border-radius:8px; border:1px solid var(--ink);
182
+ background:var(--ink); color:var(--bg); font-weight:600; cursor:pointer; }
183
+
184
+ /* --- Multi-workspace hosted shell + config surface (functional, not polished;
185
+ a dedicated styling sub-project refines this). Reuses the board's tokens. --- */
186
+ .shepherd-root { max-width:1100px; margin:0 auto; padding:40px 44px; }
187
+ .shepherd-root > main { padding-top:18px; }
188
+ .shepherd-config { display:flex; flex-direction:column; gap:24px; }
189
+ .shepherd-config h2 { font-size:16px; margin:0 0 4px; }
190
+ .shepherd-workspaces, .shepherd-members, .shepherd-connect-agent {
191
+ display:flex; flex-direction:column; gap:10px;
192
+ padding:16px; border:1px solid var(--line); border-radius:10px; background:var(--card); }
193
+ .shepherd-workspaces .switcher { display:flex; flex-wrap:wrap; gap:8px; }
194
+ .shepherd-workspaces .role { color:var(--ink3); font-size:12px; }
195
+ .shepherd-workspaces .admin, .shepherd-workspaces .invites { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
196
+ .shepherd-config input, .shepherd-config select {
197
+ padding:8px 10px; border-radius:8px; border:1px solid var(--line);
198
+ background:var(--bg); color:var(--ink); }
199
+ .shepherd-config button {
200
+ padding:8px 14px; border-radius:8px; border:1px solid var(--ink);
201
+ background:var(--ink); color:var(--bg); font-weight:600; cursor:pointer; }
202
+ .shepherd-config button:disabled { opacity:.55; cursor:default; }
203
+ .shepherd-config .invite-result, .shepherd-connect-agent .token-once {
204
+ font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px;
205
+ word-break:break-all; padding:8px 10px; border-radius:8px;
206
+ background:var(--chip); border:1px solid var(--line2); }
207
+ .shepherd-config .revoked { color:var(--ink3); text-decoration:line-through; }
208
+ .shepherd-empty-state { text-align:center; color:var(--ink2); padding:48px 24px; }
209
+ .shepherd-empty-state button { margin-top:14px; }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Pure, auth-agnostic page logic for the Shepherd wallboard.
3
+ *
4
+ * Ported verbatim from packages/hub/public/app.js (the original plain-JS single
5
+ * static page), adding TypeScript types only — behavior is byte-for-byte
6
+ * identical, as the characterization suite in test/logic.test.ts pins. These are
7
+ * the page's "Pure helpers" plus the "Glob containment + active-claim grouping"
8
+ * section; the browser-only DOM-wiring half of app.js is NOT ported here.
9
+ *
10
+ * Times are computed against the server's clock (`serverTime` in the payload),
11
+ * not the browser's, so countdowns and "last seen" stay correct under skew —
12
+ * hence every time helper takes an explicit `nowMs`/`endedIso` rather than
13
+ * reading `Date.now()`.
14
+ *
15
+ * Note: this module intentionally keeps its OWN browser-subset glob matcher
16
+ * (normalizeGlob/segmentCovers/patternCovers below); it does not import the
17
+ * server-side packages/hub/src/globs.ts, which is a deliberately-separate
18
+ * duplicate.
19
+ */
20
+ import type { WorkspaceTaskT } from "@shepherd/shared";
21
+ /**
22
+ * Human "N ago" for a past ISO timestamp, relative to `nowMs` (epoch ms).
23
+ *
24
+ * @param iso - A past instant as an ISO 8601 string.
25
+ * @param nowMs - The reference "now" in epoch milliseconds (the server clock).
26
+ * @returns A short relative label, e.g. `"just now"`, `"30s ago"`, `"2h ago"`.
27
+ */
28
+ export declare function formatRelative(iso: string, nowMs: number): string;
29
+ /**
30
+ * Human "expires in N" / "expired" for a future ISO timestamp, relative to
31
+ * `nowMs`.
32
+ *
33
+ * @param iso - The expiry instant as an ISO 8601 string.
34
+ * @param nowMs - The reference "now" in epoch milliseconds (the server clock).
35
+ * @returns `"expired"` once at/past the instant, else e.g. `"expires in 4m"`.
36
+ */
37
+ export declare function formatCountdown(iso: string, nowMs: number): string;
38
+ /**
39
+ * Deterministic chat color for an agent name — same name always maps to the
40
+ * same hue, so each speaker reads consistently across the announcement thread.
41
+ * Saturation/lightness are tuned to stay legible on the dark background.
42
+ *
43
+ * @param name - The agent name (may be empty, which yields hue 0 — never throws).
44
+ * @returns An `hsl(...)` CSS color string.
45
+ */
46
+ export declare function colorForName(name: string): string;
47
+ /**
48
+ * Up to two initials for an avatar. Prefers the capital letters of a CamelCase
49
+ * agent name (RedDragon → RD); otherwise the first two letters (alice → AL).
50
+ *
51
+ * @param name - The agent name; empty yields the `"?"` placeholder.
52
+ * @returns A 1–2 character uppercase label.
53
+ */
54
+ export declare function initialsFor(name: string): string;
55
+ /**
56
+ * The slice of a mention token the autocomplete operates on: `start`/`end` are
57
+ * the text range to replace when a suggestion is accepted (start = the `@`),
58
+ * and `query` is the text typed after the `@` (possibly empty).
59
+ */
60
+ export interface MentionMatch {
61
+ /** Index of the `@` — the start of the replaceable slice. */
62
+ start: number;
63
+ /** The caret index — the end of the replaceable slice. */
64
+ end: number;
65
+ /** The mention-name characters between the `@` and the caret. */
66
+ query: string;
67
+ }
68
+ /**
69
+ * The active `@mention` immediately to the LEFT of the caret, or null when the
70
+ * caret isn't in one. Drives the autocomplete: the `@` must start the text or
71
+ * follow whitespace (so email addresses like `a@b` don't trigger it), and only
72
+ * mention-name characters may sit between it and the caret.
73
+ *
74
+ * @param text - The full input text.
75
+ * @param caretIndex - The caret position within `text`.
76
+ * @returns The mention slice and query, or `null` when not in a mention.
77
+ */
78
+ export declare function parseMention(text: string, caretIndex: number): MentionMatch | null;
79
+ /**
80
+ * The first `@mention` in `text` that matches a known agent name, returned in
81
+ * the name's canonical casing (so `@reddragon` resolves to `RedDragon`), or null
82
+ * when none match. This is what turns a typed message into a directed DM; an
83
+ * unmatched `@foo` is treated as plain text and the message broadcasts.
84
+ *
85
+ * @param text - The message text to scan.
86
+ * @param knownNames - Canonical agent names to resolve against (case-insensitive).
87
+ * @returns The canonical name of the first matching mention, or `null`.
88
+ */
89
+ export declare function extractTarget(text: string, knownNames: string[]): string | null;
90
+ /**
91
+ * Sorted unique repo identifiers present across the given tasks.
92
+ *
93
+ * @param tasks - Items carrying a `repo` (a structural subset of {@link WorkspaceTaskT}).
94
+ * @returns The distinct repos, ascending.
95
+ */
96
+ export declare function distinctRepos(tasks: Pick<WorkspaceTaskT, "repo">[]): string[];
97
+ /**
98
+ * True if `item.repo` matches the selection; `null`/`"__all__"` = all repos.
99
+ *
100
+ * @param item - Anything carrying a `repo`.
101
+ * @param selected - The selected repo, or `null`/`"__all__"` for all.
102
+ * @returns Whether the item belongs to the current board filter.
103
+ */
104
+ export declare function matchesRepo(item: {
105
+ repo: string;
106
+ }, selected: string | null): boolean;
107
+ /**
108
+ * Live agents addressable by @mention under the current board filter: only those
109
+ * whose (most-recent) session sits in the selected repo, so a message can't be
110
+ * directed at someone who isn't in the repo you're viewing. Mirrors the crew the
111
+ * board already shows for that repo; in All-repos mode everyone live is
112
+ * addressable. Returns de-duplicated canonical names, sorted.
113
+ *
114
+ * @param agents - Agents carrying `name`, `presence`, and `repo` (a structural
115
+ * subset of {@link WorkspaceAgentT}).
116
+ * @param selectedRepo - The selected repo, or `null`/`"__all__"` for all.
117
+ * @returns Sorted, de-duplicated names of mentionable live agents.
118
+ */
119
+ export declare function mentionableAgents(agents: {
120
+ name: string;
121
+ presence: string;
122
+ repo: string;
123
+ }[], selectedRepo: string | null): string[];
124
+ /**
125
+ * First-load default repo: newest active task's repo, else newest task's, else
126
+ * null.
127
+ *
128
+ * @param tasks - Tasks carrying `repo` and `status` (a structural subset of
129
+ * {@link WorkspaceTaskT}); assumed newest-first.
130
+ * @returns The repo to select on first load, or `null` when there are no tasks.
131
+ */
132
+ export declare function defaultRepo(tasks: Pick<WorkspaceTaskT, "repo" | "status">[]): string | null;
133
+ /**
134
+ * Human label for a history task's status.
135
+ *
136
+ * @param status - The task status.
137
+ * @returns `"dropped"` for a dropped task, otherwise `"done"`.
138
+ */
139
+ export declare function statusLabel(status: string): string;
140
+ /**
141
+ * "active Nm/Nh" for the created->ended span; "" when the task hasn't ended.
142
+ *
143
+ * @param createdIso - The task's creation instant (ISO 8601 string).
144
+ * @param endedIso - The task's end instant, or `null` while still active.
145
+ * @returns A duration label, or the empty string for an unfinished task.
146
+ */
147
+ export declare function formatActiveDuration(createdIso: string, endedIso: string | null): string;
148
+ /**
149
+ * Local-day bucket label: "Today" / "Yesterday" / "Mon D".
150
+ *
151
+ * @param iso - The instant to bucket (ISO 8601 string).
152
+ * @param nowMs - The reference "now" in epoch milliseconds.
153
+ * @returns `"Today"`, `"Yesterday"`, or a short month/day label.
154
+ */
155
+ export declare function dayBucket(iso: string, nowMs: number): string;
156
+ /**
157
+ * Does glob set `outer` fully cover glob set `inner` — every path `inner` could
158
+ * touch already inside `outer`? Conservative: unsure -> false, so a caller only
159
+ * folds a claim it's certain is contained (a false negative just shows both
160
+ * claims, which is safe). An empty `inner` is trivially covered.
161
+ *
162
+ * @param outer - The candidate broader glob set.
163
+ * @param inner - The candidate narrower glob set.
164
+ * @returns Whether every glob in `inner` is covered by some glob in `outer`.
165
+ */
166
+ export declare function globsCover(outer: string[], inner: string[]): boolean;
167
+ /**
168
+ * The fields a claim must carry to be grouped: its agent, glob territory, and
169
+ * creation order, plus the header fields surfaced on the group. A structural
170
+ * subset of {@link WorkspaceTaskT}, so callers may pass partial task shapes.
171
+ */
172
+ export type ClaimLike = Pick<WorkspaceTaskT, "agentName" | "pathGlobs" | "createdAt" | "model" | "program" | "repo">;
173
+ /**
174
+ * One agent's grouped active claims for the board. `primaries` are the visible
175
+ * claims; `narrower` are those strictly covered by a broader sibling and folded
176
+ * away. Header fields (model/program/repo) come from the group's newest claim.
177
+ *
178
+ * @typeParam T - The concrete claim shape passed in, preserved on the members.
179
+ */
180
+ export interface ClaimGroup<T extends ClaimLike = ClaimLike> {
181
+ /** The agent these claims belong to. */
182
+ agentName: string;
183
+ /** Representative model from the group's newest claim. */
184
+ model: T["model"];
185
+ /** Representative program from the group's newest claim. */
186
+ program: T["program"];
187
+ /** Representative repo from the group's newest claim. */
188
+ repo: T["repo"];
189
+ /** Visible claims, newest-first. */
190
+ primaries: T[];
191
+ /** Claims folded under a broader sibling, newest-first. */
192
+ narrower: T[];
193
+ }
194
+ /**
195
+ * Group active claims by agent for the board. Within an agent's claims, one that
196
+ * is STRICTLY covered by a broader sibling (the sibling covers it but not vice
197
+ * versa, so identical claims don't cannibalise each other) folds into `narrower`;
198
+ * everything else stays a visible `primary`. Distinct or merely-overlapping
199
+ * claims all remain primaries. Pure — no DOM, no clock; `createdAt` drives order.
200
+ *
201
+ * Groups are newest-claim-first; `primaries`/`narrower` are each newest-first;
202
+ * header fields (model/program/repo) come from the group's newest claim.
203
+ *
204
+ * @param tasks - The active claims to group (a structural subset of
205
+ * {@link WorkspaceTaskT}).
206
+ * @returns One {@link ClaimGroup} per agent, newest-claim-first.
207
+ */
208
+ export declare function groupActiveClaims<T extends ClaimLike>(tasks: T[]): ClaimGroup<T>[];