whose-turn 1.0.0-alpha.2 → 1.0.0-alpha.4
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/CHANGELOG.md +19 -1
- package/README.md +14 -12
- package/config.example.json +0 -16
- package/connectors/index.mjs +1 -2
- package/dist/assets/bem-COKTjsky.js +1 -0
- package/dist/assets/index-B23uRijc.css +1 -0
- package/dist/assets/index-D-P2XKhJ.js +3 -0
- package/dist/assets/{report-Xynr6yGL.js → report-BtGnm9ax.js} +1 -1
- package/dist/index.html +3 -3
- package/dist/report.html +2 -2
- package/lib/api.mjs +11 -4
- package/lib/config.mjs +1 -1
- package/lib/errors.mjs +2 -0
- package/lib/facts.mjs +1 -1
- package/lib/feedback.mjs +5 -2
- package/lib/update.mjs +135 -0
- package/package.json +1 -1
- package/server.mjs +19 -3
- package/connectors/demo.mjs +0 -146
- package/dist/assets/bem-puYdOas_.js +0 -1
- package/dist/assets/index-DR8StBp8.js +0 -3
- package/dist/assets/index-d0wlPvUz.css +0 -1
package/lib/errors.mjs
CHANGED
|
@@ -14,6 +14,8 @@ const REASONS = {
|
|
|
14
14
|
UPSTREAM_FAILED: { code: 502, status: 'UNAVAILABLE', message: 'The tracker did not answer; try again in a minute.' },
|
|
15
15
|
RATE_LIMITED: { code: 429, status: 'RESOURCE_EXHAUSTED', message: 'The source is rate-limited; try again later.' },
|
|
16
16
|
OPEN_TOO_SOON: { code: 429, status: 'RESOURCE_EXHAUSTED', message: 'The window was just opened; wait a few seconds before opening it again.' },
|
|
17
|
+
UPDATE_IN_PROGRESS: { code: 409, status: 'ABORTED', message: 'An update is already installing; the server restarts when it is done.' },
|
|
18
|
+
UPDATE_UNAVAILABLE: { code: 412, status: 'FAILED_PRECONDITION', message: 'There is no update to apply: the server runs from a checkout or already runs the latest version.' },
|
|
17
19
|
FORBIDDEN_ORIGIN: { code: 403, status: 'PERMISSION_DENIED', message: 'Only the local window may call this API.' },
|
|
18
20
|
INTERNAL: { code: 500, status: 'INTERNAL', message: 'The local server failed on this request; the details are in its log.' },
|
|
19
21
|
};
|
package/lib/facts.mjs
CHANGED
|
@@ -9,7 +9,7 @@ const CHAIN_GROUPS = ['backlog', 'passed', 'code_review', 'qa', 'on_hold'];
|
|
|
9
9
|
/** Every fact a table row may test or quote: its type, the closed values when there are any, and where it comes from. */
|
|
10
10
|
export const FACTS = {
|
|
11
11
|
connector: { type: 'string', from: 'the connector entry id the ticket came from' },
|
|
12
|
-
'ticket.key': { type: 'string', from: 'tracker connectors (jira, linear
|
|
12
|
+
'ticket.key': { type: 'string', from: 'tracker connectors (jira, linear)' },
|
|
13
13
|
'ticket.status': { type: 'string', values: 'status groups', from: 'the ticket status matched against rules.statuses; else todo, in_progress or done from the status category' },
|
|
14
14
|
'ticket.status_name': { type: 'string', from: 'the status as the tracker names it' },
|
|
15
15
|
'ticket.assignee': { type: 'string', from: '"me" when the ticket is on you, else the assignee name' },
|
package/lib/feedback.mjs
CHANGED
|
@@ -11,6 +11,7 @@ const KINDS = { bug: 'Bug', improvement: 'Improvement', idea: 'Feature' };
|
|
|
11
11
|
|
|
12
12
|
const LOOKUP = `
|
|
13
13
|
query Lookup($team: String!, $project: String!) {
|
|
14
|
+
viewer { id }
|
|
14
15
|
teams(filter: { key: { eq: $team } }) { nodes { id key labels { nodes { id name } } } }
|
|
15
16
|
projects(filter: { name: { eqIgnoreCase: $project } }) { nodes { id name } }
|
|
16
17
|
}`;
|
|
@@ -129,10 +130,12 @@ export const createFeedback = async ({ settings, secrets, http, cache, reports }
|
|
|
129
130
|
if (!team || !project) {
|
|
130
131
|
throw new ApiError('NOT_CONFIGURED', { detail: `Linear team ${settings.team} or project ${settings.project} not found` });
|
|
131
132
|
}
|
|
132
|
-
cache.lookup = { teamId: team.id, projectId: project.id, labels: Object.fromEntries(team.labels.nodes.map((label) => [label.name.toLowerCase(), label.id])) };
|
|
133
|
+
cache.lookup = { teamId: team.id, projectId: project.id, assigneeId: data.viewer?.id || '', labels: Object.fromEntries(team.labels.nodes.map((label) => [label.name.toLowerCase(), label.id])) };
|
|
133
134
|
}
|
|
134
135
|
const labelId = cache.lookup.labels[(KINDS[kind] || kind).toLowerCase()];
|
|
135
|
-
|
|
136
|
+
// The key's owner gets the issue, so it lands on their own dashboard; `feedback.assign: false` leaves it to triage.
|
|
137
|
+
const assigneeId = settings.assign === false ? '' : cache.lookup.assigneeId;
|
|
138
|
+
const input = { teamId: cache.lookup.teamId, projectId: cache.lookup.projectId, title: cleanTitle, description: describe({ kind, body, context, from }), ...(labelId ? { labelIds: [labelId] } : {}), ...(assigneeId ? { assigneeId } : {}) };
|
|
136
139
|
const data = await call(http, key, CREATE, { input });
|
|
137
140
|
if (!data.issueCreate?.success) {
|
|
138
141
|
throw new ApiError('UPSTREAM_FAILED');
|
package/lib/update.mjs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Self-update of an installed package: a dist-tags check every ten minutes, one desktop notification per new version, and the one-click install and restart behind POST /api/update. ADR 0007, RMA-231. */
|
|
2
|
+
import { execFile, spawn as spawnChild } from 'node:child_process';
|
|
3
|
+
import { openSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { ApiError } from './errors.mjs';
|
|
6
|
+
import { readJson, writeJson } from './store.mjs';
|
|
7
|
+
|
|
8
|
+
export const PACKAGE = 'whose-turn';
|
|
9
|
+
export const AGENT_LABEL = 'local.whose-turn';
|
|
10
|
+
export const UPDATE_CHECK_MS = 10 * 60 * 1000;
|
|
11
|
+
const INSTALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
12
|
+
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
13
|
+
|
|
14
|
+
/** Where the BladeForge plugin installs the package: `<dataDir>/app/node_modules/whose-turn`. */
|
|
15
|
+
export const installedRoot = (dataDir) => join(dataDir, 'app', 'node_modules', PACKAGE);
|
|
16
|
+
|
|
17
|
+
/** True when the server runs from the plugin's install, so an update has somewhere to land; a checkout is never offered one. */
|
|
18
|
+
export const runsInstalled = (appDir, dataDir, real = realpathSync) => {
|
|
19
|
+
try {
|
|
20
|
+
return real(appDir) === real(installedRoot(dataDir));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** The dist-tag every install follows: `latest`, which the release workflow moves on every publish, prereleases included (RMA-230). */
|
|
27
|
+
export const CHANNEL = 'latest';
|
|
28
|
+
|
|
29
|
+
/** The launch agent this process runs under, as the exit code that brings it back, or null when it runs on its own: launchd names its job in XPC_SERVICE_NAME and restarts on any exit (KeepAlive); the systemd unit shows in the cgroup and restarts only on a non-zero exit (Restart=on-failure). */
|
|
30
|
+
export const probeAgent = ({ platform = process.platform, env = process.env, cgroup = () => readFileSync('/proc/self/cgroup', 'utf8') } = {}) => {
|
|
31
|
+
if (platform === 'darwin') {
|
|
32
|
+
return env.XPC_SERVICE_NAME === AGENT_LABEL ? { exitCode: 0 } : null;
|
|
33
|
+
}
|
|
34
|
+
if (platform === 'linux') {
|
|
35
|
+
try {
|
|
36
|
+
return cgroup().includes(`/${PACKAGE}.service`) ? { exitCode: 75 } : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Runs npm and resolves with '' when it succeeded, else with the last lines it printed; on Windows npm is a .cmd, which needs a shell, so every argument is quoted. */
|
|
45
|
+
export const runNpm = (npm, args, platform = process.platform) => new Promise((resolve) => {
|
|
46
|
+
const shell = platform === 'win32';
|
|
47
|
+
execFile(npm, shell ? args.map((arg) => `"${arg}"`) : args, { timeout: INSTALL_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, shell }, (error, stdout, stderr) => {
|
|
48
|
+
if (!error) {
|
|
49
|
+
return resolve('');
|
|
50
|
+
}
|
|
51
|
+
const lines = String(stderr || stdout || error.message).split('\n').map((line) => line.trim()).filter(Boolean);
|
|
52
|
+
resolve(lines.slice(-3).join(' ').slice(0, 400) || 'npm failed');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** The restart after an install: under a launch agent exit and let it bring the new copy back; without one close the port, start the new copy detached and exit. */
|
|
57
|
+
export const restartServer = ({ server, root, dataDir, agent = () => probeAgent(), spawn = spawnChild, exit = process.exit }) => {
|
|
58
|
+
const found = agent();
|
|
59
|
+
if (found) {
|
|
60
|
+
return exit(found.exitCode);
|
|
61
|
+
}
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
server.close(resolve);
|
|
64
|
+
server.closeAllConnections();
|
|
65
|
+
}).then(() => {
|
|
66
|
+
const log = openSync(join(dataDir, 'server.log'), 'a');
|
|
67
|
+
spawn(process.execPath, [join(root, 'server.mjs')], { cwd: root, env: process.env, detached: true, stdio: ['ignore', log, log] }).unref();
|
|
68
|
+
exit(0);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** The updater the API reads and writes; `installed` false (a checkout) keeps it idle and offers nothing. */
|
|
73
|
+
export const createUpdater = ({ http, dataDir, version, installed, notify, registry = 'https://registry.npmjs.org', npm = 'npm', run = runNpm, restart }) => {
|
|
74
|
+
const listeners = new Set();
|
|
75
|
+
const memoFile = join(dataDir, 'update.json');
|
|
76
|
+
let latest = '';
|
|
77
|
+
let state = 'idle';
|
|
78
|
+
let reason = '';
|
|
79
|
+
|
|
80
|
+
const status = () => ({ current: version, latest, available: installed && Boolean(latest) && latest !== version, state, ...(state === 'failed' ? { reason } : {}) });
|
|
81
|
+
const announce = () => listeners.forEach((listener) => listener({ update: status() }));
|
|
82
|
+
const move = (next, why = '') => {
|
|
83
|
+
state = next;
|
|
84
|
+
reason = why;
|
|
85
|
+
announce();
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const check = async () => {
|
|
89
|
+
if (!installed) {
|
|
90
|
+
return status();
|
|
91
|
+
}
|
|
92
|
+
const found = await http.fetchJson(`${registry}/-/package/${PACKAGE}/dist-tags`).then((tags) => tags && tags[CHANNEL], () => '');
|
|
93
|
+
if (typeof found === 'string' && VERSION.test(found) && found !== latest) {
|
|
94
|
+
latest = found;
|
|
95
|
+
announce();
|
|
96
|
+
if (found !== version && readJson(memoFile, {}).notified !== found) {
|
|
97
|
+
writeJson(memoFile, { notified: found });
|
|
98
|
+
notify(`Whose Turn ${found} is out`, 'Update from the window or your next session.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return status();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// `--ignore-scripts`: the package declares no install scripts, so a release that gained one runs nothing on the user's machine.
|
|
105
|
+
const install = async () => {
|
|
106
|
+
const failure = await run(npm, ['install', '--prefix', join(dataDir, 'app'), '--no-audit', '--no-fund', '--no-save', '--ignore-scripts', `${PACKAGE}@${latest}`]);
|
|
107
|
+
if (failure) {
|
|
108
|
+
return move('failed', failure);
|
|
109
|
+
}
|
|
110
|
+
move('restarting');
|
|
111
|
+
await restart();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const apply = () => {
|
|
115
|
+
if (state === 'installing' || state === 'restarting') {
|
|
116
|
+
throw new ApiError('UPDATE_IN_PROGRESS', { state });
|
|
117
|
+
}
|
|
118
|
+
if (!status().available) {
|
|
119
|
+
throw new ApiError('UPDATE_UNAVAILABLE', { current: version, latest });
|
|
120
|
+
}
|
|
121
|
+
move('installing');
|
|
122
|
+
install().catch((error) => move('failed', String(error?.message || error)));
|
|
123
|
+
return status();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const start = () => {
|
|
127
|
+
check();
|
|
128
|
+
setInterval(check, UPDATE_CHECK_MS).unref();
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return { status, check, apply, start, subscribe: (listener) => { listeners.add(listener); return () => listeners.delete(listener); } };
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** An updater for a server with nothing to update, the API's default. */
|
|
135
|
+
export const noUpdates = () => createUpdater({ http: null, dataDir: '', version: '', installed: false, notify: () => {}, restart: async () => {} });
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Whose Turn server: loads the workspace config, polls every workspace on one loop with rate-limit backoff, checks data health on a timer, serves the window and the local API. */
|
|
3
|
-
import {
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { loadConfig, saveWorkspaces, normaliseWorkspaces } from './lib/config.mjs';
|
|
6
7
|
import { createHttpClient } from './lib/http-client.mjs';
|
|
@@ -11,6 +12,8 @@ import { createRegistry } from './lib/registry.mjs';
|
|
|
11
12
|
import { openInbox } from './lib/inbox.mjs';
|
|
12
13
|
import { openReports } from './lib/feedback.mjs';
|
|
13
14
|
import { startApi } from './lib/api.mjs';
|
|
15
|
+
import { notify } from './lib/notify.mjs';
|
|
16
|
+
import { createUpdater, restartServer, runsInstalled } from './lib/update.mjs';
|
|
14
17
|
|
|
15
18
|
const appDir = dirname(fileURLToPath(import.meta.url));
|
|
16
19
|
const http = createHttpClient();
|
|
@@ -18,6 +21,18 @@ const first = loadConfig();
|
|
|
18
21
|
const secrets = createSecrets(first.dataDir);
|
|
19
22
|
const inbox = openInbox(first.dataDir);
|
|
20
23
|
const reports = openReports(first.dataDir);
|
|
24
|
+
const { version } = JSON.parse(readFileSync(join(appDir, 'package.json'), 'utf8'));
|
|
25
|
+
let server;
|
|
26
|
+
const updater = createUpdater({
|
|
27
|
+
http,
|
|
28
|
+
dataDir: first.dataDir,
|
|
29
|
+
version,
|
|
30
|
+
installed: runsInstalled(appDir, first.dataDir),
|
|
31
|
+
notify,
|
|
32
|
+
registry: process.env.WHOSE_TURN_REGISTRY,
|
|
33
|
+
npm: process.env.WHOSE_TURN_NPM,
|
|
34
|
+
restart: () => restartServer({ server, root: appDir, dataDir: first.dataDir }),
|
|
35
|
+
});
|
|
21
36
|
let pollSeconds = first.pollSeconds;
|
|
22
37
|
/** @type {{ stale_minutes: number, failing_polls: number }} */
|
|
23
38
|
let healthLimits = /** @type {any} */ (first.health);
|
|
@@ -56,16 +71,17 @@ const saveConfig = async (rawWorkspaces) => {
|
|
|
56
71
|
};
|
|
57
72
|
|
|
58
73
|
try {
|
|
59
|
-
await startApi({ port: first.port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir: first.dataDir });
|
|
74
|
+
server = await startApi({ port: first.port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir: first.dataDir, updater });
|
|
60
75
|
} catch (error) {
|
|
61
76
|
console.error(error.message);
|
|
62
77
|
process.exit(1);
|
|
63
78
|
}
|
|
64
|
-
console.log(`whose-turn on http://127.0.0.1:${first.port} · data ${first.dataDir} · workspaces ${registry.workspaces.map((workspace) => workspace.id).join(', ') || 'none'}`);
|
|
79
|
+
console.log(`whose-turn ${version} on http://127.0.0.1:${first.port} · data ${first.dataDir} · workspaces ${registry.workspaces.map((workspace) => workspace.id).join(', ') || 'none'}`);
|
|
65
80
|
for (const problem of registry.problems) {
|
|
66
81
|
console.error(problem);
|
|
67
82
|
}
|
|
68
83
|
pollLoop();
|
|
84
|
+
updater.start();
|
|
69
85
|
|
|
70
86
|
const HEALTH_TICK = 30000;
|
|
71
87
|
let lastTick = Date.now();
|
package/connectors/demo.mjs
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/** Demo connector: three invented projects with every state the rules know, timestamps relative to now, and a little life on later polls. No network. */
|
|
2
|
-
import { toIsoUtc } from '../lib/text.mjs';
|
|
3
|
-
|
|
4
|
-
export const id = 'demo';
|
|
5
|
-
export const label = 'Demo';
|
|
6
|
-
export const links = () => [{ label: 'Demo tracker', url: 'https://demo.example/' }];
|
|
7
|
-
export const fields = [
|
|
8
|
-
{ key: 'scenario', label: 'Scenario', hint: 'northwind, atlas or garden', required: true },
|
|
9
|
-
{ key: 'part', label: 'Part', hint: 'tickets (the tracker side), code (PRs and asks), or empty for both' },
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
const HOUR = 3600000;
|
|
13
|
-
let base = Date.now();
|
|
14
|
-
const ago = (hours) => toIsoUtc(base - hours * HOUR);
|
|
15
|
-
const SITE = 'https://demo.example/browse/';
|
|
16
|
-
const REPO = 'https://github.com/example/';
|
|
17
|
-
|
|
18
|
-
const ticket = (key, summary, status, category, assignee, hoursAgo, sinceHours, changedBy) => ({
|
|
19
|
-
key, summary, status, status_category: category, assignee, updated: ago(hoursAgo), url: `${SITE}${key}`,
|
|
20
|
-
status_change: sinceHours === undefined ? null : { since: ago(sinceHours), by: changedBy || assignee },
|
|
21
|
-
});
|
|
22
|
-
const comment = (id, key, hoursAgo, author, body) => ({ id, key, at: ago(hoursAgo), author, body, url: `${SITE}${key}?focusedCommentId=${id}` });
|
|
23
|
-
const pr = (number, key, title, repo, overrides = {}) => ({
|
|
24
|
-
pr: number, key, title, base: 'main', state: 'OPEN', review: '', checks: 'passing', merged_at: '', updated_at: ago(5), url: `${REPO}${repo}/pull/${number}`,
|
|
25
|
-
last_comment_by: '', last_comment_at: '', last_comment_url: '', last_comment: '', unanswered: false, ...overrides,
|
|
26
|
-
});
|
|
27
|
-
const activity = (hoursAgo, number, key, kind, who, repo, text, body) => ({ at: ago(hoursAgo), pr: `${repo}#${number}`, key, kind, who, url: `${REPO}${repo}/pull/${number}`, text, body: body || text });
|
|
28
|
-
const failedRun = (id, repo, workflow, hoursAgo, title) => ({
|
|
29
|
-
id, ref: `${repo}:${workflow}@main`, repo: `example/${repo}`, workflow, branch: 'main', failed: true, mine: true, actor: 'alex', title, url: `${REPO}${repo}/actions/runs/${id}`, at: ago(hoursAgo), streak: id,
|
|
30
|
-
});
|
|
31
|
-
const runActivity = (run) => ({ at: run.at, pr: run.ref, key: '', title: run.title, kind: 'check failed', who: run.workflow, url: run.url, text: `${run.workflow} on ${run.branch}`, body: '' });
|
|
32
|
-
const ask = (noteId, repo, number, title, key, kind, who, hoursAgo, text) => ({ id: `gha:${noteId}`, repo: `example/${repo}`, number, ref: `${repo}#${number}`, title, key, kind, who, at: ago(hoursAgo), url: `${REPO}${repo}/pull/${number}`, text, body: text });
|
|
33
|
-
|
|
34
|
-
const northwind = (polls) => {
|
|
35
|
-
const me = 'Alex Doe';
|
|
36
|
-
const tickets = [
|
|
37
|
-
ticket('NW-142', 'Rounds: due dates differ between locales', 'Passed', 'indeterminate', me, 1, 1, 'Sam Tester'),
|
|
38
|
-
ticket('NW-151', 'Export to CSV loses the header row on retry', 'In Progress', 'indeterminate', me, 3, 30),
|
|
39
|
-
ticket('NW-137', 'Login page: remember the last used organisation', polls >= 3 ? 'Ready for QA' : 'Code Review', 'indeterminate', polls >= 3 ? 'Sam Tester' : me, polls >= 3 ? 0.1 : 20, polls >= 3 ? 0.1 : 26, polls >= 3 ? me : undefined),
|
|
40
|
-
ticket('NW-171', 'Spike: search index on Postgres', 'In Progress', 'indeterminate', me, 9, 9),
|
|
41
|
-
ticket('NW-129', 'Audit log keeps the deleted user name', 'Ready for QA', 'indeterminate', 'Sam Tester', 30, 29, me),
|
|
42
|
-
ticket('NW-118', 'Nightly report email arrives twice', 'In QA', 'indeterminate', 'Sam Tester', 80, 79, me),
|
|
43
|
-
ticket('NW-104', 'Invoice PDF: wrong currency symbol for CHF', 'On Hold', 'indeterminate', me, 120, 118, 'Pat Owner'),
|
|
44
|
-
ticket('NW-160', 'Import wizard for legacy spreadsheets', 'Backlog', 'new', 'Unassigned', 200),
|
|
45
|
-
ticket('NW-099', 'Dashboard tiles: tooltip on hover', 'Done', 'done', me, 50),
|
|
46
|
-
ticket('NW-088', 'Rate limit on the search endpoint', 'Done', 'done', me, 140),
|
|
47
|
-
];
|
|
48
|
-
const comments = [
|
|
49
|
-
comment('c1', 'NW-142', 1, 'Sam Tester', 'Have tested this on staging and it is now QA passed. Please also test it in the customer org once merged.'),
|
|
50
|
-
comment('c2', 'NW-151', 4, 'Pat Owner', `@${me} can you confirm the retry keeps the original delimiter? The client asked twice.`),
|
|
51
|
-
comment('c3', 'NW-151', 6, me, '@Pat Owner yes, the delimiter comes from the first attempt; adding a test for it.'),
|
|
52
|
-
comment('c4', 'NW-151', 2, 'Pat Owner', `@${me} and one more: does it cover TSV as well?`),
|
|
53
|
-
comment('c5', 'NW-104', 118, 'Pat Owner', 'On hold: waiting for the vendor to confirm the CHF rounding rule, they promised an answer next week.'),
|
|
54
|
-
comment('c6', 'NW-104', 140, me, 'Reproduced with the sample invoice, fix is a one-liner once we know the rule.'),
|
|
55
|
-
comment('c7', 'NW-118', 70, 'Sam Tester', `@${me} fyi, moved to In QA, will run the nightly job twice tonight.`),
|
|
56
|
-
comment('c8', 'NW-129', 28, 'Automation for Jira', 'Transitioned by rule "Ready for QA when PR approved".'),
|
|
57
|
-
...(polls >= 3 ? [comment('c9', 'NW-137', 0.1, 'Sam Tester', `@${me} picked it up, first pass looks good, one question: should the choice survive logout?`)] : []),
|
|
58
|
-
];
|
|
59
|
-
const prs = [
|
|
60
|
-
pr(3978, 'NW-142', 'NW-142 fix week start per locale', 'northwind-web', { base: 'release/2.5', review: 'APPROVED', updated_at: ago(2), last_comment_by: 'Chris Reviewer', last_comment_at: ago(3), last_comment: 'Nice find on the locale changing the week start.', last_comment_url: `${REPO}northwind-web/pull/3978#issuecomment-1` }),
|
|
61
|
-
pr(3991, 'NW-151', 'NW-151 keep the header row on CSV retry', 'northwind-web', { checks: 'failing', updated_at: ago(1) }),
|
|
62
|
-
pr(3985, 'NW-137', 'NW-137 remember the last organisation on login', 'northwind-web', { review: polls >= 3 ? 'APPROVED' : 'CHANGES_REQUESTED', updated_at: ago(polls >= 3 ? 0.2 : 19), last_comment_by: 'Chris Reviewer', last_comment_at: ago(19), last_comment: 'Please do not store the org id in localStorage without a namespace.', last_comment_url: `${REPO}northwind-web/pull/3985#discussion_r1`, unanswered: polls < 3 }),
|
|
63
|
-
pr(3960, 'NW-129', 'NW-129 keep the user name in the audit log', 'northwind-web', { state: 'MERGED', merged_at: ago(31), updated_at: ago(31) }),
|
|
64
|
-
pr(3940, 'NW-099', 'NW-099 dashboard tile tooltips', 'northwind-web', { state: 'MERGED', merged_at: ago(52), updated_at: ago(52) }),
|
|
65
|
-
];
|
|
66
|
-
const acts = [
|
|
67
|
-
activity(1, 3991, 'NW-151', 'check failed', 'unit-tests', 'northwind-web', ''),
|
|
68
|
-
activity(3, 3978, 'NW-142', 'comment', 'Chris Reviewer', 'northwind-web', 'Nice find on the locale changing the week start.'),
|
|
69
|
-
activity(19, 3985, 'NW-137', 'changes requested', 'Chris Reviewer', 'northwind-web', 'Please do not store the org id in localStorage without a namespace.', 'Please do not store the org id in `localStorage` without a namespace.\n\nTwo tabs with two organisations would overwrite each other. A `nw:` prefix plus the user id is enough.'),
|
|
70
|
-
activity(31, 3960, 'NW-129', 'merged', '', 'northwind-web', ''),
|
|
71
|
-
activity(52, 3940, 'NW-099', 'merged', '', 'northwind-web', ''),
|
|
72
|
-
...(polls >= 3 ? [activity(0.2, 3985, 'NW-137', 'approved', 'Chris Reviewer', 'northwind-web', 'Namespace added, looks good now.')] : []),
|
|
73
|
-
];
|
|
74
|
-
const asks = [
|
|
75
|
-
ask('n1', 'northwind-api', 512, 'Paginate the audit endpoint', 'NW-133', 'review requested', 'Chris Reviewer', 7, 'Paginate the audit endpoint'),
|
|
76
|
-
ask('n2', 'northwind-infra', 77, 'Rotate the staging certificates', '', 'mentioned you', 'Pat Owner', 26, `@alex could you check whether the cron on staging still runs after the rotation?`),
|
|
77
|
-
];
|
|
78
|
-
const runs = [failedRun(9001, 'northwind-web', 'scout-publish', 0.5, 'Merge pull request #3940 from example/nw-099-tooltips')];
|
|
79
|
-
return { me, tickets, comments, prs, activity: [...acts, ...runs.map(runActivity)], asks, runs };
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const atlas = (polls) => {
|
|
83
|
-
const me = 'Alex Doe';
|
|
84
|
-
const tickets = [
|
|
85
|
-
ticket('ATL-77', 'Offline mode: queue edits made without network', 'In Review', 'indeterminate', me, 10, 12),
|
|
86
|
-
ticket('ATL-81', 'Crash on rotating the map while loading', 'Testing', 'indeterminate', 'Jo Qa', 40, 36, me),
|
|
87
|
-
ticket('ATL-64', 'Dark theme for the trip summary', 'Blocked', 'indeterminate', me, 90, 88, 'Dana Lead'),
|
|
88
|
-
ticket('ATL-85', 'Accessibility labels on the bottom bar', 'Todo', 'indeterminate', me, 5),
|
|
89
|
-
ticket('ATL-59', 'Share a trip as a link', 'Done', 'done', me, 100),
|
|
90
|
-
ticket('ATL-90', 'Replace the deprecated map SDK', 'Backlog', 'new', 'Dana Lead', 300),
|
|
91
|
-
];
|
|
92
|
-
const comments = [
|
|
93
|
-
comment('a1', 'ATL-64', 88, 'Dana Lead', 'Blocked until design delivers the dark palette, see the Figma thread.'),
|
|
94
|
-
comment('a2', 'ATL-77', 9, 'Dana Lead', `@${me} please add a screenshot of the queue indicator to the PR description`),
|
|
95
|
-
comment('a3', 'ATL-81', 35, 'Jo Qa', 'Testing on the three devices now.'),
|
|
96
|
-
...(polls >= 4 ? [comment('a4', 'ATL-81', 0.1, 'Jo Qa', `@${me} reproduced once more on the small device, log attached, can you have a look?`)] : []),
|
|
97
|
-
];
|
|
98
|
-
const prs = [
|
|
99
|
-
pr(214, 'ATL-77', 'ATL-77 offline edit queue', 'atlas-mobile', { base: 'develop', updated_at: ago(9), last_comment_by: 'Dana Lead', last_comment_at: ago(9), last_comment: 'please add a screenshot of the queue indicator', last_comment_url: `${REPO}atlas-mobile/pull/214#issuecomment-3`, unanswered: true }),
|
|
100
|
-
pr(209, 'ATL-81', 'ATL-81 guard the map rotation during load', 'atlas-mobile', { base: 'develop', state: 'MERGED', merged_at: ago(41), updated_at: ago(41) }),
|
|
101
|
-
pr(198, 'ATL-59', 'ATL-59 share trip link', 'atlas-mobile', { base: 'develop', state: 'MERGED', merged_at: ago(101), updated_at: ago(101) }),
|
|
102
|
-
];
|
|
103
|
-
const acts = [
|
|
104
|
-
activity(9, 214, 'ATL-77', 'comment', 'Dana Lead', 'atlas-mobile', 'please add a screenshot of the queue indicator'),
|
|
105
|
-
activity(41, 209, 'ATL-81', 'merged', '', 'atlas-mobile', ''),
|
|
106
|
-
activity(101, 198, 'ATL-59', 'merged', '', 'atlas-mobile', ''),
|
|
107
|
-
];
|
|
108
|
-
return { me, tickets, comments, prs, activity: acts, asks: [] };
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const garden = () => {
|
|
112
|
-
const me = 'alex';
|
|
113
|
-
const prs = [
|
|
114
|
-
pr(12, '', 'Water schedule: skip rainy days', 'garden-bot', { updated_at: ago(2), checks: 'running' }),
|
|
115
|
-
pr(11, '', 'Soil sensor calibration', 'garden-bot', { state: 'MERGED', merged_at: ago(30), updated_at: ago(30) }),
|
|
116
|
-
pr(9, '', 'Add the greenhouse zone', 'garden-bot', { state: 'MERGED', merged_at: ago(120), updated_at: ago(120) }),
|
|
117
|
-
];
|
|
118
|
-
const acts = [
|
|
119
|
-
activity(2, 12, '', 'comment', 'you', 'garden-bot', 'Rebased on the sensor change.'),
|
|
120
|
-
activity(30, 11, '', 'merged', '', 'garden-bot', ''),
|
|
121
|
-
activity(120, 9, '', 'merged', '', 'garden-bot', ''),
|
|
122
|
-
];
|
|
123
|
-
return { me, tickets: [], comments: [], prs, activity: acts, asks: [ask('g1', 'garden-bot', 13, 'Frost warning notifications', '', 'review requested', 'Sam Friend', 4, 'Frost warning notifications')] };
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const SCENARIOS = { northwind, atlas, garden };
|
|
127
|
-
|
|
128
|
-
export const poll = async ({ settings, cache }) => {
|
|
129
|
-
const scenario = SCENARIOS[settings.scenario];
|
|
130
|
-
if (!scenario) {
|
|
131
|
-
throw new Error(`Unknown demo scenario "${settings.scenario}"; use northwind, atlas or garden`);
|
|
132
|
-
}
|
|
133
|
-
cache.polls = (cache.polls || 0) + 1;
|
|
134
|
-
cache.base = cache.base || Date.now();
|
|
135
|
-
base = cache.base;
|
|
136
|
-
const scenarioData = scenario(cache.polls);
|
|
137
|
-
const titles = new Map(scenarioData.prs.map((entry) => [entry.url, entry.title]));
|
|
138
|
-
const data = { runs: [], ...scenarioData, activity: scenarioData.activity.map((item) => ({ ...item, title: item.title || titles.get(item.url) || '' })) };
|
|
139
|
-
if (settings.part === 'tickets') {
|
|
140
|
-
return { me: data.me, tickets: data.tickets, comments: data.comments, ticket_base: SITE, errors: {} };
|
|
141
|
-
}
|
|
142
|
-
if (settings.part === 'code') {
|
|
143
|
-
return { me: data.me, prs: data.prs, activity: data.activity, asks: data.asks, runs: data.runs, errors: {} };
|
|
144
|
-
}
|
|
145
|
-
return { ...data, ticket_base: SITE, errors: {} };
|
|
146
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const T=e=>fetch(e).then(t=>t.json(),()=>Promise.reject(new Error("unreachable"))),E=(e,t)=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t||{})}).then(n=>n.json(),()=>Promise.reject(new Error("unreachable"))),s=(e,t)=>(e.details||[]).find(n=>n["@type"]===t),S=e=>{var o,r;const t=s(e,"BadRequest");if(t&&t.field_violations)return t.field_violations.map(i=>i.description);const n=(r=(o=s(e,"ErrorInfo"))==null?void 0:o.metadata)==null?void 0:r.detail;return[n?String(n):e.message]},h=e=>{var t;return((t=s(e,"ErrorInfo"))==null?void 0:t.reason)||""},O=e=>fetch("/api/handoff?"+new URLSearchParams(e)).then(t=>t.ok?t.text():Promise.reject(new Error(String(t.status)))),_=e=>typeof e=="string"&&/^https:\/\//.test(e)?e:"",f="No workspace configured. Open Settings.",y=e=>e!=="",N=(e,t,n)=>e.some(o=>o.id===t&&o.open_moves!==n)?e.map(o=>o.id===t?{...o,open_moves:n}:o):e,A=(e,t)=>{const n=e.filter(a=>a.group==="you").map(a=>({row:a,handled:!!a.handled})).sort((a,c)=>a.row.rank-c.row.rank||String(a.row.updated).localeCompare(String(c.row.updated))),o=n.filter(a=>!a.handled).length,r=t==="handled";return{rows:n.filter(a=>a.handled===r),open:o,handled:n.length-o}},d=e=>String(e.status_since||e.updated),R=e=>e.filter(t=>t.group==="done").sort((t,n)=>d(n).localeCompare(d(t))).slice(0,15),b=e=>e.filter(t=>t.group==="others").sort((t,n)=>String(n.updated).localeCompare(String(t.updated))),v=e=>e==="failing"?"bad":e==="running"?"warn":e==="passing"?"ok":"",g={code_review:"info",qa:"info",on_hold:"warn"},P=e=>g[e||""]||"",D=e=>({open:"info",done:"ok",dropped:"warn"})[e||""]||"info",k=(e,t,n=1)=>e.filter(o=>o>t+n).length,p="unreachable",l="The local server is not answering",u=l+"; try again once it is back.",C=l+"; your text is kept.",m={NO_WORKSPACE:f,NOT_CONFIGURED:"The maintainer's tracker is not connected on this machine, so nothing can be sent from here.",UPSTREAM_FAILED:"The tracker did not answer; try again in a minute.",RATE_LIMITED:"The source is rate-limited; the monitor backs off.",OPEN_TOO_SOON:"The app window is already opening; give it a few seconds.",INTERNAL:"The local server failed; see its log."},w=(e,t="Not saved")=>m[h(e)]||t+": "+(e.message||"the server refused the change."),U=(e,{refused:t="Not saved",unreachable:n=u}={})=>{if(e instanceof Error)return e.message===p?n:t+": "+e.message;if(!e||typeof e!="object"||Array.isArray(e))return"";const o=e.error;return o?w(o,t):""},j=e=>{const t=Math.round((Date.now()-new Date(e).getTime())/6e4);return isFinite(t)?t<1?"now":t<60?t+"m ago":t<2880?Math.round(t/60)+"h ago":new Date(e).toLocaleDateString("en-US",{month:"short",day:"numeric"}):""},L=e=>{const t=(Date.now()-new Date(e??"").getTime())/36e5;return!isFinite(t)||t<0?{text:"",old:!1,hours:0}:{text:t<1?"<1h":t<48?Math.floor(t)+"h":Math.floor(t/24)+"d",old:t>=24,hours:t}},M=e=>e?new Date(e).toLocaleString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"",I=(e,t)=>t?`${e}--${t}`:"";export{l as S,C as T,y as a,U as b,j as c,v as d,w as e,P as f,T as g,O as h,M as i,R as j,L as k,k as l,I as m,b as n,D as o,E as p,S as q,h as r,_ as s,N as w,A as y};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{w as K,t as qe,r as Ye,s as Jt,c as w,d as N,o as r,a as i,F as V,b as j,e as X,u,n as W,f as o,g as _,h as T,i as I,_ as F,j as U,k as O,l as q,m as We,p as vt,q as Ne,v as te,x as Zt,y as Le,z as je,A as He,B as Qt,C as kt,D as es,E as ts,G as ss,H as ns}from"./_plugin-vue_export-helper-DHr5RHTx.js";import{s as os,m as yt,u as gt,a as wt,r as bt,p as as,b as ls,i as ot,l as xt,c as rs,M as G,d as we,e as ke,f as is,g as Xe,h as Me,j as cs,k as Ke,n as ds,o as us,q as ps,t as _s,v as at,w as hs}from"./scheme-DJ3WNw60.js";import{g as he,r as fs,a as ms,e as vs,w as $t,p as le,b as oe,T as Pe,S as xe,c as ne,h as ks,s as Ge,m as fe,d as ys,f as Ct,i as Je,j as gs,k as Ze,l as ws,y as bs,n as xs,o as $s,q as St}from"./bem-puYdOas_.js";const pe=os(bt),lt=(e,t)=>{const s=G,a=new Float32Array(s*s);for(let l=0;l<a.length;l+=1)a[l]=e[l]===t?1e6:0;const n=(l,c,h)=>{a[c]+h<a[l]&&(a[l]=a[c]+h)};for(let l=0;l<s;l+=1)for(let c=0;c<s;c+=1){const h=l*s+c;c>0&&n(h,h-1,1),l>0&&n(h,h-s,1),c>0&&l>0&&n(h,h-s-1,Math.SQRT2),c<s-1&&l>0&&n(h,h-s+1,Math.SQRT2)}for(let l=s-1;l>=0;l-=1)for(let c=s-1;c>=0;c-=1){const h=l*s+c;c<s-1&&n(h,h+1,1),l<s-1&&n(h,h+s,1),c<s-1&&l<s-1&&n(h,h+s+1,Math.SQRT2),c>0&&l<s-1&&n(h,h+s-1,Math.SQRT2)}return a},De=e=>{const t=String(e||"").trim(),s=/^#([0-9a-f]{6})$/i.exec(t);if(s)return[0,2,4].map(n=>parseInt(s[1].slice(n,n+2),16));const a=t.match(/[\d.]+/g);return a&&a.length>=3?a.slice(0,3).map(Number):null},Cs=e=>{const t=getComputedStyle(e),s=De(t.getPropertyValue("--md-primary"))||[44,86,151],a=De(t.getPropertyValue("--md-on-primary-container"))||s;return{core:ot()?s:s.map((n,l)=>xt(n,a[l],.35)),rim:De(t.getPropertyValue("--blob-3"))||[23,223,220],strength:ot()?.9:1}},Ss=(e,t,s)=>({size:e.range(.85,1.05)*Math.min(t,s),right:e.chance(.4),bottom:e.chance(.7),overflowX:e.range(-.02,.06),overflowY:e.range(-.02,.04)}),Ts=e=>{const t={minX:G,maxX:0,minY:G,maxY:0};for(let a=0;a<e.length;a+=1)if(e[a]){const n=a%G,l=Math.floor(a/G);t.minX=Math.min(t.minX,n),t.maxX=Math.max(t.maxX,n),t.minY=Math.min(t.minY,l),t.maxY=Math.max(t.maxY,l)}const s=a=>(a-G/2)/we;return{minX:s(t.minX),maxX:s(t.maxX),minY:s(t.minY),maxY:s(t.maxY)}},Ms=(e,t,s,a)=>{const n=t.maxX-t.minX,l=t.maxY-t.minY,c=Math.min(e.size,2*s*.95/n,2*a*.98/l),h=c/2,m=-e.overflowX*c,p=-e.overflowY*c;return{size:c,centerX:e.right?s-m-t.maxX*h:m-t.minX*h,centerY:e.bottom?a-p-t.maxY*h:p-t.minY*h}},As=(e,t,s)=>"rgba("+e.core.map((n,l)=>Math.round(xt(n,e.rim[l],t))).join(",")+","+s.toFixed(3)+")",Rs=(e,t,s)=>{const a=[],n=t.size/62,l=t.size/2/we,c=t.size*is/4,h=n*.866;for(let m=0,p=t.centerY-c;p<t.centerY+c;m+=1,p+=h)for(let y=t.centerX-c+m%2*n/2;y<t.centerX+c;y+=n){const A=y+e.range(-.3,.3)*n,E=p+e.range(-.3,.3)*n,R=Math.floor((A-t.centerX)/l+G/2),C=Math.floor((E-t.centerY)/l+G/2);if(R<0||C<0||R>=G||C>=G)continue;const d=C*G+R,v=e.next(),f=e.range(.75,1.25),B=e.range(-.15,.15);if(s.inside[d]){const z=s.depth[d]/we,L=ke(z/.035),J=ke(z/.3);if(v>.5+.5*L)continue;a.push({x:A,y:E,radius:n*.5*(.45+.55*L)*(.95+.3*J)*f,alpha:.5+.25*L+.25*J,tint:ke(.65-.65*J+B)})}else{const z=s.gap[d]/we;if(v>.14*Math.exp(-z/.05)+.004)continue;a.push({x:A,y:E,radius:n*e.range(.1,.24),alpha:.35,tint:ke(.8+B)})}}return a},Es=()=>document.getElementById("wt"),Os=()=>document.getElementById("backdrop"),Tt=()=>Math.floor(Math.random()*4294967296),ye={},D={workspace:"",seed:Tt(),species:"",wantedSpecies:"",wantedPose:"",built:null},Ds=(e,t)=>{const s=rs(D.seed),a=pe[Math.floor(s.next()*pe.length)];D.species=pe.includes(D.wantedSpecies)?D.wantedSpecies:a;const n=Ss(s,e,t),l={facing:n.right?-1:1,tilt:s.range(-.07,.07),stretchX:s.range(.92,1.08),stretchY:s.range(.92,1.08)};s.chance(.25)&&(l.facing=-l.facing);const c=as(ls(bt,D.species,s,{pose:D.wantedPose}).shapes,l),h={inside:c,depth:lt(c,1),gap:lt(c,0)},m=Ms(n,Ts(c),e,t);return{key:D.seed+":"+D.species+":"+e+"x"+t,dots:Rs(s,m,h)}},$e=()=>{const e=Os();if(!e)return;const t=e.clientWidth,s=e.clientHeight,a=window.devicePixelRatio||1,n=Math.round(t*a),l=Math.round(s*a);(e.width!==n||e.height!==l)&&(e.width=n,e.height=l);const c=e.getContext("2d");if(!c||(c.setTransform(a,0,0,a,0,0),c.clearRect(0,0,t,s),!t||!s))return;const h=D.seed+":"+(D.wantedSpecies||"?")+":"+t+"x"+s;(!D.built||D.built.key.split(":")[0]!==String(D.seed)||!D.built.key.endsWith(t+"x"+s)||D.wantedSpecies&&D.species!==D.wantedSpecies)&&(D.built=Ds(t,s));const m=Es();if(!m)return h;const p=Cs(m);return D.built.dots.forEach(y=>{c.fillStyle=As(p,y.tint,y.alpha*p.strength),c.beginPath(),c.arc(y.x,y.y,y.radius,0,Math.PI*2),c.fill()}),h};window.whoseTurnBackdrop={species:pe.slice(),seeded:yt,get current(){return{workspace:D.workspace,species:D.species,seed:D.seed}},show:e=>{const t=e||{},s=String(t.workspace||"");ye[s]||(ye[s]=Tt());const a=t.seed!==void 0?Number(t.seed)>>>0:ye[s];ye[s]=a;const n=t.species&&pe.includes(t.species)?t.species:"",l=String(t.pose||"");return s===D.workspace&&a===D.seed&&n===D.wantedSpecies&&l===D.wantedPose&&D.built||(D.workspace=s,D.seed=a,D.wantedSpecies=n,D.wantedPose=l,D.built=null,$e()),window.whoseTurnBackdrop.current},repaint:$e};K(Xe,$e);gt(window,"resize",wt($e,200));const{show:Mt,repaint:Is,seeded:jr}=window.whoseTurnBackdrop,Bs=()=>{window.tippy&&window.tippy.delegate(document.body,{target:"[data-tip]",content:e=>e.dataset.tip||"",trigger:"mouseenter focus click",delay:[0,80],duration:[120,80],interactive:!0,maxWidth:440,placement:"top",theme:"wt",appendTo:()=>document.body})},Vs=()=>{let e=0;return{next:()=>++e,accepts:t=>t===e}},rt=e=>{const t=structuredClone(qe(e)),s=t.connectors||{},a=t.rules||{};return a.statuses=a.statuses||{},{...t,connectors:Array.isArray(s)?s:Object.entries(s).map(([n,l])=>({type:n,...l})),rules:a,theme:t.theme||{}}},Ws=e=>{const t=structuredClone(qe(e));return t.theme&&!Object.keys(t.theme).length&&delete t.theme,t.rules&&!Object.keys(t.rules.statuses||{}).length&&delete t.rules.statuses,t.rules&&!Object.keys(t.rules).length&&delete t.rules,t},it=new WeakMap;let Ns=0;const ct=e=>{const t=qe(e);let s=it.get(t);return s||(s=++Ns,it.set(t,s)),s},dt=e=>JSON.stringify(e),js=(e,t)=>{const s=[];return e.forEach(a=>a.connectors.forEach(n=>{const l=t.get(n)||{};Object.keys(l).forEach(c=>{l[c]&&s.push({ws:a.id,connector:n.id||n.type,key:c,value:l[c]})})})),s},re=(e,t,s)=>{typeof s=="boolean"||s&&s.length?e[t]=s:delete e[t]},Ps=(e,t)=>{const s=a=>{const n=t.indexOf(a.id);return n<0?t.length:n};return[...e].sort((a,n)=>s(a)-s(n))},Us=(e,t,s,a)=>{const n=e.filter(l=>l!==t);return n.splice(n.indexOf(s)+(a?0:1),0,t),n},Fs={gt:">",gte:"≥",lt:"<",lte:"≤",prefix:"starts with"},zs=(e,t)=>`${e} is ${t?"present":"absent"}`,qs=(e,t)=>Array.isArray(t)?`${e} in ${t.join(", ")}`:t&&typeof t=="object"?Object.entries(t).map(([s,a])=>s==="present"?zs(e,a):`${e} ${Fs[s]} ${JSON.stringify(a)}`).join(" and "):`${e} = ${t}`,Ys=e=>({when:Object.entries(e.when).map(([t,s])=>qs(t,s)).join(" and ")||"anything else",then:e.then.whose==="hidden"?"hidden":`${e.then.whose}: ${e.then.next}`}),Ls=e=>e.modal?"modal":e.settings?"settings":e.feedback?"feedback":null,Hs=(e,t)=>{const s=new URLSearchParams(e).get("mode");return s==="app"||s==="tab"?s:t==="app"?"app":"tab"},Xs=e=>"/?mode=tab"+(e?"&ws="+encodeURIComponent(e):""),Ks=e=>({width:e.outerWidth,height:e.outerHeight}),At={writeDefaults:!1},Rt=Me("wt-ws","",At),Et=cs("wt-mode","",At),k=Jt({workspaces:[],workspace:{id:"",name:"",connectors:[],links:[],key_pattern:"",theme:null},turn:[],feed:[],marks:{seen:[],base:0,handled:{}},counts:{mine:0,others:0,failing:0,done:0},todos:[],errors:{},health:{ok:!0,stale:!1,fresh_at:"",failing:[],alerts:[]},problems:[],polled_at:""}),g=Ye({ws:new URLSearchParams(location.search).get("ws")||Rt.value,open:new Set,mode:Hs(location.search,Et.value),live:!1,reconnects:0,status:"Connecting…",modal:null,feedback:!1,settings:!1,settingsDirty:!1,settingsAsk:!1,settingsAt:""});Et.value=g.mode;const Ce=Ke("",6e3),ae=(e,t)=>{const s=oe(e,t);return s&&(Ce.value=s),!s},Gs=()=>{g.mode==="app"&>(window,"resize",wt(()=>le("/api/window",Ks(window)).catch(ae),600))},Ot={resume:()=>{}},ce=us({scheduler:e=>{const t=ps(e,3e4,{immediate:!1});return Ot.resume=t.resume,t}}),ut=w(()=>{const e=k.workspace.theme;return e&&e.light&&e.dark?e[Xe.value?"dark":"light"]:{}}),H=w(()=>k.counts),pt=w(()=>[...k.problems||[],...k.health.alerts.map(e=>e.text),...Object.entries(k.errors||{}).map(([e,t])=>e+": "+t)]),Js=(e="")=>{g.settingsAt=e,g.settings=!0},Dt=w(()=>Ls(g)),Zs=()=>{const e=Dt.value;if(e==="modal"&&(g.modal=null),e==="feedback"&&(g.feedback=!1),e==="settings"){if(g.settingsDirty){g.settingsAsk=!0;return}g.settings=!1}},Ue=new Map,Qs=(e,t)=>{t&&Ue.set(e,document.activeElement)},en=(e,t)=>{if(t)return;const s=Ue.get(e);Ue.delete(e),s instanceof HTMLElement&&s.focus()},tn={modal:()=>!!g.modal,settings:()=>g.settings,feedback:()=>g.feedback};Object.entries(tn).forEach(([e,t])=>{K(t,s=>Qs(e,s),{flush:"sync"}),K(t,s=>en(e,s),{flush:"post"})});let Se=-1,Fe=0;const _t=Vs(),me=(e=g.ws)=>{const t=_t.next();return he("/api/state"+(e?"?ws="+encodeURIComponent(e):"")).then(s=>_t.accepts(t)?s.error?fs(s.error)!=="NO_SUCH_WORKSPACE"||!ms(e)?(g.status=vs(s.error,"Could not load"),k):me(""):(Object.assign(k,s),g.ws=k.workspace.id,g.status="",Mt({workspace:k.workspace.id}),document.title="Whose Turn · "+k.workspace.name,k):k)},It=()=>(Se=-1,me()),_e=ds("/api/stream",[],{immediate:!1,autoConnect:!1,autoReconnect:!0,serializer:{read:e=>JSON.parse(e||"null")}});K(_e.status,e=>{g.live=e==="OPEN",g.live&&(g.status="",g.reconnects++)},{flush:"sync"});K(_e.error,e=>{e&&(g.status="Disconnected from the local server, retrying…")},{flush:"sync"});K(_e.data,e=>{e&&(typeof e.open_moves=="number"&&(k.workspaces=$t(k.workspaces,e.ws,e.open_moves)),!(k.workspace.id&&e.ws!==k.workspace.id)&&(g.status="",e.version!==Se?(Se=e.version,Fe===0&&me()):k.polled_at=e.polled_at))},{flush:"sync"});const sn=()=>{_e.status.value==="CLOSED"&&_e.open()},nn=()=>(Ot.resume(),me().then(sn)),Bt=e=>e===k.workspace.id?Promise.resolve():(Se=-1,Fe++,me(e).then(()=>{g.ws===e&&(Rt.value=g.ws,g.open.clear())},()=>{Ce.value=xe+"; staying on "+k.workspace.name+"."}).finally(()=>{Fe--})),Vt=(e,t)=>le(e,{ws:k.workspace.id,...t}),ze=e=>Vt("/api/marks",e).then(t=>{const s=oe(t);return s||(Object.assign(k,{marks:t.marks,turn:t.turn,counts:t.counts}),k.workspaces=$t(k.workspaces,k.workspace.id,t.counts.mine)),s},t=>oe(t)),on=e=>Vt("/api/todos",e).then(t=>{const s=oe(t,{unreachable:Pe});return!s&&Array.isArray(t)&&(k.todos=t),s},t=>oe(t,{unreachable:Pe})),an=e=>he("/api/config").then(t=>le("/api/config",{workspaces:Ps(t.workspaces,e),secrets:[]})).then(t=>{ae(t,{refused:"The order was not saved"})&&It()},t=>ae(t,{refused:"The order was not saved"})),Ie={id:"demo",name:"Demo",key_pattern:"\\bNW-\\d+\\b",theme:{seed:"#3d7a5c",accent:"#c27a2c"},connectors:[{type:"demo",scenario:"northwind",label:"Demo tracker"}]},ln=()=>he("/api/config").then(e=>e.workspaces.some(t=>t.id===Ie.id)?null:le("/api/config",{workspaces:[...e.workspaces,Ie],secrets:[]})).then(e=>{if(ae(e,{refused:"The demo workspace was not added"}))return Bt(Ie.id)},e=>ae(e,{refused:"The demo workspace was not added"})),ht=()=>{g.status="Checking now…",le("/api/refresh").catch(e=>{g.status="",ae(e)}),Mt({workspace:k.workspace.id,seed:Math.floor(Math.random()*4294967296)})},rn="M12 2.5l1.9 5.6 5.6 1.9-5.6 1.9L12 17.5l-1.9-5.6L4.5 10l5.6-1.9L12 2.5zM5 15l.9 2.6 2.6.9-2.6.9L5 22l-.9-2.6-2.6-.9 2.6-.9L5 15zm14-2l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7.7-2z",cn="M19.4 13a7.6 7.6 0 0 0 0-2l2-1.5-2-3.4-2.4 1a7.5 7.5 0 0 0-1.7-1L15 3.5H9l-.3 2.6a7.5 7.5 0 0 0-1.7 1l-2.4-1-2 3.4 2 1.5a7.6 7.6 0 0 0 0 2l-2 1.5 2 3.4 2.4-1a7.5 7.5 0 0 0 1.7 1l.3 2.6h6l.3-2.6a7.5 7.5 0 0 0 1.7-1l2.4 1 2-3.4-2-1.5zM12 15.5a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7z",dn="M19 8h-1.8a5.9 5.9 0 0 0-1.3-1.9l1.6-1.6-1.4-1.4-1.9 1.9A5.7 5.7 0 0 0 12 4.5a5.7 5.7 0 0 0-2.2.5L7.9 3.1 6.5 4.5l1.6 1.6A5.9 5.9 0 0 0 6.8 8H5v2h1.2a7 7 0 0 0 0 1v1H5v2h1.3a6 6 0 0 0 11.4 0H19v-2h-1.2v-1a7 7 0 0 0 0-1H19V8zm-7 10a4 4 0 0 1-4-4v-3a4 4 0 0 1 8 0v3a4 4 0 0 1-4 4zm-1-7h2v5h-2z",un=e=>{let t=2166136261;for(let s=0;s<e.length;s+=1)t^=e.charCodeAt(s),t=Math.imul(t,16777619);return t>>>0},Wt=e=>{const t=String(e||"").trim().split(/[\s_-]+/).filter(Boolean);return(t.length>=2?t.slice(0,3).map(a=>a.charAt(0)):(t[0]||"?").slice(0,2).split("")).join("").toUpperCase()},pn=e=>({1:17,2:14,3:11})[e.length]||11,_n=(e,t)=>{const s=t(un(e.id)),a=[],n=3+Math.floor(s()*3);for(let c=0;c<n;c+=1){const h=s()*Math.PI*2,m=9+s()*7;a.push({cx:(18+Math.cos(h)*m).toFixed(1),cy:(18+Math.sin(h)*m).toFixed(1),r:(1.6+s()*2.2).toFixed(1),opacity:(.35+s()*.4).toFixed(2)})}const l=Wt(e.name||e.id);return{dots:a,glyph:l,fontSize:pn(l)}},hn={class:"rail","aria-label":"Workspaces"},fn=["data-drop","href","aria-label","aria-selected","onClick","onDragstart","onDragover","onDrop"],mn={viewBox:"0 0 36 36",class:"rail__icon","aria-hidden":"true"},vn=["fill"],kn=["cx","cy","r","fill","opacity"],yn=["y","font-size","fill"],gn={class:"rail__label"},wn={key:0,class:"rail__badge"},bn={viewBox:"0 0 24 24",class:"rail__gear","aria-hidden":"true"},xn=["d"],$n=N({__name:"Rail",emits:["settings"],setup(e,{emit:t}){const s=t,a=w(()=>Object.fromEntries(k.workspaces.map(d=>[d.id,_n(d,yt)]))),n=w(()=>Object.fromEntries(k.workspaces.map(d=>{const v=d.theme?d.theme[Xe.value?"dark":"light"]:null;return[d.id,{fill:v?v["--md-primary-container"]:"var(--md-primary-container)",ink:v?v["--md-on-primary-container"]:"var(--md-on-primary-container)",dot:v?v["--md-primary"]:"var(--md-primary)"}]}))),l=d=>{d.detail>0&&d.currentTarget.blur()},c=(d,v)=>{l(d),Bt(v)},h=d=>{s("settings"),l(d)},m=I(""),p=I({}),y=d=>{const v=d.currentTarget;return d.clientY-v.getBoundingClientRect().top<v.offsetHeight/2},A=(d,v)=>{m.value=v.id,d.dataTransfer&&(d.dataTransfer.effectAllowed="move",d.dataTransfer.setData("text/plain",v.id))},E=()=>{m.value="",p.value={}},R=(d,v)=>{if(!m.value||m.value===v.id)return;d.preventDefault(),d.dataTransfer&&(d.dataTransfer.dropEffect="move");const f=y(d)?"before":"after";(p.value.id!==v.id||p.value.side!==f)&&(p.value={id:v.id,side:f})},C=(d,v)=>{if(!m.value||m.value===v.id)return;d.preventDefault();const f=Us(k.workspaces.map(B=>B.id),m.value,v.id,y(d));p.value={},an(f)};return(d,v)=>(r(),i("nav",hn,[(r(!0),i(V,null,j(u(k).workspaces,f=>(r(),i("a",{key:f.id,class:W(["rail__item",{"rail__item--dragging":m.value===f.id}]),"data-drop":p.value.id===f.id?p.value.side:null,href:"?ws="+encodeURIComponent(f.id),"aria-label":f.name,"aria-selected":f.id===u(k).workspace.id,draggable:"true",onClick:X(B=>c(B,f.id),["prevent"]),onDragstart:B=>A(B,f),onDragend:E,onDragover:B=>R(B,f),onDragleave:v[0]||(v[0]=B=>p.value={}),onDrop:B=>C(B,f)},[(r(),i("svg",mn,[o("rect",{x:"0",y:"0",width:"36",height:"36",rx:"10",fill:n.value[f.id].fill},null,8,vn),(r(!0),i(V,null,j(a.value[f.id].dots,(B,z)=>(r(),i("circle",{key:z,cx:B.cx,cy:B.cy,r:B.r,fill:n.value[f.id].dot,opacity:B.opacity},null,8,kn))),128)),o("text",{x:"18",y:a.value[f.id].fontSize>=17?24:23,"text-anchor":"middle","font-size":a.value[f.id].fontSize,"font-weight":"700","letter-spacing":"-0.5","font-family":"Roboto, Inter, system-ui, sans-serif",fill:n.value[f.id].ink},_(a.value[f.id].glyph),9,yn)])),o("span",gn,_(f.name),1),f.open_moves?(r(),i("span",wn,_(f.open_moves),1)):T("",!0)],42,fn))),128)),v[2]||(v[2]=o("span",{class:"rail__spacer"},null,-1)),o("a",{class:"rail__item",href:"#settings","aria-label":"Settings",onClick:X(h,["prevent"])},[(r(),i("svg",bn,[o("path",{d:u(cn),fill:"currentColor"},null,8,xn)])),v[1]||(v[1]=o("span",{class:"rail__label"},"Settings",-1))])]))}}),Cn=F($n,[["__scopeId","data-v-be863916"]]),Be=" · checks every minute",Nt=(e,t,s,a)=>{const n=e.failing.map(c=>c.source),l=t?ne(t):"—";return n.length&&n.length>=s?{dot:"off",text:"No data: "+n.join(", ")+" failing · last data "+ne(e.fresh_at),failing:n,noData:!0}:e.stale?{dot:"off",text:"Stale · last data "+ne(e.fresh_at)+Be,failing:n,noData:!1}:n.length?{dot:a?"warn":"off",text:n.join(", ")+" failing · last check "+l+Be,failing:n,noData:!1}:{dot:a?"ok":"off",text:"Live · last check "+l+Be,failing:n,noData:!1}},Sn=["data-tip"],Tn={class:"ai-button__icon",viewBox:"0 0 24 24","aria-hidden":"true"},Mn=["d"],An=N({__name:"AiButton",props:{query:{},big:{type:Boolean}},setup(e){const t=e,s={"":"Copy for AI: this with its context as text, ready to paste into Claude or any other assistant",sent:"Copied",failed:"Could not copy"},a=Ke("",1600),n=w(()=>s[a.value]),l=()=>ks(t.query).then(c=>navigator.clipboard.writeText(c)).then(()=>{a.value="sent"},()=>{a.value="failed"});return(c,h)=>(r(),i("button",{type:"button",class:W(["ai-button",{"ai-button--big":e.big,"ai-button--sent":u(a)==="sent","ai-button--failed":u(a)==="failed"}]),"aria-label":"Copy for AI","data-tip":n.value,onClick:X(l,["stop"])},[(r(),i("svg",Tn,[o("path",{d:u(rn),fill:"currentColor"},null,8,Mn)]))],10,Sn))}}),de=F(An,[["__scopeId","data-v-d32a60c7"]]),Rn={class:"status-bar"},En={class:"status-bar__icon",viewBox:"0 0 24 24","aria-hidden":"true"},On=["d"],Dn={key:0,class:"status-bar__notice",role:"alert"},In=["href"],Bn=["aria-disabled"],Vn=5e3,Wn=N({__name:"StatusBar",emits:["report"],setup(e,{emit:t}){const s=t,a=w(()=>(ce.value,Nt(k.health,k.polled_at,k.workspace.connectors.length,g.live))),n=I(!1),l=I(!1),c=w(()=>l.value?"Opened in the app window. You can close this tab.":g.status||a.value.text),h=p=>ae(p,{refused:"The app window did not open"}),m=()=>{if(n.value)return;n.value=!0,setTimeout(()=>n.value=!1,Vn);const p=y=>{h(y),l.value=!oe(y)};le("/api/open",{mode:"app"}).then(p,p)};return(p,y)=>(r(),i("div",Rn,[o("button",{type:"button",class:"btn status-bar__btn status-bar__report",title:"A bug or a wish about this monitor, straight to its backlog",onClick:y[0]||(y[0]=A=>s("report"))},[(r(),i("svg",En,[o("path",{d:u(dn),fill:"currentColor"},null,8,On)])),y[2]||(y[2]=U("Report ",-1))]),o("span",{class:W(["status-bar__live",{"status-bar__live--off":a.value.dot==="off","status-bar__live--warn":a.value.dot==="warn"}])},null,2),o("span",null,_(c.value),1),u(Ce)?(r(),i("span",Dn,_(u(Ce)),1)):T("",!0),o("button",{type:"button",class:"btn status-bar__btn",onClick:y[1]||(y[1]=(...A)=>u(ht)&&u(ht)(...A))},"Refresh now"),u(g).mode==="app"?(r(),i("a",{key:1,class:"btn status-bar__btn",href:u(Xs)(u(g).ws),target:"_blank",rel:"noopener"},"Open in browser",8,In)):(r(),i("button",{key:2,type:"button",class:W(["btn status-bar__btn",{"status-bar__btn--busy":n.value}]),"aria-disabled":n.value,onClick:m},_(n.value?"Opening…":"Open as app"),11,Bn)),O(de,{query:{all:"1"}}),y[3]||(y[3]=o("span",{class:"ai-hint status-bar__ai-hint"},"AI: copy everything",-1))]))}}),Nn=F(Wn,[["__scopeId","data-v-b74e71cb"]]),jt=e=>{const t=Me("wt-collapsed",[],{writeDefaults:!1}),s=w(()=>t.value.includes(e));return{collapsed:s,toggle:()=>{t.value=s.value?t.value.filter(n=>n!==e):[...t.value,e]}}},jn=["href"],Pn={key:1},se=N({__name:"ExtLink",props:{text:{default:""},url:{default:""}},setup(e){const t=e,s=w(()=>Ge(t.url));return(a,n)=>s.value?(r(),i("a",{key:0,href:s.value,target:"_blank",rel:"noopener"},_(e.text),9,jn)):(r(),i("span",Pn,_(e.text),1))}}),Un={class:"workspace-header__title"},Fn={class:"workspace-header__name"},zn={class:"workspace-header__line workspace-header__sentence"},qn={class:"workspace-header__count"},Yn={class:"workspace-header__count"},Ln={key:0},Hn={class:"workspace-header__count"},Xn={key:0,class:"workspace-header__line workspace-header__strip"},Kn={class:"workspace-header__count"},Gn={class:"workspace-header__count"},Jn={class:"workspace-header__count"},Zn={class:"workspace-header__count"},Qn={class:"workspace-header__line workspace-header__give"},eo={class:"workspace-header__links"},to={class:"workspace-header__tiles"},so={class:"workspace-header__tile workspace-header__tile--you"},no={class:"workspace-header__tile-value"},oo={class:"workspace-header__tile"},ao={class:"workspace-header__tile-value"},lo={class:"workspace-header__tile-value"},ro={class:"workspace-header__tile"},io={class:"workspace-header__tile-value"},co=N({__name:"WorkspaceHeader",setup(e){const{collapsed:t,toggle:s}=jt("header");return(a,n)=>(r(),i("header",{id:"summary",class:W(["island workspace-header",{"workspace-header--collapsed":u(t)}])},[o("div",Un,[o("h1",Fn,_(u(k).workspace.name||"Whose Turn"),1),o("p",zn,[o("b",qn,_(u(H).mine),1),n[3]||(n[3]=U(" waiting on you, ",-1)),o("b",Yn,_(u(H).others),1),n[4]||(n[4]=U(" waiting on someone else",-1)),u(H).failing?(r(),i("span",Ln,[n[1]||(n[1]=U(", ",-1)),o("b",Hn,_(u(H).failing),1),n[2]||(n[2]=U(" failing",-1))])):T("",!0),n[5]||(n[5]=U(". ",-1))]),u(t)?(r(),i("p",Xn,[n[6]||(n[6]=U(" Your move ",-1)),o("b",Kn,_(u(H).mine),1),n[7]||(n[7]=U(" · Waiting on others ",-1)),o("b",Gn,_(u(H).others),1),n[8]||(n[8]=U(" · Failing ",-1)),o("b",Jn,_(u(H).failing),1),n[9]||(n[9]=U(" · Done ",-1)),o("b",Zn,_(u(H).done),1)])):T("",!0),o("p",Qn,[O(de,{query:{ws:u(k).workspace.id,kind:"workspace"},big:""},null,8,["query"]),n[10]||(n[10]=o("span",{class:"ai-hint"},"AI: copy this workspace",-1)),o("span",eo,[(r(!0),i(V,null,j(u(k).workspace.links||[],l=>(r(),q(se,{key:l.url,text:l.label,url:l.url,title:l.url},null,8,["text","url","title"]))),128))])])]),o("button",{type:"button",class:W(["fold-toggle workspace-header__fold",{"fold-toggle--folded":u(t)}]),"aria-label":"Collapse or expand header","data-tip":"Fold the header; it stays folded until you open it again",onClick:n[0]||(n[0]=X((...l)=>u(s)&&u(s)(...l),["stop"]))}," ▾ ",2),o("div",to,[o("div",so,[n[11]||(n[11]=o("span",{class:"workspace-header__tile-label"},"Your move",-1)),o("strong",no,_(u(H).mine),1)]),o("div",oo,[n[12]||(n[12]=o("span",{class:"workspace-header__tile-label"},"Waiting on others",-1)),o("strong",ao,_(u(H).others),1)]),o("div",{class:W(["workspace-header__tile",{"workspace-header__tile--fail":u(H).failing}])},[n[13]||(n[13]=o("span",{class:"workspace-header__tile-label"},"Open PRs with failing checks",-1)),o("strong",lo,_(u(H).failing),1)],2),o("div",ro,[n[14]||(n[14]=o("span",{class:"workspace-header__tile-label"},"Done in the last 30 days",-1)),o("strong",io,_(u(H).done),1)])])],2))}}),uo=F(co,[["__scopeId","data-v-0f4b6d6e"]]),Qe=e=>{K(()=>g.reconnects,e)},po=["id"],_o={class:"island__head"},ho={class:"island__title"},fo={class:"island__note"},mo={class:"island__note-text"},vo=["data-tip"],ko=["aria-label"],yo={class:"island__body"},go=N({__name:"Island",props:{id:{},title:{},note:{default:""},tip:{default:""},column:{type:Boolean}},setup(e){const t=e,{collapsed:s,toggle:a}=jt(t.id);return(n,l)=>(r(),i("section",{id:e.id,class:W(["island",{"island--collapsed":u(s),"island--column":e.column}])},[o("div",_o,[o("h2",ho,[U(_(e.title)+" ",1),We(n.$slots,"head",{},void 0)]),o("p",fo,[o("span",mo,_(e.note),1),e.tip?(r(),i("button",{key:0,type:"button",class:"hint-icon","aria-label":"More info","data-tip":e.tip}," ? ",8,vo)):T("",!0),o("button",{type:"button",class:W(["fold-toggle",{"fold-toggle--folded":u(s)}]),"aria-label":"Collapse or expand "+e.id,"data-tip":"Fold this panel; it stays folded until you open it again",onClick:l[0]||(l[0]=X((...c)=>u(a)&&u(a)(...c),["stop"]))}," ▾ ",10,ko)])]),o("div",yo,[We(n.$slots,"default",{},void 0)])],10,po))}}),Ae=F(go,[["__scopeId","data-v-a03b7f62"]]),wo=["aria-label"],bo=["aria-pressed","onClick"],xo={key:0,class:"segmented__count"},$o=N({__name:"Segmented",props:Ne({options:{},label:{default:""},chips:{type:Boolean}},{modelValue:{required:!0},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const t=vt(e,"modelValue");return(s,a)=>(r(),i("span",{class:W(["segmented",{"segmented--chips":e.chips}]),role:"group","aria-label":e.label},[(r(!0),i(V,null,j(e.options,n=>(r(),i("button",{key:n.id,type:"button",class:"segmented__option","aria-pressed":t.value===n.id,onClick:l=>t.value=n.id},[U(_(n.label),1),n.count!==void 0?(r(),i("b",xo,_(n.count),1)):T("",!0)],8,bo))),128))],10,wo))}}),Te=F($o,[["__scopeId","data-v-7901b5d6"]]),Co={class:"ticket-cell"},So={class:"ticket-cell__summary"},To=N({__name:"TicketCell",props:{row:{}},setup(e){return(t,s)=>(r(),i("td",Co,[O(se,{text:e.row.key,url:e.row.ticket_url,class:"ticket-key"},null,8,["text","url"]),o("span",So,_(e.row.summary),1)]))}}),et=F(To,[["__scopeId","data-v-8436de88"]]),Mo={key:0,class:"pr-cell"},Ao={key:1,class:"pill"},Ro=N({__name:"PrCell",props:{row:{}},setup(e){return(t,s)=>(r(),i("td",null,[e.row.pr?(r(),i("span",Mo,[O(se,{text:e.row.pr,url:e.row.pr_url,class:"pr-cell__link"},null,8,["text","url"]),e.row.pr_state==="OPEN"&&e.row.checks?(r(),i("span",{key:0,class:W(["pill",u(fe)("pill",u(ys)(e.row.checks))])},_(e.row.checks),3)):e.row.pr_state?(r(),i("span",Ao,_(e.row.pr_state.toLowerCase()),1)):T("",!0)])):T("",!0)]))}}),Pt=F(Ro,[["__scopeId","data-v-75c443c3"]]),Eo=["open"],Oo={class:"recently-done__summary"},Do={class:"recently-done__count"},Io={class:"recently-done__list"},Bo={class:"table"},Vo=["data-tip"],Wo={class:"table__actions"},No=N({__name:"RecentlyDone",setup(e){const t=Me("wt-recent-open",!1,{writeDefaults:!1,serializer:{read:n=>n==="1",write:n=>n?"1":"0"}}),s=n=>{t.value=n.target.open},a=w(()=>(ce.value,gs(k.turn).map(n=>({row:n,since:Ze(n.status_since||n.updated)}))));return(n,l)=>a.value.length?(r(),i("details",{key:0,class:"recently-done",open:u(t),onToggle:s},[o("summary",Oo,[l[0]||(l[0]=U(" Recently done ",-1)),o("span",Do,_(a.value.length),1)]),o("div",Io,[o("table",Bo,[l[1]||(l[1]=o("thead",null,[o("tr",null,[o("th",null,"Ticket"),o("th",null,"Status"),o("th",null,"Done"),o("th",{class:"table__actions-col"},[o("span",{class:"visually-hidden"},"Copy for AI")])])],-1)),o("tbody",null,[(r(!0),i(V,null,j(a.value,({row:c,since:h})=>(r(),i("tr",{key:c.row_id},[O(et,{row:c},null,8,["row"]),o("td",null,[o("span",{class:W(["pill",u(fe)("pill",u(Ct)(c.status_group))])},_(c.status),3)]),o("td",null,[o("span",{class:"age","data-tip":"Done "+u(Je)(c.status_since||c.updated)},_(h.text),9,Vo)]),o("td",Wo,[O(de,{class:"table__ai",query:{ws:u(k).workspace.id,kind:"row",ref:c.row_id}},null,8,["query"])])]))),128))])])])],40,Eo)):T("",!0)}}),jo=F(No,[["__scopeId","data-v-12ee20e8"]]),Po=13,Uo=N({__name:"RowScroll",props:{floor:{type:Boolean}},setup(e){const t=I(null),s=I(null),{y:a}=_s(t),{height:n}=at(t),{height:l}=at(s),c=()=>{var y,A,E;return[...((E=(A=(y=s.value)==null?void 0:y.querySelector("table"))==null?void 0:A.tBodies[0])==null?void 0:E.rows)||[]]},h=()=>{const y=t.value;return y?y.getBoundingClientRect().top+y.clientTop+y.clientHeight:0},m=w(()=>(a.value,n.value,l.value,ws(c().map(y=>y.getBoundingClientRect().bottom),h(),Po))),p=()=>{var d,v;const y=t.value,A=c(),E=A[A.length-m.value];if(!y||!E)return;const C=((v=(d=s.value)==null?void 0:d.querySelector("thead"))==null?void 0:v.getBoundingClientRect().bottom)??y.getBoundingClientRect().top;y.scrollBy({top:E.getBoundingClientRect().top-C,behavior:"smooth"})};return(y,A)=>(r(),i(V,null,[o("div",{ref_key:"scroller",ref:t,class:W(["row-scroll",{"row-scroll--floor":e.floor}])},[o("div",{ref_key:"content",ref:s},[We(y.$slots,"default",{},void 0)],512)],2),m.value?(r(),i("button",{key:0,type:"button",class:"row-scroll__more",onClick:p}," +"+_(m.value)+" more ",1)):T("",!0)],64))}}),Ut=F(Uo,[["__scopeId","data-v-7bd9ace7"]]),Fo=I("open"),zo=()=>Fo,qo={class:"table"},Yo={class:"hint-row"},Lo={class:"your-move__next"},Ho=["data-tip"],Xo=["data-tip"],Ko=["data-tip"],Go={key:0,class:"alert alert--inline your-move__problem",role:"alert"},Jo={class:"table__actions"},Zo=["aria-label","onClick"],Qo={key:0,class:"empty"},ea=N({__name:"YourMove",setup(e){const t=zo(),s=w(()=>{ce.value;const h=bs(k.turn,t.value);return{...h,rows:h.rows.map(m=>({...m,age:Ze(m.row.activity_at)}))}}),a=w(()=>[{id:"open",label:"Open",count:s.value.open},{id:"handled",label:"Handled",count:s.value.handled}]),n=w(()=>{if(t.value==="handled")return"Nothing marked as handled.";const{noData:h,failing:m}=Nt(k.health,k.polled_at,k.workspace.connectors.length,g.live);return h?"No data: "+m.join(", ")+(m.length===1?" is":" are")+" failing. Check Settings.":"Nothing is waiting on you."}),l=Ye(new Map);Qe(()=>l.clear());const c=async h=>{const m=h.row.row_id,p=await ze({handled:{[m]:h.handled?null:new Date().toISOString()}});p?l.set(m,p):l.delete(m)};return(h,m)=>(r(),q(Ae,{id:"your-move",title:"Your move",note:"Next step is yours",tip:"Each row is something where the next step is yours. Ticket, PR and comment open in a new tab."},{head:te(()=>[O(Te,{modelValue:u(t),"onUpdate:modelValue":m[0]||(m[0]=p=>Zt(t)?t.value=p:null),options:a.value,label:"Show",class:"your-move__view"},null,8,["modelValue","options"])]),default:te(()=>[O(Ut,{floor:""},{default:te(()=>[o("table",qo,[m[1]||(m[1]=o("thead",null,[o("tr",null,[o("th",null,"Ticket"),o("th",null,"Next step"),o("th",null,"PR"),o("th",{class:"hide-narrow"},"Last comment"),o("th",{class:"table__actions-col"},[o("span",{class:"visually-hidden"},"Mark")])])],-1)),o("tbody",null,[(r(!0),i(V,null,j(s.value.rows,p=>(r(),i("tr",{key:p.row.row_id,class:W(["your-move__row",{"your-move__row--handled":p.handled}])},[O(et,{row:p.row},null,8,["row"]),o("td",null,[o("span",Yo,[o("span",Lo,_(p.row.next),1),p.age.text?(r(),i("span",{key:0,class:W(["age",{"age--old":p.age.old}]),"data-tip":"Waiting since "+u(Je)(p.row.activity_at)},_(p.age.text),11,Ho)):T("",!0),p.row.why?(r(),i("button",{key:1,type:"button",class:"hint-icon","aria-label":"Why this is your move","data-tip":p.row.why}," ? ",8,Xo)):T("",!0),p.handled?(r(),i("span",{key:2,class:"handled-chip","data-tip":"Marked "+u(ne)(u(k).marks.handled[p.row.row_id]||"")+". Comes back in 7 days if it is still open, or sooner when something new happens."},"handled",8,Ko)):T("",!0)]),l.get(p.row.row_id)?(r(),i("p",Go,_(l.get(p.row.row_id)),1)):T("",!0)]),O(Pt,{row:p.row},null,8,["row"]),o("td",{class:W(["hide-narrow",{"your-move__quote":p.row.comment}])},[p.row.comment?(r(),q(se,{key:0,text:p.row.comment,url:p.row.comment_url},null,8,["text","url"])):T("",!0)],2),o("td",Jo,[O(de,{class:"table__ai",query:{ws:u(k).workspace.id,kind:"row",ref:p.row.row_id}},null,8,["query"]),o("button",{type:"button",class:W(["your-move__mark",{"your-move__mark--undo":p.handled}]),"aria-label":(p.handled?"Move back to open: ":"Mark as handled: ")+p.row.key,onClick:X(y=>c(p),["stop"])},_(p.handled?"Undo":"Handled"),11,Zo)])],2))),128))])])]),_:1}),s.value.rows.length?T("",!0):(r(),i("p",Qo,_(n.value),1)),u(t)==="open"?(r(),q(jo,{key:1})):T("",!0)]),_:1}))}}),ta=F(ea,[["__scopeId","data-v-e3e1502a"]]),sa=I(!1),na=()=>sa,oa={class:"todos__add"},aa={key:0,class:"alert alert--inline",role:"alert"},la={class:"todos__list"},ra=["aria-checked","aria-label","onClick"],ia={class:"todos__text"},ca={class:"todos__meta"},da=["onClick"],ua={key:0,class:"empty todos__empty"},pa=N({__name:"Todos",setup(e){const t=na(),s=I(""),a=w(()=>(k.todos||[]).filter(d=>!d.done)),n=w(()=>(k.todos||[]).filter(d=>d.done)),l=w(()=>new RegExp(k.workspace.key_pattern||"(?!)","g")),c=d=>{const v=[];let f=0;const B=d.text;for(const z of B.matchAll(l.value))z.index>f&&v.push({text:B.slice(f,z.index)}),v.push({text:z[0],url:z[0]===d.key?d.ticket_url:""}),f=z.index+z[0].length;return f<B.length&&v.push({text:B.slice(f)}),v},h=w(()=>(ce.value,(t.value?a.value.concat(n.value):a.value).map(d=>({todo:d,parts:c(d),meta:(d.by&&d.by!=="you"?d.by+" · ":"")+ne(d.created)})))),m=I("");Qe(()=>{m.value=""});const p=async d=>(m.value=await on(d),!m.value),y=async()=>{const d=s.value.trim();d&&await p({action:"add",text:d})&&s.value.trim()===d&&(s.value="")},A=d=>p({action:d.done?"undo":"done",id:d.id}),E=d=>p({action:"delete",id:d.id}),R=w({get:()=>t.value?"done":"open",set:d=>{t.value=d==="done"}}),C=w(()=>[{id:"open",label:"Open",count:a.value.length},{id:"done",label:"Done",count:n.value.length}]);return(d,v)=>(r(),q(Ae,{id:"todo",title:"To do",note:"Your own notes",tip:'Your personal to-dos, kept on this machine. Add here, or ask Claude: "add to my to-do: …". A ticket key like PROJ-123 in the text becomes a link.'},{head:te(()=>[O(Te,{modelValue:R.value,"onUpdate:modelValue":v[0]||(v[0]=f=>R.value=f),options:C.value,label:"Show",class:"todos__view"},null,8,["modelValue","options"])]),default:te(()=>[o("div",oa,[Le(o("input",{"onUpdate:modelValue":v[1]||(v[1]=f=>s.value=f),class:"input todos__input",type:"text",placeholder:"Add a to-do and press Enter","aria-label":"New to-do",autocomplete:"off",onKeydown:je(y,["enter"])},null,544),[[He,s.value]]),o("button",{type:"button",class:"btn btn--tonal",onClick:y},"Add")]),m.value?(r(),i("p",aa,_(m.value),1)):T("",!0),o("ul",la,[(r(!0),i(V,null,j(h.value,({todo:f,parts:B,meta:z})=>(r(),i("li",{key:f.id,class:W(["todos__item",{"todos__item--done":f.done}])},[o("button",{type:"button",class:W(["todos__check",{"todos__check--on":f.done}]),role:"checkbox","aria-checked":!!f.done,"aria-label":f.done?"Mark not done":"Mark done",onClick:L=>A(f)},null,10,ra),o("span",ia,[(r(!0),i(V,null,j(B,(L,J)=>(r(),i(V,{key:J},[L.url!==void 0?(r(),q(se,{key:0,text:L.text,url:L.url},null,8,["text","url"])):(r(),i(V,{key:1},[U(_(L.text),1)],64))],64))),128))]),o("span",ca,_(z),1),o("button",{type:"button",class:"todos__delete","aria-label":"Delete to-do",onClick:L=>E(f)}," × ",8,da)],2))),128)),h.value.length?T("",!0):(r(),i("span",ua,_(u(t)?"Nothing here yet.":"Nothing to do. Add one above, or ask Claude."),1))])]),_:1}))}}),_a=F(pa,[["__scopeId","data-v-6f034e1e"]]),ha={key:0,class:"empty"},fa={class:"table waiting__table"},ma=["data-tip"],va={class:"hide-narrow"},ka={key:0,class:"hint-row"},ya=["data-tip"],ga={class:"table__actions"},wa=N({__name:"Waiting",setup(e){const t=w(()=>(ce.value,xs(k.turn).map(s=>({row:s,since:s.status_since?Ze(s.status_since):null}))));return(s,a)=>(r(),q(Ae,{id:"waiting",title:"Waiting on others",note:"Someone else holds the next step",tip:"Tickets you touched in the last 30 days where someone else holds the next step: a reviewer, QA, or a new owner."},{default:te(()=>[t.value.length?(r(),q(Ut,{key:1},{default:te(()=>[o("table",fa,[a[0]||(a[0]=o("thead",null,[o("tr",null,[o("th",{class:"waiting__col waiting__col--ticket"},"Ticket"),o("th",{class:"waiting__col waiting__col--status"},"Status"),o("th",{class:"waiting__col waiting__col--with"},"With"),o("th",{class:"hide-narrow"},"Waiting for"),o("th",{class:"waiting__col waiting__col--pr"},"PR"),o("th",{class:"table__actions-col waiting__col waiting__col--actions"},[o("span",{class:"visually-hidden"},"Copy for AI")])])],-1)),o("tbody",null,[(r(!0),i(V,null,j(t.value,({row:n,since:l})=>(r(),i("tr",{key:n.row_id},[O(et,{row:n},null,8,["row"]),o("td",null,[o("span",{class:W(["pill",u(fe)("pill",u(Ct)(n.status_group))])},_(n.status),3)]),o("td",null,[U(_(n.whose)+" ",1),l?(r(),i("span",{key:0,class:W(["age",{"age--old":l.hours>=72}]),"data-tip":n.status+" since "+u(Je)(n.status_since)},_(l.text),11,ma)):T("",!0)]),o("td",va,[n.hold_url?(r(),i("span",ka,[O(se,{text:n.next,url:n.hold_url,class:"waiting__reason"},null,8,["text","url"]),o("button",{type:"button",class:"hint-icon","aria-label":"Full reason","data-tip":n.hold_full||n.next}," ? ",8,ya)])):(r(),i(V,{key:1},[U(_(n.next),1)],64))]),O(Pt,{row:n},null,8,["row"]),o("td",ga,[O(de,{class:"table__ai",query:{ws:u(k).workspace.id,kind:"row",ref:n.row_id}},null,8,["query"])])]))),128))])])]),_:1})):(r(),i("p",ha,"Nothing is waiting on someone else."))]),_:1}))}}),ba=F(wa,[["__scopeId","data-v-a632447c"]]),xa=(e,t)=>e.seen instanceof Set?e.seen.has(t):e.seen.includes(t),Ft=(e,t)=>new Date(e.at).getTime()>(t.base||0)&&!xa(t,e.id),$a=(e,t)=>{const s=String(t||"").toLowerCase().split(/\s+/).filter(Boolean);if(!s.length)return!0;const a=[e.ref,e.key,e.title,e.kind,e.who,e.text,e.body].join(" ").toLowerCase();return s.every(n=>a.includes(n))},Ca=e=>e.key||e.ref||e.id,Sa=(e,t)=>{const s=new Map;for(const a of e){const n=Ca(a),l=s.get(n)||{id:n,items:[],newest:a.at,unread:0,title:""};s.set(n,l),l.items.push(a),l.title||(l.title=a.title||""),a.at>l.newest&&(l.newest=a.at),Ft(a,t)&&(l.unread+=1)}return[...s.values()].sort((a,n)=>String(n.newest).localeCompare(String(a.newest)))},Ta=e=>new Set(e.filter(t=>t.group==="you"&&!t.handled).map(t=>t.key)),Ma=(e,t=2)=>{const s=[...new Set(e.map(n=>n.kind))],a=s.length-t;return s.slice(0,t).join(" · ")+(a>0?` +${a}`:"")},Aa=e=>/fail|changes/.test(e)?"bad":/approved|merged|passed/.test(e)?"ok":"",Ra=e=>/comment|approved|changes|mentioned|review requested/.test(e.kind)&&String(e.body||"").trim()!=="",zt=e=>e.ref!==e.key&&(e.key||e.title)||"",Ea=e=>[zt(e),e.text===e.title?"":e.text].filter(Boolean).join(" · "),Oa=e=>e.type==="github"&&e.label==="GitHub"?"GH":Wt(e.label),Da={class:"feed-item__body"},Ia={class:"feed-item__top"},Ba={key:0,class:"feed-item__who"},Va={key:1,class:"at-you"},Wa={class:"feed-item__right"},Na={class:"timestamp feed-item__when"},ja=["aria-label","aria-expanded"],Pa={key:0,class:"feed-item__text"},Ua={class:"feed-item__full"},Fa={class:"feed-item__actions"},za={key:1,class:"feed-item__text"},qa=N({__name:"FeedItem",props:{item:{},source:{},fresh:{type:Boolean},lit:{type:Boolean},expanded:{type:Boolean}},emits:["open","toggle","seen"],setup(e,{emit:t}){const s=e,a=t,n=w(()=>Oa(s.source)),l=w(()=>zt(s.item)),c=w(()=>Ea(s.item)),h=w(()=>Aa(s.item.kind)),m=w(()=>h.value||(s.source.type==="github"?"":"jira")),p=w(()=>Ra(s.item)),y=w(()=>p.value&&s.expanded),A=w(()=>(ce.value,ne(s.item.at))),E=()=>{s.fresh&&a("seen",s.item.id)},R=()=>{a("toggle",s.item.id),E()};return(C,d)=>(r(),i("li",{class:W(["feed-item",{"feed-item--new":e.fresh,"feed-item--lit":e.lit,"feed-item--open":y.value}]),onClick:E},[o("span",{class:W(["feed-item__icon",u(fe)("feed-item__icon",m.value)])},_(n.value),3),o("div",Da,[o("div",Ia,[O(se,{text:e.item.ref,url:e.item.url},null,8,["text","url"]),o("span",{class:W(["feed-item__kind",{"feed-item__kind--bad":h.value==="bad"}])},_(e.item.kind),3),e.item.who?(r(),i("span",Ba,_(e.item.who),1)):T("",!0),e.item.mention?(r(),i("span",Va,"@you")):T("",!0),o("span",Wa,[o("span",Na,_(A.value),1),p.value?(r(),i("button",{key:0,type:"button",class:"feed-item__toggle","aria-label":y.value?"Collapse":"Expand","aria-expanded":y.value,onClick:X(R,["stop"])},null,8,ja)):T("",!0)])]),y.value?(r(),i(V,{key:0},[l.value?(r(),i("span",Pa,_(l.value),1)):T("",!0),o("div",Ua,_(e.item.body),1),o("div",Fa,[o("button",{type:"button",class:"btn btn--tonal",onClick:d[0]||(d[0]=X(v=>a("open",e.item),["stop"]))}," Open formatted "),O(de,{query:{ws:u(k).workspace.id,kind:"feed",ref:e.item.id}},null,8,["query"]),O(se,{text:"Open in "+e.source.label,url:e.item.url,class:"btn"},null,8,["text","url"])])],64)):c.value?(r(),i("span",za,_(c.value),1)):T("",!0)])],2))}}),ft=F(qa,[["__scopeId","data-v-27e556e1"]]),Ya=Ye({source:"all",view:Me("wt-feed-view","ticket",{writeDefaults:!1}),query:"",openGroups:new Set}),La=()=>Ya,Ha={key:0,class:"feed__new-count"},Xa={class:"feed__bar"},Ka={key:0,class:"alert alert--inline",role:"alert"},Ga={class:"feed__search"},Ja={class:"feed__search-count"},Za={key:1,class:"empty"},Qa={key:2,class:"feed__list"},el=["title","onClick"],tl=["aria-expanded","aria-label"],sl={class:"feed__group-text"},nl={class:"feed__group-key"},ol={key:0,class:"feed__group-sum"},al={class:"feed__group-meta"},ll={class:"feed__group-kinds"},rl={class:"timestamp feed__group-when"},il={key:0,class:"feed__children"},cl=N({__name:"Feed",emits:["open"],setup(e,{emit:t}){const s=t,a=La(),n=[{id:"ticket",label:"By ticket"},{id:"flat",label:"Flat"}],l=w(()=>k.workspace.connectors||[]),c=w(()=>Object.fromEntries(l.value.map(x=>[x.id,x]))),h=x=>c.value[x]||{id:x,type:x,label:x},m=w(()=>l.value.length>1?[{id:"all",label:"All"}].concat(l.value.map(x=>({id:x.id,label:x.label}))):[]),p=w({get:()=>m.value.some(x=>x.id===a.source)?a.source:"all",set:x=>{a.source=x}}),y=w(()=>({base:k.marks.base,seen:new Set(k.marks.seen)})),A=x=>Ft(x,y.value),E=w(()=>k.feed.filter(A).length),R=w(()=>k.feed.filter(x=>p.value==="all"||x.source===p.value)),C=w(()=>R.value.filter(x=>$a(x,a.query)).slice(0,200)),d=w(()=>new Map(k.turn.map(x=>[x.key,x]))),v=w(()=>Ta(k.turn)),f=w(()=>(ce.value,Sa(C.value,y.value).map(x=>({...x,row:d.value.get(x.id),move:v.value.has(x.id),when:ne(x.newest),kinds:Ma(x.items),open:a.openGroups.has(x.id)||!!a.query})))),B=(x,Y)=>{x.has(Y)?x.delete(Y):x.add(Y)},z=x=>B(a.openGroups,x.id),L=x=>B(g.open,x),J=x=>ze({seen:[x]}).then(ae),ue=I("");Qe(()=>{ue.value=""});const Q=async()=>{ue.value=await ze({seen:k.feed.map(x=>x.id),base:Date.now()})};return(x,Y)=>(r(),q(Ae,{id:"feed",title:"What changed",note:"Newest first, every source",tip:"Newest first: comments, reviews, merges and failed checks on your PRs, and ticket changes. Each item opens where it happened.",class:"feed",column:""},{head:te(()=>[E.value?(r(),i("span",Ha,_(E.value)+" new",1)):T("",!0)]),default:te(()=>[o("div",Xa,[m.value.length?(r(),q(Te,{key:0,modelValue:p.value,"onUpdate:modelValue":Y[0]||(Y[0]=M=>p.value=M),options:m.value,label:"Feed source"},null,8,["modelValue","options"])):T("",!0),O(Te,{modelValue:u(a).view,"onUpdate:modelValue":Y[1]||(Y[1]=M=>u(a).view=M),options:n,label:"Feed view"},null,8,["modelValue"])]),ue.value?(r(),i("p",Ka,_(ue.value),1)):T("",!0),o("div",Ga,[Le(o("input",{"onUpdate:modelValue":Y[2]||(Y[2]=M=>u(a).query=M),class:"input feed__search-input",type:"search",placeholder:"Search the feed: key, author, words…","aria-label":"Search the feed",autocomplete:"off"},null,512),[[He,u(a).query,void 0,{trim:!0}]]),o("span",Ja,_(u(a).query?C.value.length+" of "+R.value.length:""),1),o("button",{type:"button",class:"feed__mark-read",onClick:Q},"Mark all read")]),u(k).feed.length?(r(),i("ol",Qa,[u(a).view==="ticket"?(r(!0),i(V,{key:0},j(f.value,M=>(r(),i("li",{key:M.id,class:W(["feed__group",{"feed__group--new":M.unread,"feed__group--open":M.open,"feed__group--move":M.move}])},[o("div",{class:"feed__group-head",title:M.move?"Your move on this ticket":void 0,onClick:Z=>z(M)},[o("button",{type:"button",class:"feed__group-caret","aria-expanded":M.open,"aria-label":"Collapse or expand "+M.id}," › ",8,tl),o("div",sl,[o("div",nl,[O(se,{text:M.id,url:M.row?M.row.ticket_url:M.items[0].url,class:"ticket-key feed__group-ticket"},null,8,["text","url"]),M.row||M.title?(r(),i("span",ol,_(M.row?M.row.summary:M.title),1)):T("",!0)]),o("div",al,[o("span",{class:W(["feed__group-count",{"feed__group-count--new":M.unread}])},_(M.unread?M.unread+" new":M.items.length),3),o("span",ll,_(M.kinds),1),o("span",rl,_(M.when),1)])]),O(de,{class:"feed__group-copy",query:{ws:u(k).workspace.id,kind:"thread",ref:M.id}},null,8,["query"])],8,el),M.open?(r(),i("ul",il,[(r(!0),i(V,null,j(M.items,Z=>(r(),q(ft,{key:Z.id,item:Z,source:h(Z.source),fresh:A(Z),lit:!!M.unread,expanded:u(g).open.has(Z.id),onOpen:Y[3]||(Y[3]=Re=>s("open",Re)),onToggle:L,onSeen:J},null,8,["item","source","fresh","lit","expanded"]))),128))])):T("",!0)],2))),128)):(r(!0),i(V,{key:1},j(C.value,M=>(r(),q(ft,{key:M.id,item:M,source:h(M.source),fresh:A(M),expanded:u(g).open.has(M.id),onOpen:Y[4]||(Y[4]=Z=>s("open",Z)),onToggle:L,onSeen:J},null,8,["item","source","fresh","expanded"]))),128))])):(r(),i("p",Za," No changes yet. New comments, reviews and status changes show up here. "))]),_:1}))}}),dl=F(cl,[["__scopeId","data-v-d92a7c74"]]),ul={id:"first-run",class:"island first-run","aria-labelledby":"first-run-title"},pl={class:"first-run__text"},_l={class:"first-run__actions"},hl=["aria-disabled"],fl=N({__name:"FirstRun",setup(e){const t=I(!1),s=()=>Js("connectors"),a=()=>{t.value||(t.value=!0,ln().finally(()=>{t.value=!1}))};return(n,l)=>(r(),i("section",ul,[l[1]||(l[1]=o("h2",{id:"first-run-title",class:"first-run__title"}," Whose Turn watches your tickets and pull requests and shows what is waiting on you. ",-1)),o("p",pl,[o("b",null,_(u(k).workspace.name),1),l[0]||(l[0]=U(" has no sources yet, so there is nothing to watch. Connect a repository or a tracker, or look around a demo workspace with invented data first. ",-1))]),o("div",_l,[o("button",{type:"button",class:"btn btn--tonal",onClick:s},"Add a source"),o("button",{type:"button",class:"btn","aria-disabled":t.value,onClick:a}," Try a demo workspace ",8,hl)]),l[2]||(l[2]=o("p",{class:"first-run__text first-run__ai"},' Or ask Claude: "add my GitHub repository to Whose Turn." ',-1))]))}}),ml=F(fl,[["__scopeId","data-v-ef746675"]]),vl=new RegExp("(`[^`]{1,500}`)|(\\*\\*[^*]{1,500}\\*\\*)|(\\[[^\\]]{1,300}\\]\\(https?:\\/\\/[^)\\s]{1,2000}\\))|(https?:\\/\\/[^\\s<>()]{1,2000})|(@[A-Za-z0-9-]+(?: [A-Z][a-z]+)?)|((?<![A-Za-z0-9])\\*[^*\\s][^*]{0,500}\\*(?![A-Za-z0-9])|(?<![A-Za-z0-9])_[^_\\s][^_]{0,500}_(?![A-Za-z0-9]))","g"),mt=(e,t)=>{const s=Ge(t);return s?{type:"link",text:e,href:s}:{type:"text",text:e}},kl=e=>{const t=e[0];if(e[1])return{type:"code",text:t.slice(1,-1)};if(e[2])return{type:"strong",text:t.slice(2,-2)};if(e[3]){const s=t.indexOf("](");return mt(t.slice(1,s),t.slice(s+2,-1))}return e[4]?mt(t,t):e[5]?{type:"mention",text:t}:{type:"em",text:t.slice(1,-1)}},ge=e=>{const t=new RegExp(vl),s=[];let a=0,n;for(;n=t.exec(e);)n.index>a&&s.push({type:"text",text:e.slice(a,n.index)}),s.push(kl(n)),a=n.index+n[0].length;return a<e.length&&s.push({type:"text",text:e.slice(a)}),s},yl=e=>{const t=String(e||"").replace(/<!--[\s\S]*?-->/g,"").split(`
|
|
2
|
-
`),s=[];let a=[],n=null;const l=()=>{a.length&&(s.push({type:"paragraph",lines:a.map(ge)}),a=[]),n=null};for(let c=0;c<t.length;c++){const h=t[c];if(/^\s*```/.test(h)){l();const A=[];for(c++;c<t.length&&!/^\s*```/.test(t[c]);c++)A.push(t[c]);s.push({type:"fence",text:A.join(`
|
|
3
|
-
`)});continue}const m=h.match(/^(#{1,6})\s+(.*)$/);if(m){l(),s.push({type:"heading",level:m[1].length<=2?4:5,inline:ge(m[2])});continue}const p=h.match(/^>\s?(.*)$/);if(p){l(),s.push({type:"quote",inline:ge(p[1])});continue}const y=h.match(/^\s*([-*+]|\d+[.)])\s+(.*)$/);if(y){const A=n;a.length&&(l(),n=A);const E=/\d/.test(y[1]),R=n,C=R&&R.ordered===E?R:{type:"list",ordered:E,items:[]};C!==R&&s.push(C),n=C,C.items.push(ge(y[2]));continue}if(!h.trim()){l();continue}n=null,a.push(h)}return l(),s},be=(e,t,s="")=>{const a=document.createElement(e);return a.textContent=t,s&&(a.className=s),a},gl=e=>{if(e.type==="text")return document.createTextNode(e.text);if(e.type==="link"){const t=be("a",e.text);return t.setAttribute("href",e.href),t.setAttribute("target","_blank"),t.setAttribute("rel","noopener"),t}return e.type==="mention"?be("span",e.text,"markdown-view__mention"):be(e.type,e.text)},qt=(e,t)=>{e.forEach(s=>t.append(gl(s)))},Ve=(e,t)=>{const s=document.createElement(e);return qt(t,s),s},wl=e=>{if(e.type==="fence"){const s=document.createElement("pre");return s.append(be("code",e.text)),s}if(e.type==="heading")return Ve(`h${e.level}`,e.inline);if(e.type==="quote")return Ve("blockquote",e.inline);if(e.type==="list"){const s=document.createElement(e.ordered?"ol":"ul");return e.items.forEach(a=>s.append(Ve("li",a))),s}const t=document.createElement("p");return e.lines.forEach((s,a)=>{a&&t.append(document.createElement("br")),qt(s,t)}),t},bl=e=>{const t=document.createDocumentFragment();return yl(e).forEach(s=>t.append(wl(s))),t},xl=N({__name:"MarkdownView",props:{source:{default:""}},setup(e){const t=e,s=I(null);return Qt(()=>{s.value&&s.value.replaceChildren(bl(t.source))}),(a,n)=>(r(),i("div",{ref_key:"host",ref:s,class:"markdown-view"},null,512))}}),$l=F(xl,[["__scopeId","data-v-dcf42e95"]]),Cl={class:"modal__card"},Sl={class:"modal__head"},Tl={id:"modal-title",class:"modal__title"},Ml=["href"],Al=N({__name:"CommentModal",setup(e){const t=w(()=>g.modal),s=w(()=>t.value?[t.value.ref,t.value.kind,t.value.who].filter(Boolean).join(" · "):""),a=w(()=>t.value?Ge(t.value.url):""),n=w(()=>{var h;const c=(k.workspace.connectors||[]).find(m=>{var p;return m.id===((p=t.value)==null?void 0:p.source)});return"Open in "+(c?c.label:(h=t.value)==null?void 0:h.source)}),l=()=>{g.modal=null};return(c,h)=>t.value?(r(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"modal-title",onClick:X(l,["self"])},[o("div",Cl,[o("div",Sl,[o("h3",Tl,_(s.value),1),a.value?(r(),i("a",{key:0,class:"btn",href:a.value,target:"_blank",rel:"noopener"},_(n.value),9,Ml)):T("",!0),o("button",{type:"button",class:"btn btn--tonal",onClick:l},"Close")]),O($l,{source:t.value.body||t.value.text||""},null,8,["source"])])])):T("",!0)}}),Rl={class:"modal__card feedback-sheet__card"},El={class:"modal__head"},Ol={class:"modal__meta"},Dl=["aria-disabled"],Il={key:0,class:"alert"},Bl={class:"sheet-form"},Vl=["placeholder","onKeydown"],Wl={key:0,class:"feedback-sheet__note"},Nl={key:1,class:"feedback-sheet__note"},jl={key:2,class:"empty"},Pl=["href"],Ul={key:3,class:"feedback-sheet__reports"},Fl=["href"],zl={class:"feedback-sheet__report-title"},ql={class:"timestamp"},Yl=N({__name:"FeedbackSheet",setup(e){const t=I(""),s=I(null),a=I(!1),n=I([]),l=I(null),c=I(""),h=I(!0),m=Ke("",1600),p=I([]),y=d=>"/report?id="+encodeURIComponent(d),A=()=>he("/api/feedback").then(d=>{h.value=d.configured,c.value=d.configured?"Linear · "+d.project:"not connected",p.value=d.reports||[]},()=>{n.value=[xe+"; past reports cannot be loaded."]}),E=()=>{g.feedback=!1},R=()=>{const d=t.value.trim();if(!d){n.value=["Write at least one line."];return}n.value=[],navigator.clipboard.writeText(d).then(()=>{m.value="sent"},()=>{m.value="failed"})},C=()=>{if(!h.value||a.value)return;const d=t.value.trim();if(!d){n.value=["Write at least one line."];return}a.value=!0,le("/api/feedback",{text:d,from:"the window",ws:k.workspace.id,kind_ref:{kind:"workspace",ref:k.workspace.id}}).then(v=>{if(a.value=!1,v.error){n.value=St(v.error);return}l.value=v.issue||null,t.value="",p.value=v.reports||p.value}).catch(v=>{a.value=!1,n.value=[oe(v,{refused:"Not sent",unreachable:Pe})]})};return K(()=>g.feedback,d=>{d&&(n.value=[],l.value=null,A().then(()=>kt(()=>{var v;return(v=s.value)==null?void 0:v.focus()})))}),(d,v)=>u(g).feedback?(r(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"feedback-title",onClick:X(E,["self"])},[o("div",Rl,[o("div",El,[v[1]||(v[1]=o("h3",{id:"feedback-title",class:"modal__title"},"To the Whose Turn backlog",-1)),o("span",Ol,_(c.value),1),o("button",{type:"button",class:"btn btn--tonal","aria-disabled":!h.value||a.value,onClick:C},_(a.value?"Composing…":"Send"),9,Dl),o("button",{type:"button",class:"btn",onClick:E},"Close")]),n.value.length?(r(),i("div",Il,_(n.value.join(" · ")),1)):T("",!0),o("div",Bl,[Le(o("textarea",{ref_key:"field",ref:s,"onUpdate:modelValue":v[0]||(v[0]=f=>t.value=f),class:"input feedback-sheet__text",rows:"5",placeholder:"Describe the problem or the wish in your own words."+(h.value?" Cmd+Enter sends.":""),autocomplete:"off",onKeydown:[je(X(C,["meta","prevent"]),["enter"]),je(X(C,["ctrl","prevent"]),["enter"])]},null,40,Vl),[[He,t.value]]),h.value?(r(),i("p",Nl," An assistant turns it into a proper issue (title, kind, description) and files it in the Whose Turn backlog with this workspace's summary attached. ")):(r(),i("p",Wl,[v[2]||(v[2]=U(" The maintainer's tracker is not connected on this machine, so nothing can be sent from here. Copy your remark and pass it on yourself. ",-1)),o("button",{type:"button",class:"btn",onClick:R},_(u(m)==="sent"?"Copied":u(m)==="failed"?"Could not copy":"Copy remark"),1)])),l.value?(r(),i("p",jl,[v[3]||(v[3]=U(" Filed as ",-1)),o("a",{href:y(l.value.identifier),target:"_blank",rel:"noopener"},_(l.value.identifier),9,Pl),U(" · "+_(l.value.title),1)])):T("",!0),p.value.length?(r(),i("div",Ul,[v[4]||(v[4]=o("h4",{class:"sheet-form__heading"},"Your reports",-1)),(r(!0),i(V,null,j(p.value,f=>(r(),i("div",{key:f.id,class:"feedback-sheet__report"},[o("a",{href:y(f.id),target:"_blank",rel:"noopener"},_(f.id),9,Fl),o("span",zl,_(f.title),1),o("span",{class:W(["pill",u(fe)("pill",u($s)(f.outcome))])},_(f.state||"filed"),3),o("span",ql,_(u(ne)(f.changed_at||f.filed_at)),1)]))),128))])):T("",!0)])])])):T("",!0)}}),Ll=F(Yl,[["__scopeId","data-v-ec34adcc"]]),Hl={class:"field-row"},Xl={class:"field-row__label"},Kl={key:0,class:"field-row__check"},Gl=["checked"],Jl={key:2,class:"field-row__inline"},Zl=["value"],Ql=["value"],er=["value"],tr=["placeholder","value"],sr={key:5,class:"field-row__hint"},nr=N({__name:"FieldRow",props:Ne({spec:{},placeholder:{default:""},hint:{default:""}},{modelValue:{type:[String,Array,Boolean]},modelModifiers:{}}),emits:Ne(["secret"],["update:modelValue"]),setup(e,{emit:t}){const s=e,a=t,n=vt(e,"modelValue"),l=/^#[0-9a-f]{6}$/i,c=()=>String(n.value??""),h=()=>n.value||[],m=R=>{n.value=R},p=()=>n.value??s.spec.default??!1,y=R=>{n.value=R===(s.spec.default??!1)?void 0:R},A=R=>R.target.value,E=R=>m(R.split(",").map(C=>C.trim()).filter(Boolean));return(R,C)=>(r(),i("div",Hl,[o("label",Xl,_(e.spec.label)+_(e.spec.required?" *":""),1),e.spec.boolean?(r(),i("label",Kl,[o("input",{type:"checkbox",checked:p(),onChange:C[0]||(C[0]=d=>y(d.target.checked))},null,40,Gl),U(" "+_(e.hint||e.spec.hint),1)])):e.spec.secret?(r(),i("input",{key:1,class:"input",type:"password",placeholder:"paste to replace",autocomplete:"off",onInput:C[1]||(C[1]=d=>a("secret",e.spec.key,A(d)))},null,32)):e.spec.colour?(r(),i("div",Jl,[o("input",{class:"input field-row__colour",type:"color",value:l.test(c())?c():"#888888",onInput:C[2]||(C[2]=d=>m(A(d)))},null,40,Zl),o("input",{class:"input",type:"text",placeholder:"#rrggbb",value:c(),onInput:C[3]||(C[3]=d=>m(A(d)))},null,40,Ql),o("button",{type:"button",class:"btn",onClick:C[4]||(C[4]=d=>m(""))},"None")])):e.spec.list?(r(),i("input",{key:3,class:"input",type:"text",value:h().join(", "),onInput:C[5]||(C[5]=d=>E(A(d)))},null,40,er)):(r(),i("input",{key:4,class:"input",type:"text",placeholder:e.placeholder,value:c(),onInput:C[6]||(C[6]=d=>m(A(d)))},null,40,tr)),!e.spec.boolean&&(e.hint||e.spec.hint)?(r(),i("small",sr,_(e.hint||e.spec.hint),1)):T("",!0)]))}}),ie=F(nr,[["__scopeId","data-v-2ba0ba86"]]),or={class:"modal__card settings-sheet__card"},ar={class:"modal__head"},lr={class:"modal__meta"},rr={key:0,class:"settings-sheet__dirty"},ir={key:0,class:"settings-sheet__confirm",role:"alertdialog"},cr={class:"settings-sheet__confirm-text"},dr={key:1,class:"alert settings-sheet__alert",role:"alert"},ur={key:2,class:"alert settings-sheet__alert"},pr={key:3,class:"settings-sheet__body"},_r={class:"settings-sheet__list"},hr=["aria-current","onClick"],fr={class:"sheet-form"},mr={key:0,class:"empty"},vr={ref:"connectors-head",class:"sheet-form__heading"},kr={class:"settings-sheet__connector-head"},yr={class:"settings-sheet__connector-name"},gr=["onClick"],wr={class:"settings-sheet__actions"},br=["onClick"],xr={key:0,class:"settings-sheet__rules"},$r={class:"settings-sheet__rule-when"},Cr={class:"settings-sheet__rule-then"},Sr={key:1,class:"empty"},Tr=N({__name:"SettingsSheet",setup(e){const t=I([]),s=I(0),a=I([]),n=I({workspace:[],theme:[],rules:[],statuses:[]}),l=I(""),c=I([]),h=I({}),m=es("connectors-head"),p=new Map,y=I(0),A=I(""),E=I(null),R=I(""),C=w(()=>g.settings&&!R.value&&(y.value>0||dt(t.value)!==A.value)),d={key:"id",label:"Entry id"},v={key:"label",label:"Label"},f=w(()=>t.value[s.value]),B=w(()=>(f.value&&h.value[f.value.id]||[]).map(Ys)),z=b=>a.value.find(S=>S.type===b.type)||{type:b.type,label:b.type,fields:[]},L=w(()=>t.value.map((b,S)=>({uid:ct(b),label:b.name||b.id||"Workspace "+(S+1)}))),J=w(()=>{var b;return(((b=f.value)==null?void 0:b.connectors)||[]).map(S=>({entry:S,uid:ct(S),spec:z(S)}))}),ue=b=>{var $;const S=($=k.workspaces.find(P=>P.id===b))==null?void 0:$.theme_seed;return S?`empty: ${S}, derived from the workspace id`:"empty derives one from the workspace id"},Q=(b,S)=>b[S],x=(b,S,$)=>{p.set(b,Object.assign(p.get(b)||{},{[S]:$})),y.value+=1},Y=()=>{t.value.push(rt({id:"new-"+(t.value.length+1),name:"New workspace"})),s.value=t.value.length-1},M=()=>{t.value.splice(s.value,1),s.value=Math.max(0,s.value-1)},Z=()=>{f.value&&(E.value={text:`Delete ${f.value.name||f.value.id}? Its to-dos and marks stay on disk.`,yes:"Delete",no:"Keep",run:M})},Re=b=>{var S;return(S=f.value)==null?void 0:S.connectors.push({type:b.type})},Yt=b=>{f.value&&(p.delete(f.value.connectors[b]),f.value.connectors.splice(b,1))},Lt=b=>{if(!f.value)return;const S=z(f.value.connectors[b]).label;E.value={text:`Remove the ${S} connector from ${f.value.name||f.value.id}?`,yes:"Remove",no:"Keep",run:()=>Yt(b)}},Ht=()=>he("/api/config").then(b=>{R.value="",t.value=(b.workspaces||[]).map(rt),a.value=b.connectors||[],n.value=b.fields,p.clear(),y.value=0,E.value=null,s.value=Math.max(0,t.value.findIndex(S=>S.id===k.workspace.id)),l.value=b.config_file||"",c.value=b.problems||[],h.value=b.tables||{},A.value=dt(t.value)}).catch(()=>{R.value=xe+"; settings cannot be loaded."}).then(kt).then(()=>{var b;g.settingsAt==="connectors"&&((b=m.value)==null||b.scrollIntoView({block:"start"})),g.settingsAt=""}),Ee=()=>{g.settings=!1},tt=()=>{E.value={text:"Discard changes?",yes:"Discard",no:"Keep editing",run:Ee}},st=()=>C.value?tt():Ee(),nt=b=>{const S=E.value;E.value=null,b&&(S==null||S.run())},Xt=async()=>{try{const b=await le("/api/config",{workspaces:t.value.map(S=>Ws(S)),secrets:js(t.value,p)});if(b.error){c.value=St(b.error);return}Ee(),It()}catch(b){c.value=[oe(b,{unreachable:xe+"; your changes are kept, save again."})]}};return K(C,b=>{g.settingsDirty=b},{immediate:!0}),K(()=>g.settingsAsk,b=>{b&&(g.settingsAsk=!1,tt())}),K(()=>g.settings,b=>{b&&Ht()}),(b,S)=>u(g).settings?(r(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"settings-title",onClick:X(st,["self"])},[o("div",or,[o("div",ar,[S[2]||(S[2]=o("h3",{id:"settings-title",class:"modal__title"},"Workspaces",-1)),o("span",lr,_(l.value),1),C.value?(r(),i("span",rr,"Unsaved changes")):T("",!0),R.value?T("",!0):(r(),i("button",{key:1,type:"button",class:"btn btn--tonal",onClick:Xt},"Save")),o("button",{type:"button",class:"btn",onClick:st},"Close")]),E.value?(r(),i("div",ir,[o("span",cr,_(E.value.text),1),o("button",{type:"button",class:"btn btn--danger",onClick:S[0]||(S[0]=$=>nt(!0))},_(E.value.yes),1),o("button",{type:"button",class:"btn",onClick:S[1]||(S[1]=$=>nt(!1))},_(E.value.no),1)])):T("",!0),R.value?(r(),i("div",dr,_(R.value),1)):c.value.length?(r(),i("div",ur,_(c.value.join(" · ")),1)):T("",!0),R.value?T("",!0):(r(),i("div",pr,[o("ul",_r,[(r(!0),i(V,null,j(L.value,($,P)=>(r(),i("li",{key:$.uid},[o("button",{type:"button",class:"settings-sheet__workspace","aria-current":P===s.value,onClick:ve=>s.value=P},_($.label),9,hr)]))),128)),o("li",null,[o("button",{type:"button",class:"settings-sheet__workspace settings-sheet__workspace--add",onClick:Y}," + Add workspace ")])]),o("div",fr,[f.value?(r(),i(V,{key:1},[S[3]||(S[3]=o("h4",{class:"sheet-form__heading"},"Workspace",-1)),(r(!0),i(V,null,j(n.value.workspace,$=>(r(),q(ie,{key:$.key,spec:$,"model-value":Q(f.value,$.key),"onUpdate:modelValue":P=>u(re)(f.value,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),S[4]||(S[4]=o("h4",{class:"sheet-form__heading"},"Theme",-1)),(r(!0),i(V,null,j(n.value.theme,$=>{var P;return r(),q(ie,{key:$.key,spec:$,hint:$.key==="seed"&&!((P=f.value.theme)!=null&&P.seed)?ue(f.value.id):"","model-value":Q(f.value.theme,$.key),"onUpdate:modelValue":ve=>u(re)(f.value.theme,$.key,ve)},null,8,["spec","hint","model-value","onUpdate:modelValue"])}),128)),o("h4",vr,"Connectors",512),(r(!0),i(V,null,j(J.value,({entry:$,spec:P,uid:ve},Kt)=>(r(),i("div",{key:ve,class:"settings-sheet__connector"},[o("div",kr,[o("strong",yr,_(P.label),1),o("button",{type:"button",class:"btn btn--danger",onClick:ee=>Lt(Kt)}," Remove ",8,gr)]),O(ie,{spec:d,placeholder:P.type,hint:"only needed when one type appears twice","model-value":Q($,"id"),"onUpdate:modelValue":ee=>u(re)($,"id",ee)},null,8,["placeholder","model-value","onUpdate:modelValue"]),O(ie,{spec:v,placeholder:P.label,hint:"shown in the feed filter","model-value":Q($,"label"),"onUpdate:modelValue":ee=>u(re)($,"label",ee)},null,8,["placeholder","model-value","onUpdate:modelValue"]),(r(!0),i(V,null,j(P.fields,ee=>(r(),q(ie,{key:ee.key,spec:ee,"model-value":Q($,ee.key),"onUpdate:modelValue":Oe=>u(re)($,ee.key,Oe),onSecret:(Oe,Gt)=>x($,Oe,Gt)},null,8,["spec","model-value","onUpdate:modelValue","onSecret"]))),128))]))),128)),o("div",wr,[(r(!0),i(V,null,j(a.value,$=>(r(),i("button",{key:$.type,type:"button",class:"btn",onClick:P=>Re($)}," + "+_($.label),9,br))),128))]),S[5]||(S[5]=o("h4",{class:"sheet-form__heading"},"Rules",-1)),(r(!0),i(V,null,j(n.value.rules,$=>(r(),q(ie,{key:$.key,spec:$,"model-value":Q(f.value.rules,$.key),"onUpdate:modelValue":P=>u(re)(f.value.rules,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),(r(!0),i(V,null,j(n.value.statuses,$=>(r(),q(ie,{key:$.key,spec:$,"model-value":Q(f.value.rules.statuses,$.key),"onUpdate:modelValue":P=>u(re)(f.value.rules.statuses,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),S[6]||(S[6]=o("h4",{class:"sheet-form__heading"},"Whose turn table",-1)),S[7]||(S[7]=o("p",{class:"settings-sheet__rules-note"}," The rows in force, first match wins. Read-only here: ask Claude to change rules.table through /api/config. ",-1)),B.value.length?(r(),i("ol",xr,[(r(!0),i(V,null,j(B.value,($,P)=>(r(),i("li",{key:P,class:"settings-sheet__rule"},[o("span",$r,_($.when),1),o("span",Cr,_($.then),1)]))),128))])):(r(),i("p",Sr,"Save the workspace to see its rows.")),o("div",{class:"settings-sheet__actions"},[o("button",{type:"button",class:"btn btn--danger",onClick:Z}," Delete workspace ")])],64)):(r(),i("p",mr,"No workspaces yet. Add one on the left."))])]))])])):T("",!0)}}),Mr=F(Tr,[["__scopeId","data-v-779d567d"]]),Ar={class:"app__shell"},Rr=["inert"],Er={id:"wt",class:"dashboard dashboard--fit"},Or=["inert"],Dr={class:"dashboard__main"},Ir=N({__name:"App",setup(e){const t=w(()=>!!k.workspace.id&&!k.workspace.connectors.length),s=w(()=>Dt.value!==null);return K(ut,Is,{flush:"post"}),hs("Escape",Zs,{target:document}),ts(()=>{Gs()}),(a,n)=>(r(),i("div",{class:"app",style:ss(u(ut))},[O(Cn,{inert:s.value,onSettings:n[0]||(n[0]=l=>u(g).settings=!0)},null,8,["inert"]),o("div",Ar,[O(Nn,{inert:s.value,onReport:n[1]||(n[1]=l=>u(g).feedback=!0)},null,8,["inert"]),u(pt).length?(r(),i("div",{key:0,class:"alert",inert:s.value},_(u(pt).join(" · ")),9,Rr)):T("",!0),o("div",Er,[n[3]||(n[3]=o("canvas",{id:"backdrop",class:"dashboard__backdrop","aria-hidden":"true"},null,-1)),O(uo,{inert:s.value},null,8,["inert"]),t.value?(r(),q(ml,{key:0,inert:s.value},null,8,["inert"])):(r(),i("div",{key:1,class:"dashboard__layout",inert:s.value},[o("div",Dr,[O(ta,{class:"dashboard__move"}),O(_a,{class:"dashboard__todo"}),O(ba,{class:"dashboard__waiting"})]),O(dl,{class:"dashboard__feed",onOpen:n[2]||(n[2]=l=>u(g).modal=l)})],8,Or)),O(Ll),O(Mr),O(Al)])])],4))}}),Br=F(Ir,[["__scopeId","data-v-f146b085"]]);ns(Br).mount("#app");nn();Bs();
|