@korso/shepherd-ui 0.1.0 → 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 (99) hide show
  1. package/dist/ShepherdProvider.d.ts +12 -0
  2. package/dist/ShepherdProvider.js +33 -0
  3. package/dist/ShepherdProvider.test.d.ts +1 -0
  4. package/dist/ShepherdProvider.test.js +47 -0
  5. package/dist/ShepherdRoot.d.ts +9 -0
  6. package/dist/ShepherdRoot.js +88 -0
  7. package/dist/ShepherdRoot.routing.test.d.ts +1 -0
  8. package/dist/ShepherdRoot.routing.test.js +61 -0
  9. package/dist/ShepherdRoot.test.d.ts +1 -0
  10. package/dist/ShepherdRoot.test.js +37 -0
  11. package/dist/app/assets/index-CDOCIg6s.js +11 -0
  12. package/dist/app/index.html +12 -0
  13. package/dist/client.d.ts +107 -0
  14. package/dist/client.js +238 -0
  15. package/dist/client.test.d.ts +1 -0
  16. package/dist/client.test.js +298 -0
  17. package/dist/components/ActiveList.d.ts +22 -0
  18. package/dist/components/ActiveList.js +59 -0
  19. package/dist/components/Chat.d.ts +30 -0
  20. package/dist/components/Chat.js +61 -0
  21. package/dist/components/Composer.d.ts +37 -0
  22. package/dist/components/Composer.js +146 -0
  23. package/dist/components/Crew.d.ts +23 -0
  24. package/dist/components/Crew.js +31 -0
  25. package/dist/components/Dashboard.d.ts +34 -0
  26. package/dist/components/Dashboard.js +209 -0
  27. package/dist/components/DoneList.d.ts +29 -0
  28. package/dist/components/DoneList.js +50 -0
  29. package/dist/components/RepoSelect.d.ts +38 -0
  30. package/dist/components/RepoSelect.js +56 -0
  31. package/dist/components/Territory.d.ts +21 -0
  32. package/dist/components/Territory.js +21 -0
  33. package/dist/config/ConnectAgent.d.ts +10 -0
  34. package/dist/config/ConnectAgent.js +119 -0
  35. package/dist/config/ConnectAgent.test.d.ts +1 -0
  36. package/dist/config/ConnectAgent.test.js +92 -0
  37. package/dist/config/EmptyState.d.ts +12 -0
  38. package/dist/config/EmptyState.js +4 -0
  39. package/dist/config/EmptyState.test.d.ts +1 -0
  40. package/dist/config/EmptyState.test.js +32 -0
  41. package/dist/config/Members.d.ts +10 -0
  42. package/dist/config/Members.js +83 -0
  43. package/dist/config/Members.test.d.ts +1 -0
  44. package/dist/config/Members.test.js +66 -0
  45. package/dist/config/Workspaces.d.ts +12 -0
  46. package/dist/config/Workspaces.js +96 -0
  47. package/dist/config/Workspaces.test.d.ts +1 -0
  48. package/dist/config/Workspaces.test.js +90 -0
  49. package/dist/config/index.d.ts +8 -0
  50. package/dist/config/index.js +6 -0
  51. package/dist/context.d.ts +28 -0
  52. package/dist/context.js +36 -0
  53. package/dist/index.cjs +5289 -0
  54. package/dist/index.cjs.map +1 -0
  55. package/dist/index.d.cts +149 -0
  56. package/dist/index.d.ts +18 -0
  57. package/dist/index.js +15 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/lib/Dashboard-CG6KeFQ3.js +1214 -0
  60. package/dist/lib/index.d.ts +438 -16
  61. package/dist/lib/index.js +455 -6
  62. package/dist/lib/selfhost.js +9 -9
  63. package/dist/lib/styles.css +27 -0
  64. package/dist/logic.d.ts +208 -0
  65. package/dist/logic.js +372 -0
  66. package/dist/main.d.ts +1 -0
  67. package/dist/main.js +11 -0
  68. package/dist/selfhost/assets/{index-tSyzirZa.css → index-BZmImfYZ.css} +1 -1
  69. package/dist/selfhost/assets/index-CS268KSw.js +40 -0
  70. package/dist/selfhost/index.html +2 -2
  71. package/dist/selfhost.d.ts +22 -0
  72. package/dist/selfhost.js +97 -0
  73. package/dist/test/mockClient.d.ts +6 -0
  74. package/dist/test/mockClient.js +45 -0
  75. package/dist/test/setup.d.ts +1 -0
  76. package/dist/test/setup.js +2 -0
  77. package/dist/useLandscapePolling.d.ts +63 -0
  78. package/dist/useLandscapePolling.js +129 -0
  79. package/dist/views/Chat.d.ts +5 -0
  80. package/dist/views/Chat.js +60 -0
  81. package/dist/views/Chat.test.d.ts +1 -0
  82. package/dist/views/Chat.test.js +92 -0
  83. package/dist/views/Config.d.ts +12 -0
  84. package/dist/views/Config.js +6 -0
  85. package/dist/views/Tasks.d.ts +5 -0
  86. package/dist/views/Tasks.js +75 -0
  87. package/dist/views/Tasks.test.d.ts +1 -0
  88. package/dist/views/Tasks.test.js +115 -0
  89. package/dist/views/useLandscape.d.ts +14 -0
  90. package/dist/views/useLandscape.js +74 -0
  91. package/dist/views/useLandscape.test.d.ts +1 -0
  92. package/dist/views/useLandscape.test.js +101 -0
  93. package/dist/views/wallboard.d.ts +46 -0
  94. package/dist/views/wallboard.js +209 -0
  95. package/dist/views/wallboard.test.d.ts +1 -0
  96. package/dist/views/wallboard.test.js +143 -0
  97. package/package.json +1 -1
  98. package/dist/lib/Dashboard-CB6SL-J2.js +0 -1064
  99. package/dist/selfhost/assets/index-BBx3vo2Y.js +0 -40
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { formatRelative, statusLabel, formatActiveDuration, dayBucket, initialsFor, colorForName, globsCover, groupActiveClaims, extractTarget, } from "./wallboard.js";
3
+ // ---------------------------------------------------------------------------
4
+ // wallboard helpers — the pure logic ported from packages/hub/public/app.js,
5
+ // driving the React Tasks/Chat views. Mirrors that module's behavior so the
6
+ // ported board reads the same data the same way (functional parity).
7
+ // ---------------------------------------------------------------------------
8
+ function task(over = {}) {
9
+ return {
10
+ agentName: "alice",
11
+ program: "claude",
12
+ model: "opus",
13
+ repo: "acme/web",
14
+ intent: "do a thing",
15
+ pathGlobs: ["src/**"],
16
+ status: "active",
17
+ createdAt: "2026-06-29T00:00:00.000Z",
18
+ endedAt: null,
19
+ ...over,
20
+ };
21
+ }
22
+ describe("formatRelative", () => {
23
+ const now = Date.parse("2026-06-29T01:00:00.000Z");
24
+ it("says 'just now' under 5s", () => {
25
+ expect(formatRelative("2026-06-29T00:59:58.000Z", now)).toBe("just now");
26
+ });
27
+ it("renders seconds / minutes / hours / days", () => {
28
+ expect(formatRelative("2026-06-29T00:59:30.000Z", now)).toBe("30s ago");
29
+ expect(formatRelative("2026-06-29T00:30:00.000Z", now)).toBe("30m ago");
30
+ expect(formatRelative("2026-06-28T22:00:00.000Z", now)).toBe("3h ago");
31
+ expect(formatRelative("2026-06-26T01:00:00.000Z", now)).toBe("3d ago");
32
+ });
33
+ });
34
+ describe("statusLabel", () => {
35
+ it("maps dropped → dropped, everything else → done", () => {
36
+ expect(statusLabel("dropped")).toBe("dropped");
37
+ expect(statusLabel("done")).toBe("done");
38
+ });
39
+ });
40
+ describe("formatActiveDuration", () => {
41
+ it("is empty when not ended", () => {
42
+ expect(formatActiveDuration("2026-06-29T00:00:00.000Z", null)).toBe("");
43
+ });
44
+ it("renders minutes then hours", () => {
45
+ expect(formatActiveDuration("2026-06-29T00:00:00.000Z", "2026-06-29T00:30:00.000Z")).toBe("active 30m");
46
+ expect(formatActiveDuration("2026-06-29T00:00:00.000Z", "2026-06-29T03:00:00.000Z")).toBe("active 3h");
47
+ });
48
+ });
49
+ describe("dayBucket", () => {
50
+ const now = Date.parse("2026-06-29T12:00:00.000Z");
51
+ it("buckets today / yesterday", () => {
52
+ expect(dayBucket("2026-06-29T08:00:00.000Z", now)).toBe("Today");
53
+ expect(dayBucket("2026-06-28T08:00:00.000Z", now)).toBe("Yesterday");
54
+ });
55
+ });
56
+ describe("initialsFor", () => {
57
+ it("prefers two capitals of a CamelCase name", () => {
58
+ expect(initialsFor("RedDragon")).toBe("RD");
59
+ });
60
+ it("falls back to the first two letters", () => {
61
+ expect(initialsFor("alice")).toBe("AL");
62
+ });
63
+ it("handles empty", () => {
64
+ expect(initialsFor("")).toBe("?");
65
+ });
66
+ });
67
+ describe("groupActiveClaims", () => {
68
+ it("groups claims by agent", () => {
69
+ const groups = groupActiveClaims([
70
+ task({ agentName: "alice", pathGlobs: ["a/**"] }),
71
+ task({ agentName: "bob", pathGlobs: ["b/**"] }),
72
+ ]);
73
+ expect(groups.map((g) => g.agentName).sort()).toEqual(["alice", "bob"]);
74
+ });
75
+ it("folds a strictly-covered claim into narrower", () => {
76
+ const groups = groupActiveClaims([
77
+ task({
78
+ agentName: "alice",
79
+ pathGlobs: ["src/**"],
80
+ createdAt: "2026-06-29T00:00:00.000Z",
81
+ }),
82
+ task({
83
+ agentName: "alice",
84
+ pathGlobs: ["src/a.ts"],
85
+ createdAt: "2026-06-29T00:01:00.000Z",
86
+ }),
87
+ ]);
88
+ expect(groups).toHaveLength(1);
89
+ const g = groups[0];
90
+ expect(g.primaries.map((p) => p.pathGlobs)).toEqual([["src/**"]]);
91
+ expect(g.narrower.map((p) => p.pathGlobs)).toEqual([["src/a.ts"]]);
92
+ });
93
+ });
94
+ describe("colorForName", () => {
95
+ it("is deterministic — the same name always maps to the same hue", () => {
96
+ expect(colorForName("RedDragon")).toBe(colorForName("RedDragon"));
97
+ });
98
+ it("distinguishes different names", () => {
99
+ expect(colorForName("alice")).not.toBe(colorForName("bob"));
100
+ });
101
+ it("returns a well-formed hsl() string", () => {
102
+ expect(colorForName("alice")).toMatch(/^hsl\(\d+, 38%, 42%\)$/);
103
+ });
104
+ });
105
+ describe("globsCover", () => {
106
+ it("treats an empty inner set as covered", () => {
107
+ expect(globsCover(["src/**"], [])).toBe(true);
108
+ });
109
+ it("covers a literal under a '**' wildcard", () => {
110
+ expect(globsCover(["src/**"], ["src/a.ts"])).toBe(true);
111
+ });
112
+ it("does not cover across a path segment with a single '*'", () => {
113
+ // '*' is a single-segment wildcard: it must not span the '/'.
114
+ expect(globsCover(["src/*"], ["src/a/b.ts"])).toBe(false);
115
+ });
116
+ it("lets '**' span multiple path segments", () => {
117
+ expect(globsCover(["**"], ["src/deep/nested/file.ts"])).toBe(true);
118
+ });
119
+ it("matches a literal segment exactly, not a wildcard sibling", () => {
120
+ expect(globsCover(["src/a.ts"], ["src/a.ts"])).toBe(true);
121
+ expect(globsCover(["src/a.ts"], ["src/b.ts"])).toBe(false);
122
+ });
123
+ it("does not let a narrower literal cover a wildcard", () => {
124
+ expect(globsCover(["src/a.ts"], ["src/*"])).toBe(false);
125
+ });
126
+ it("normalizes '.' and '..' segments before comparing", () => {
127
+ // "src/foo/../**" normalizes to "src/**", which covers "src/a.ts".
128
+ expect(globsCover(["src/foo/../**"], ["src/a.ts"])).toBe(true);
129
+ });
130
+ it("requires every inner glob to be covered by some outer glob", () => {
131
+ expect(globsCover(["src/**"], ["src/a.ts", "lib/b.ts"])).toBe(false);
132
+ expect(globsCover(["src/**", "lib/**"], ["src/a.ts", "lib/b.ts"])).toBe(true);
133
+ });
134
+ });
135
+ describe("extractTarget", () => {
136
+ it("resolves the first @mention matching a known name, canonical-cased", () => {
137
+ expect(extractTarget("hey @reddragon look", ["RedDragon", "alice"])).toBe("RedDragon");
138
+ });
139
+ it("returns null when no mention matches", () => {
140
+ expect(extractTarget("no mention here", ["alice"])).toBeNull();
141
+ expect(extractTarget("hi @nobody", ["alice"])).toBeNull();
142
+ });
143
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korso/shepherd-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Auth-agnostic React UI library for the Shepherd wallboard (Dashboard, client, provider). Host injects auth; the library never reads tokens.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",