@korso/shepherd-ui 0.1.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/README.md +47 -0
- package/dist/lib/Dashboard-CB6SL-J2.js +1064 -0
- package/dist/lib/index.d.ts +306 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/selfhost.d.ts +25 -0
- package/dist/lib/selfhost.js +68 -0
- package/dist/lib/styles.css +182 -0
- package/dist/selfhost/assets/index-BBx3vo2Y.js +40 -0
- package/dist/selfhost/assets/index-tSyzirZa.css +1 -0
- package/dist/selfhost/index.html +13 -0
- package/package.json +67 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a browser fetch client for the Shepherd hub with exactly two methods.
|
|
7
|
+
* Each call wires up an AbortController timeout (cleared in `finally`), merges
|
|
8
|
+
* the injected auth headers over a JSON content-type base, and Zod-parses ONLY
|
|
9
|
+
* the response at the boundary — the request body is forwarded verbatim because
|
|
10
|
+
* the caller owns its input shape.
|
|
11
|
+
*
|
|
12
|
+
* @param config - Base URL, optional injected auth, 401 hook, and timeout.
|
|
13
|
+
* @returns A {@link ShepherdClient}.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createShepherdClient(config: ShepherdClientConfig): ShepherdClient;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The Shepherd wallboard shell. Composes {@link useLandscapePolling} (which reads
|
|
19
|
+
* the client from context, keeping this auth-agnostic) with the six leaf
|
|
20
|
+
* components, owning only the UI state app.js kept in module variables:
|
|
21
|
+
*
|
|
22
|
+
* - `activeTab` — persisted to `"shepherd.tab"` (default `"tasks"`).
|
|
23
|
+
* - `selectedRepo` — persisted to `"shepherd.repo"`; `null` derives a default on
|
|
24
|
+
* the first render with >=2 repos (see {@link resolveSelectedRepo}).
|
|
25
|
+
* - `doneShown` — the history page size, grown a page at a time by "Load older".
|
|
26
|
+
*
|
|
27
|
+
* Header chrome mirrors app.js: brand, the repo filter, vitals (online/active
|
|
28
|
+
* counts under the current filter), the poll status indicator, a freshness
|
|
29
|
+
* string, and the tab strip. The body shows the Tasks panel (crew + active +
|
|
30
|
+
* done) or the Chat panel (feed + composer); the composer's post-send hook is the
|
|
31
|
+
* polling hook's `refresh`, so a sent message shows up immediately.
|
|
32
|
+
*
|
|
33
|
+
* Renders the header even before the first snapshot arrives so the board never
|
|
34
|
+
* blanks — the panels simply render their own empty states off an empty
|
|
35
|
+
* landscape.
|
|
36
|
+
*
|
|
37
|
+
* @returns The dashboard element.
|
|
38
|
+
*/
|
|
39
|
+
export declare function Dashboard(): ReactElement;
|
|
40
|
+
|
|
41
|
+
/** Props for {@link Dashboard}. Currently empty — the client comes from context. */
|
|
42
|
+
export declare type DashboardProps = Record<string, never>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The wallboard's two read/write operations against the Shepherd hub. Responses
|
|
46
|
+
* are Zod-parsed at the boundary so callers receive contract-typed values.
|
|
47
|
+
*/
|
|
48
|
+
export declare interface ShepherdClient {
|
|
49
|
+
/** GET the unfiltered whole-workspace view (agents, tasks, announcements). */
|
|
50
|
+
getLandscape(): Promise<WorkspaceLandscapeResponseT>;
|
|
51
|
+
/** POST an operator announcement (broadcast or @targeted) to the workspace. */
|
|
52
|
+
announce(req: WorkspaceAnnounceRequestT): Promise<WorkspaceAnnounceResponseT>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Construction options for {@link createShepherdClient}. Auth is supplied
|
|
57
|
+
* externally via `getAuthHeaders` so the core client stays auth-agnostic — it
|
|
58
|
+
* never reads tokens, localStorage, or a BFF; it only merges whatever headers
|
|
59
|
+
* the host injects and notifies the host on a 401 via `onUnauthorized`.
|
|
60
|
+
*/
|
|
61
|
+
export declare interface ShepherdClientConfig {
|
|
62
|
+
/** Hub origin; a trailing slash is tolerated and normalised away. */
|
|
63
|
+
baseUrl: string;
|
|
64
|
+
/** Returns auth headers to merge over the JSON content-type base, per call. */
|
|
65
|
+
getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
66
|
+
/** Invoked once when the hub responds 401, before the error is thrown. */
|
|
67
|
+
onUnauthorized?: () => void;
|
|
68
|
+
/** Per-request abort timeout in ms; defaults to {@link DEFAULT_TIMEOUT_MS}. */
|
|
69
|
+
timeoutMs?: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The single error type surfaced by the client. A missing `status` means a
|
|
74
|
+
* transport failure (network error or request timeout); a present `status` is
|
|
75
|
+
* the upstream HTTP status of a non-2xx response. One class — distinguished by
|
|
76
|
+
* the optional `status` — keeps callers from having to branch on two error
|
|
77
|
+
* types just to tell "couldn't reach the hub" from "the hub said no".
|
|
78
|
+
*/
|
|
79
|
+
export declare class ShepherdClientError extends Error {
|
|
80
|
+
/** Upstream HTTP status for a non-2xx response; absent for transport errors. */
|
|
81
|
+
readonly status?: number;
|
|
82
|
+
/**
|
|
83
|
+
* @param message - Human-readable failure description.
|
|
84
|
+
* @param status - Upstream HTTP status, omitted for network/abort failures.
|
|
85
|
+
*/
|
|
86
|
+
constructor(message: string, status?: number);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Supplies a {@link ShepherdClient} to the React subtree. This is the seam that
|
|
91
|
+
* keeps the dashboard auth-agnostic: the host constructs a client (wiring in
|
|
92
|
+
* whatever auth it uses) and injects it here, so components below only ever see
|
|
93
|
+
* the auth-neutral client interface — never tokens or transport details.
|
|
94
|
+
*
|
|
95
|
+
* @param props - The client to provide and the children that consume it.
|
|
96
|
+
* @returns A provider element wrapping `children`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function ShepherdClientProvider({ client, children, }: ShepherdClientProviderProps): ReactNode;
|
|
99
|
+
|
|
100
|
+
/** Props for {@link ShepherdClientProvider}. */
|
|
101
|
+
declare interface ShepherdClientProviderProps {
|
|
102
|
+
/** The client made available to every descendant via the context. */
|
|
103
|
+
client: ShepherdClient;
|
|
104
|
+
/** The subtree that may read the client through {@link useShepherdClient}. */
|
|
105
|
+
children: ReactNode;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reads the {@link ShepherdClient} from context. Throws when called outside a
|
|
110
|
+
* {@link ShepherdClientProvider} so a missing provider surfaces as an immediate,
|
|
111
|
+
* named error at render time instead of a confusing null-dereference later.
|
|
112
|
+
*
|
|
113
|
+
* @returns The client supplied by the nearest provider.
|
|
114
|
+
* @throws Error When invoked with no {@link ShepherdClientProvider} ancestor.
|
|
115
|
+
*/
|
|
116
|
+
export declare function useShepherdClient(): ShepherdClient;
|
|
117
|
+
|
|
118
|
+
declare const WorkspaceAnnounceRequest: z.ZodObject<{
|
|
119
|
+
body: z.ZodString;
|
|
120
|
+
targetAgentName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
121
|
+
repo: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
122
|
+
}, "strip", z.ZodTypeAny, {
|
|
123
|
+
body: string;
|
|
124
|
+
targetAgentName?: string | null | undefined;
|
|
125
|
+
repo?: string | null | undefined;
|
|
126
|
+
}, {
|
|
127
|
+
body: string;
|
|
128
|
+
targetAgentName?: string | null | undefined;
|
|
129
|
+
repo?: string | null | undefined;
|
|
130
|
+
}>;
|
|
131
|
+
|
|
132
|
+
declare type WorkspaceAnnounceRequestT = z.infer<typeof WorkspaceAnnounceRequest>;
|
|
133
|
+
|
|
134
|
+
declare const WorkspaceAnnounceResponse: z.ZodObject<{
|
|
135
|
+
ok: z.ZodLiteral<true>;
|
|
136
|
+
announcementIds: z.ZodArray<z.ZodNumber, "many">;
|
|
137
|
+
}, "strip", z.ZodTypeAny, {
|
|
138
|
+
ok: true;
|
|
139
|
+
announcementIds: number[];
|
|
140
|
+
}, {
|
|
141
|
+
ok: true;
|
|
142
|
+
announcementIds: number[];
|
|
143
|
+
}>;
|
|
144
|
+
|
|
145
|
+
declare type WorkspaceAnnounceResponseT = z.infer<typeof WorkspaceAnnounceResponse>;
|
|
146
|
+
|
|
147
|
+
declare const WorkspaceLandscapeResponse: z.ZodObject<{
|
|
148
|
+
agents: z.ZodArray<z.ZodObject<{
|
|
149
|
+
name: z.ZodString;
|
|
150
|
+
human: z.ZodString;
|
|
151
|
+
program: z.ZodString;
|
|
152
|
+
model: z.ZodNullable<z.ZodString>;
|
|
153
|
+
repo: z.ZodNullable<z.ZodString>;
|
|
154
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
155
|
+
lastHeartbeatAt: z.ZodNullable<z.ZodString>;
|
|
156
|
+
presence: z.ZodEnum<["live", "offline"]>;
|
|
157
|
+
}, "strip", z.ZodTypeAny, {
|
|
158
|
+
human: string;
|
|
159
|
+
branch: string | null;
|
|
160
|
+
name: string;
|
|
161
|
+
program: string;
|
|
162
|
+
model: string | null;
|
|
163
|
+
repo: string | null;
|
|
164
|
+
lastHeartbeatAt: string | null;
|
|
165
|
+
presence: "live" | "offline";
|
|
166
|
+
}, {
|
|
167
|
+
human: string;
|
|
168
|
+
branch: string | null;
|
|
169
|
+
name: string;
|
|
170
|
+
program: string;
|
|
171
|
+
model: string | null;
|
|
172
|
+
repo: string | null;
|
|
173
|
+
lastHeartbeatAt: string | null;
|
|
174
|
+
presence: "live" | "offline";
|
|
175
|
+
}>, "many">;
|
|
176
|
+
tasks: z.ZodArray<z.ZodObject<{
|
|
177
|
+
agentName: z.ZodString;
|
|
178
|
+
program: z.ZodString;
|
|
179
|
+
model: z.ZodNullable<z.ZodString>;
|
|
180
|
+
repo: z.ZodString;
|
|
181
|
+
intent: z.ZodString;
|
|
182
|
+
pathGlobs: z.ZodArray<z.ZodString, "many">;
|
|
183
|
+
status: z.ZodEnum<["active", "done", "dropped"]>;
|
|
184
|
+
createdAt: z.ZodString;
|
|
185
|
+
endedAt: z.ZodNullable<z.ZodString>;
|
|
186
|
+
}, "strip", z.ZodTypeAny, {
|
|
187
|
+
agentName: string;
|
|
188
|
+
status: "active" | "done" | "dropped";
|
|
189
|
+
intent: string;
|
|
190
|
+
pathGlobs: string[];
|
|
191
|
+
createdAt: string;
|
|
192
|
+
program: string;
|
|
193
|
+
model: string | null;
|
|
194
|
+
repo: string;
|
|
195
|
+
endedAt: string | null;
|
|
196
|
+
}, {
|
|
197
|
+
agentName: string;
|
|
198
|
+
status: "active" | "done" | "dropped";
|
|
199
|
+
intent: string;
|
|
200
|
+
pathGlobs: string[];
|
|
201
|
+
createdAt: string;
|
|
202
|
+
program: string;
|
|
203
|
+
model: string | null;
|
|
204
|
+
repo: string;
|
|
205
|
+
endedAt: string | null;
|
|
206
|
+
}>, "many">;
|
|
207
|
+
announcements: z.ZodArray<z.ZodObject<{
|
|
208
|
+
fromAgentName: z.ZodString;
|
|
209
|
+
fromHuman: z.ZodString;
|
|
210
|
+
body: z.ZodString;
|
|
211
|
+
targetAgentName: z.ZodNullable<z.ZodString>;
|
|
212
|
+
repo: z.ZodString;
|
|
213
|
+
fromAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
214
|
+
toAdmin: z.ZodDefault<z.ZodBoolean>;
|
|
215
|
+
createdAt: z.ZodString;
|
|
216
|
+
}, "strip", z.ZodTypeAny, {
|
|
217
|
+
fromAgentName: string;
|
|
218
|
+
fromHuman: string;
|
|
219
|
+
body: string;
|
|
220
|
+
targetAgentName: string | null;
|
|
221
|
+
createdAt: string;
|
|
222
|
+
repo: string;
|
|
223
|
+
fromAdmin: boolean;
|
|
224
|
+
toAdmin: boolean;
|
|
225
|
+
}, {
|
|
226
|
+
fromAgentName: string;
|
|
227
|
+
fromHuman: string;
|
|
228
|
+
body: string;
|
|
229
|
+
targetAgentName: string | null;
|
|
230
|
+
createdAt: string;
|
|
231
|
+
repo: string;
|
|
232
|
+
fromAdmin?: boolean | undefined;
|
|
233
|
+
toAdmin?: boolean | undefined;
|
|
234
|
+
}>, "many">;
|
|
235
|
+
serverTime: z.ZodString;
|
|
236
|
+
}, "strip", z.ZodTypeAny, {
|
|
237
|
+
announcements: {
|
|
238
|
+
fromAgentName: string;
|
|
239
|
+
fromHuman: string;
|
|
240
|
+
body: string;
|
|
241
|
+
targetAgentName: string | null;
|
|
242
|
+
createdAt: string;
|
|
243
|
+
repo: string;
|
|
244
|
+
fromAdmin: boolean;
|
|
245
|
+
toAdmin: boolean;
|
|
246
|
+
}[];
|
|
247
|
+
agents: {
|
|
248
|
+
human: string;
|
|
249
|
+
branch: string | null;
|
|
250
|
+
name: string;
|
|
251
|
+
program: string;
|
|
252
|
+
model: string | null;
|
|
253
|
+
repo: string | null;
|
|
254
|
+
lastHeartbeatAt: string | null;
|
|
255
|
+
presence: "live" | "offline";
|
|
256
|
+
}[];
|
|
257
|
+
tasks: {
|
|
258
|
+
agentName: string;
|
|
259
|
+
status: "active" | "done" | "dropped";
|
|
260
|
+
intent: string;
|
|
261
|
+
pathGlobs: string[];
|
|
262
|
+
createdAt: string;
|
|
263
|
+
program: string;
|
|
264
|
+
model: string | null;
|
|
265
|
+
repo: string;
|
|
266
|
+
endedAt: string | null;
|
|
267
|
+
}[];
|
|
268
|
+
serverTime: string;
|
|
269
|
+
}, {
|
|
270
|
+
announcements: {
|
|
271
|
+
fromAgentName: string;
|
|
272
|
+
fromHuman: string;
|
|
273
|
+
body: string;
|
|
274
|
+
targetAgentName: string | null;
|
|
275
|
+
createdAt: string;
|
|
276
|
+
repo: string;
|
|
277
|
+
fromAdmin?: boolean | undefined;
|
|
278
|
+
toAdmin?: boolean | undefined;
|
|
279
|
+
}[];
|
|
280
|
+
agents: {
|
|
281
|
+
human: string;
|
|
282
|
+
branch: string | null;
|
|
283
|
+
name: string;
|
|
284
|
+
program: string;
|
|
285
|
+
model: string | null;
|
|
286
|
+
repo: string | null;
|
|
287
|
+
lastHeartbeatAt: string | null;
|
|
288
|
+
presence: "live" | "offline";
|
|
289
|
+
}[];
|
|
290
|
+
tasks: {
|
|
291
|
+
agentName: string;
|
|
292
|
+
status: "active" | "done" | "dropped";
|
|
293
|
+
intent: string;
|
|
294
|
+
pathGlobs: string[];
|
|
295
|
+
createdAt: string;
|
|
296
|
+
program: string;
|
|
297
|
+
model: string | null;
|
|
298
|
+
repo: string;
|
|
299
|
+
endedAt: string | null;
|
|
300
|
+
}[];
|
|
301
|
+
serverTime: string;
|
|
302
|
+
}>;
|
|
303
|
+
|
|
304
|
+
export declare type WorkspaceLandscapeResponseT = z.infer<typeof WorkspaceLandscapeResponse>;
|
|
305
|
+
|
|
306
|
+
export { }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The self-host SPA root and the ONE place a team token is handled — the named
|
|
5
|
+
* auth exception to the auth-agnostic core. Everything below the provider (the
|
|
6
|
+
* client, the polling hook, the {@link Dashboard}) stays token-blind; this
|
|
7
|
+
* component owns the token's lifecycle:
|
|
8
|
+
*
|
|
9
|
+
* - It seeds token state from `localStorage["shepherd.token"]` (back-compat with
|
|
10
|
+
* the legacy board), so a returning viewer skips the gate.
|
|
11
|
+
* - With NO token it renders the {@link Gate}; a successful submit persists the
|
|
12
|
+
* token and re-renders into the dashboard.
|
|
13
|
+
* - With a token it builds a same-origin client (`baseUrl: ""`) that injects the
|
|
14
|
+
* `Authorization: Bearer <token>` header and, on a 401, clears the stored
|
|
15
|
+
* token and resets state — which re-renders straight back to the gate. This is
|
|
16
|
+
* the React analogue of app.js's `clearToken()` + re-prompt 401 flow.
|
|
17
|
+
*
|
|
18
|
+
* The client is memoised on the token so it is rebuilt only when the token
|
|
19
|
+
* actually changes, not on every freshness re-render of the dashboard subtree.
|
|
20
|
+
*
|
|
21
|
+
* @returns Either the token gate or the authenticated dashboard.
|
|
22
|
+
*/
|
|
23
|
+
export declare function SelfHostApp(): ReactElement;
|
|
24
|
+
|
|
25
|
+
export { }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as e, jsxs as l } from "react/jsx-runtime";
|
|
2
|
+
import { useState as s, useMemo as h } from "react";
|
|
3
|
+
import { c as d, a as m, D as u } from "./Dashboard-CB6SL-J2.js";
|
|
4
|
+
const i = "shepherd.token";
|
|
5
|
+
function p() {
|
|
6
|
+
try {
|
|
7
|
+
return localStorage.getItem(i) ?? "";
|
|
8
|
+
} catch {
|
|
9
|
+
return "";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function f({ onSubmit: t }) {
|
|
13
|
+
const [r, o] = s("");
|
|
14
|
+
return /* @__PURE__ */ l("section", { id: "gate", className: "gate", children: [
|
|
15
|
+
/* @__PURE__ */ e("h1", { children: "Shepherd" }),
|
|
16
|
+
/* @__PURE__ */ e("p", { children: "Enter the team token to view the workspace." }),
|
|
17
|
+
/* @__PURE__ */ l("form", { id: "gate-form", onSubmit: (n) => {
|
|
18
|
+
n.preventDefault();
|
|
19
|
+
const c = r.trim();
|
|
20
|
+
c !== "" && t(c);
|
|
21
|
+
}, children: [
|
|
22
|
+
/* @__PURE__ */ e(
|
|
23
|
+
"input",
|
|
24
|
+
{
|
|
25
|
+
id: "gate-input",
|
|
26
|
+
type: "password",
|
|
27
|
+
placeholder: "Team token",
|
|
28
|
+
"aria-label": "Team token",
|
|
29
|
+
autoComplete: "off",
|
|
30
|
+
value: r,
|
|
31
|
+
onChange: (n) => o(n.target.value)
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
/* @__PURE__ */ e("button", { type: "submit", children: "View" })
|
|
35
|
+
] })
|
|
36
|
+
] });
|
|
37
|
+
}
|
|
38
|
+
function b() {
|
|
39
|
+
const [t, r] = s(p), o = h(
|
|
40
|
+
() => d({
|
|
41
|
+
baseUrl: "",
|
|
42
|
+
getAuthHeaders: () => ({ Authorization: `Bearer ${t}` }),
|
|
43
|
+
onUnauthorized: () => {
|
|
44
|
+
try {
|
|
45
|
+
localStorage.removeItem(i);
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
r("");
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
[t]
|
|
52
|
+
);
|
|
53
|
+
return t === "" ? /* @__PURE__ */ e(
|
|
54
|
+
f,
|
|
55
|
+
{
|
|
56
|
+
onSubmit: (a) => {
|
|
57
|
+
try {
|
|
58
|
+
localStorage.setItem(i, a);
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
61
|
+
r(a);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
) : /* @__PURE__ */ e("div", { className: "wrap", children: /* @__PURE__ */ e(m, { client: o, children: /* @__PURE__ */ e(u, {}) }) });
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
b as SelfHostApp
|
|
68
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
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; }
|