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/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to whose-turn are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [1.0.0-alpha.4] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- The repository is public, and every release is published with npm provenance again. RMA-237.
|
|
9
|
+
- The self-update installs the new version with `--ignore-scripts`, as the plugin's first install does. RMA-236.
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
- The demo connector and the first-run "Try a demo workspace" button. A pre-made workspace belongs to the demo, which is separate work; the scenarios live on as test fixtures. A config that still names a `demo` source reports it as an unknown connector type. RMA-208.
|
|
13
|
+
|
|
14
|
+
## [1.0.0-alpha.3] - 2026-11-03
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Self-update of the installed package. Every ten minutes the server reads the npm dist-tags (`latest`, which every publish moves) and, when a different version is out, reports it as `update` in `/api/summary`, `/api/state` and the stream, and raises one desktop notification per version. `POST /api/update` installs it into the package's own location and restarts the server; the status bar shows "<version> is out · Update", then "Updating…", then "Updated to <version>". A checkout is never offered an update. New reasons `UPDATE_IN_PROGRESS` (409) and `UPDATE_UNAVAILABLE` (412). RMA-231.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- The window opens its event stream again. Since RMA-197 the store waited for a `CLOSED` status that VueUse never reports before the first open, so the window never subscribed to `/api/stream` and picked up new polls only on a reload.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- The release workflow publishes through npm trusted publishing (OIDC) instead of a stored token. RMA-235.
|
|
6
24
|
|
|
7
25
|
## [1.0.0-alpha.2] - 2026-11-03
|
|
8
26
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A local monitor that answers "what is waiting on me right now" across the places your work arrives: GitHub pull requests, Jira and Linear tickets, and your own to-dos. It polls each source about once a minute, decides once, on the server, whose turn every ticket is, shows the result in a window that updates live, sends a desktop notification when something becomes your move, and hands any item to an AI assistant as Markdown with its context. Its Claude Code plugin, `whose-turn@bladeforge`, makes a session start with "N your move · M unread · K to-do" already in context.
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
Everything runs on your machine: a Node 22 server on `127.0.0.1`, a window built with Vue, and no account anywhere but the sources you already use. Nothing that names an employer, an account or a team process lives in this repository; it all sits in one config file on the machine that owns it.
|
|
8
8
|
|
|
@@ -16,9 +16,9 @@ npm ci && npm run build
|
|
|
16
16
|
./install.sh && ./open.sh
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
`install.sh` makes the monitor start at login and restart if it stops (a launch agent on macOS, a systemd user unit on Linux; on Windows it prints the one `schtasks` line to run). `open.sh` opens the window as its own app window, or in a browser tab with `--browser`. The window listens on port 8765; `WHOSE_TURN_PORT` changes that.
|
|
19
|
+
`install.sh` makes the monitor start at login and restart if it stops (a launch agent on macOS, a systemd user unit on Linux; on Windows it prints the one `schtasks` line to run). `open.sh` opens the window as its own app window, or in a browser tab with `--browser`. The window listens on port 8765; `WHOSE_TURN_PORT` changes that. A clone updates with `git pull` and a rebuild; it is never offered the in-app update.
|
|
20
20
|
|
|
21
|
-
With Claude Code, the plugin installs and starts all of this for you, with no clone and no build (see [Claude Code](#claude-code)):
|
|
21
|
+
With Claude Code, the plugin installs and starts all of this for you, with no clone and no build (see [Claude Code](#claude-code)). That install updates itself: the server checks npm every ten minutes, tells you about a new version with a desktop notification and a chip in the status bar, and installs it and restarts on one click (`POST /api/update`).
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
24
|
claude plugin marketplace add aquivalabs/BladeForge
|
|
@@ -27,7 +27,7 @@ claude plugin install whose-turn@bladeforge
|
|
|
27
27
|
|
|
28
28
|
## First run
|
|
29
29
|
|
|
30
|
-
The window opens on one card: add a source through the settings sheet (the gear at the bottom of the rail)
|
|
30
|
+
The window opens on one card: add a source through the settings sheet (the gear at the bottom of the rail). A source needs an account and its filters; a token typed into a secret field goes to the platform's secret store, never into the config file. Claude can set a workspace up too, through the `whose_turn_config` MCP tool. From then on the server polls, the window follows, and the Claude Code plugin reads the same server.
|
|
31
31
|
|
|
32
32
|
## Workspaces and connectors
|
|
33
33
|
|
|
@@ -48,6 +48,7 @@ Nothing that names an employer, an account or a team process lives in this repos
|
|
|
48
48
|
| `port`, `poll_seconds`, `history_days` | the port, how often to poll, how far back the feed reaches |
|
|
49
49
|
| `stale_minutes`, `failing_polls` | health alerts: no data from any source for this many minutes (default 15), or one source failing this many polls in a row (default 3) |
|
|
50
50
|
| `feedback.workspace`, `team`, `project` | where the "to the backlog" button and the `whose_turn_feedback` MCP tool file bugs and improvements about the monitor: a Linear workspace slug, team key and project name; the key with issue-creation rights goes to the secret store under `feedback.token_service` (default `whose-turn-feedback`) and the workspace slug |
|
|
51
|
+
| `feedback.assign` | `false` leaves a filed issue unassigned for triage; by default it is assigned to the owner of the feedback key, so it shows up on that person's own dashboard |
|
|
51
52
|
| `feedback.composer` | the user's own agent that turns a remark into an issue: `{command, args}` of a CLI that reads the prompt on stdin and prints JSON (`{title, kind, body}`, or a wrapper with a `result` string that contains it); default `claude -p --output-format json --model claude-haiku-5-5`, so Claude Code on the user's subscription; `codex exec` or anything alike works the same way |
|
|
52
53
|
| `feedback.bug_words` | extra words, in any language, that make the fallback composer (used when the agent CLI gives no usable answer) call a remark a bug; matched as stems at a word start; the English defaults bug, broken, crash, error, fail, wrong always apply |
|
|
53
54
|
| `workspaces[].id`, `name` | a short id used in the API and on disk (lowercase letters, digits and hyphens, up to 40, starting with a letter or digit; a missing id becomes `ws<n>`) and the display name |
|
|
@@ -66,7 +67,6 @@ Nothing that names an employer, an account or a team process lives in this repos
|
|
|
66
67
|
| `workspaces[].links` | quick links shown in the header, as `"Label https://…"` strings or `{label, url}`; the repository, the Jira site and the Linear team come from the connectors by themselves |
|
|
67
68
|
| `workspaces[].rules` | the team's process, see Rules below; `statuses` groups, `automation_authors` and `question_words` must be lists of strings (statuses non-empty) or the save is refused |
|
|
68
69
|
| `…connectors.linear.workspace`, `team`, `project` | the Linear workspace slug (the part after linear.app/), and optionally one team key or one project name; the personal API key goes to the secret store under the workspace slug |
|
|
69
|
-
| `…connectors.demo.scenario` | `northwind` (a web team with Jira-like tickets and PRs), `atlas` (a mobile team with other status names) or `garden` (a side project, PRs only); invented data, no network |
|
|
70
70
|
| `workspaces[].theme.seed` | optional hex colour, `#rrggbb`; the window derives its light and dark Material 3 scheme from it (`lib/palette.mjs`); without it the stylesheet's blue stays |
|
|
71
71
|
| `workspaces[].theme.accent` | optional second hex that sets the tertiary hue; defaults to the seed's hue turned by 80 degrees |
|
|
72
72
|
|
|
@@ -98,7 +98,7 @@ On Windows, run `schtasks /Create /SC ONLOGON /TN whose-turn /TR "\"<path to nod
|
|
|
98
98
|
| `lib/api.mjs` | the local HTTP API, SSE stream and static files |
|
|
99
99
|
| `lib/window.mjs` | the app window's remembered size and the launcher behind "Open as app" |
|
|
100
100
|
| `lib/http-client.mjs`, `lib/secrets.mjs` (with `lib/platform/credential.ps1`), `lib/notify.mjs`, `lib/store.mjs`, `lib/errors.mjs`, `lib/text.mjs` | ETag fetch, secret lookup, desktop notification, files on disk, the error envelope, text helpers |
|
|
101
|
-
| `connectors/` | one module per source type
|
|
101
|
+
| `connectors/` | one module per source type |
|
|
102
102
|
| `web/` | the window: a Vue 3 app in TypeScript built with Vite (`web/src/App.vue`, primitives in `web/src/ui/`, one folder per island in `web/src/features/` with its components, composables and tests, shared composables in `web/src/composables/` (ADR 0006), each component's styles in a scoped BEM block (layout and shared primitives in `web/src/styles/base.css`, ADR 0005), pure logic in `web/src/lib/`, the dot-drawn animal in `web/src/backdrop.ts`, Tippy.js 6.3.7 and Popper 2.11.8 pinned in `web/public/vendor/`); `npm run build` writes `dist/`, which the server serves and git ignores (ADR 0001) |
|
|
103
103
|
| `install.sh` | bash installer that picks by `uname`: on macOS the launch agent `local.whose-turn` (log in `~/Library/Logs/whose-turn.log`); on Linux the user unit `~/.config/systemd/user/whose-turn.service` (log in `journalctl --user -u whose-turn`); both start at login and restart on a crash; on other systems it prints the Windows `schtasks` line. The node path is read with `command -v node` at install time |
|
|
104
104
|
| `open.sh` | opens the window as its own app window (Chrome on macOS; Chrome, Chromium or Edge on Linux) at the size it last had, or in a browser tab with `--browser` or when none of those is installed; bash, not zsh, so it runs on Linux too; Windows: open the URL yourself |
|
|
@@ -173,12 +173,13 @@ The monitor also alerts on its own polling: stale data, or a source that keeps f
|
|
|
173
173
|
|
|
174
174
|
## API, `127.0.0.1:<port>`
|
|
175
175
|
|
|
176
|
-
Requests must carry the local `Host`; a POST must be `application/json`. An error is a `google.rpc.Status` envelope: `{"error":{"code","status","message","details":[{"@type":"ErrorInfo","reason"}]}}`; branch on `reason`. The reasons: `NOT_FOUND` (unknown path, 404), `METHOD_NOT_ALLOWED` (405 with an `Allow: GET, POST` header, a method other than GET or POST), `NO_SUCH_WORKSPACE`, `NO_SUCH_TODO` (404), `NO_WORKSPACE` (412, a request that needs a workspace while none is configured; `/api/state` still answers 200 with an empty snapshot carrying `problems`, which the first-run card reads), `NOT_CONFIGURED` (412, feedback is not set up), `UPSTREAM_FAILED` (502, Linear failed or answered an error; the HTTP status is in the `ErrorInfo` metadata `upstream_status`), `RATE_LIMITED` (429), `UNKNOWN_ACTION`, `INVALID_BODY`, `INVALID_ARGUMENT` (an empty to-do or remark names the field `text` in a `BadRequest` detail), `INVALID_CONFIG` (400), `FORBIDDEN_ORIGIN` (403) and `INTERNAL` (500, a fixed message; the raw exception text appears only in a `DebugInfo` detail when the server runs with `WHOSE_TURN_DEBUG=1`, and always in the server log).
|
|
176
|
+
Requests must carry the local `Host`; a POST must be `application/json`. An error is a `google.rpc.Status` envelope: `{"error":{"code","status","message","details":[{"@type":"ErrorInfo","reason"}]}}`; branch on `reason`. The reasons: `NOT_FOUND` (unknown path, 404), `METHOD_NOT_ALLOWED` (405 with an `Allow: GET, POST` header, a method other than GET or POST), `NO_SUCH_WORKSPACE`, `NO_SUCH_TODO` (404), `NO_WORKSPACE` (412, a request that needs a workspace while none is configured; `/api/state` still answers 200 with an empty snapshot carrying `problems`, which the first-run card reads), `NOT_CONFIGURED` (412, feedback is not set up), `UPSTREAM_FAILED` (502, Linear failed or answered an error; the HTTP status is in the `ErrorInfo` metadata `upstream_status`), `RATE_LIMITED` (429), `UNKNOWN_ACTION`, `INVALID_BODY`, `INVALID_ARGUMENT` (an empty to-do or remark names the field `text` in a `BadRequest` detail), `INVALID_CONFIG` (400), `UPDATE_IN_PROGRESS` (409, an update is already installing), `UPDATE_UNAVAILABLE` (412, nothing to update: a checkout, or already the latest version), `FORBIDDEN_ORIGIN` (403) and `INTERNAL` (500, a fixed message; the raw exception text appears only in a `DebugInfo` detail when the server runs with `WHOSE_TURN_DEBUG=1`, and always in the server log).
|
|
177
177
|
|
|
178
178
|
- `GET /api/workspaces`: `[{id, name, key_pattern, connectors:[{id, type, label}], open_moves, theme}]`; `theme` is `{light, dark}`, each a map of CSS variable to value, or `null` without a seed. `/api/summary` leaves `theme` out.
|
|
179
179
|
- `GET /api/state?ws=<id>`: one workspace, default the first: `turn` (the rows, each with `group`, `rank`, `next`, `why`, `url`, `ticket_url`, `source`, `status_group` (`backlog`, `passed`, `code_review`, `qa`, `on_hold` or empty) and `handled`), `counts` (`mine`, `others`, `failing`, `done`: the four header tiles), `feed`, `marks`, `todos` (each with `ticket_url`), `errors`, `health`, `polled_at`, `me` (your name per source), `workspace`, plus `workspaces` and `problems`. With `&full=1` the merged raw collections come too: `tickets`, `comments`, `prs`, `activity`, `asks`, `events`.
|
|
180
180
|
- `GET /api/summary`: across all workspaces: `moves` (each with `ws`), `new_events`, `todos`, `polled_at`, `errors`, `health` (`{ok, alerts}`, alerts as text prefixed with the workspace name when there are several), and `workspaces` with the same per workspace, where `health` is `{ok, stale, fresh_at, failing:[{source, polls}], alerts:[{id, text}]}`.
|
|
181
|
-
- `GET /api/stream`: SSE. One `data:` event per poll per workspace, `{ws, version, polled_at, open_moves}`. The window reloads state when its workspace's `version` changes.
|
|
181
|
+
- `GET /api/stream`: SSE. One `data:` event per poll per workspace, `{ws, version, polled_at, open_moves}`. The window reloads state when its workspace's `version` changes. The update status arrives as `{update}`, once on connect and on every change.
|
|
182
|
+
- `/api/summary` and `/api/state` carry `update: {current, latest, available, state, reason?}`. The server checks `https://registry.npmjs.org/-/package/whose-turn/dist-tags` every ten minutes (ETag-cached) and follows the `latest` dist-tag, which every publish moves (prereleases included during the alpha); `available` is true when that tag names another version and the server runs from the plugin's install, `<data>/app/node_modules/whose-turn`. `state` is `idle`, `installing`, `restarting` or `failed` (with npm's last lines in `reason`). A new version raises one desktop notification. `POST /api/update` runs `npm install --prefix <data>/app --no-audit --no-fund --no-save whose-turn@<version>` and restarts: under the launch agent it exits and the agent starts the new copy; without one it frees the port, starts the new `server.mjs` detached (output to `<data>/server.log`) and exits. A failed install leaves the running copy as it was. A second `POST` while one runs is refused with `UPDATE_IN_PROGRESS`; one with nothing to update with `UPDATE_UNAVAILABLE`. This is the expected, announced way an install stays current, not a silent update.
|
|
182
183
|
- A read without `ws` answers for the first workspace; a write (`/api/marks`, `/api/todos`, an inbox hand-off) without `ws` is refused with `INVALID_ARGUMENT`, because it would land silently in the wrong workspace (RMA-115).
|
|
183
184
|
- `POST /api/marks` takes `{ws, seen:[ids], base:ms, handled:{row_id: iso|null}, notified:[ids]}`; `seen` must be strings, `base` a number not in the future, `handled` values ISO dates or null, else `INVALID_ARGUMENT` names the field. It answers `{marks, turn, counts}`, recomputed, so the window updates without a reload.
|
|
184
185
|
- `POST /api/todos` takes `{ws, action, …}`: `add {text, key?, by?}`; `done`, `undo`, `delete` with `{id}`; `edit {id, text}`.
|
|
@@ -196,7 +197,7 @@ Requests must carry the local `Host`; a POST must be `application/json`. An erro
|
|
|
196
197
|
|
|
197
198
|
- Material 3 plus glass: tonal surfaces seeded from the workspace's `theme.seed`, or from a seed derived from its id without one, so every workspace has its own colour; translucent blurred islands with 12 px corners, dark and light schemes, a gradient header with stat tiles.
|
|
198
199
|
- Under the glass, an animal drawn in dots (`web/src/backdrop.ts`): one species out of the JSON files in `web/src/species/`, built from shapes whose proportions come from a seeded random, in one of its poses (sitting, walking, sleeping, facing the camera). Each workspace gets its own seed for the session, so switching tabs changes the animal; `window.whoseTurnBackdrop.show({ workspace, seed, species, pose })` picks one and `.species` lists the names (the hook for pinning an animal per workspace, RMA-94). A new animal is a new JSON file, see [`docs/mechanisms/species-dsl.md`](docs/mechanisms/species-dsl.md); `/sheet` draws every species over several seeds without the glass.
|
|
199
|
-
- First run: while the open workspace has no sources, one card replaces the islands. It says what the window does, offers "Add a source" (the settings sheet on this workspace, at its connectors) and
|
|
200
|
+
- First run: while the open workspace has no sources, one card replaces the islands. It says what the window does, offers "Add a source" (the settings sheet on this workspace, at its connectors) and says Claude can set it up too.
|
|
200
201
|
- Two columns. Left: Your move, To do, Waiting on others. Right: the feed. The page never scrolls; each island scrolls inside, with sticky table headers. A PR cell shows the checks or state pill under the PR number.
|
|
201
202
|
- Every header is one line, truncated with an ellipsis. Anything that hides more text gets a "?" icon at the end. Tooltips are Tippy: instant, also on click, interactive, keep line breaks.
|
|
202
203
|
- Your move: one row per thing to do, with a "?" for why it is there, an age chip (amber after 24 h) and a Handled button. Handled rows leave this list; the Handled view in the head lists them, dimmed, with Undo.
|
|
@@ -205,7 +206,7 @@ Requests must carry the local `Host`; a POST must be `application/json`. An erro
|
|
|
205
206
|
- The AI button, a small sparkle on every row, on a feed group head (the whole thread of that ticket), on an expanded feed item, under the title and in the top bar, copies that item, the thread, the workspace or everything as Markdown with its context, ready to paste into any assistant.
|
|
206
207
|
- Feed: grouped by ticket by default, the ticket with the newest event on top; a group head is two short lines, the key with its summary, then the event count or "N new", the first two kinds of event plus "+n" and the time on the right, and a ticket that is your move carries an amber bar on its left edge; a group opens into its events. The source filter and the By ticket / Flat switch share one row, the unread total sits beside the title. A search box filters by any words across key, author, kind and text, and opens the matching groups. "Flat" shows the plain newest-first list. One filter button per connector entry, named by its `label`, else by its `id` when two entries share a type, else by the type; each item's round badge shows the initials of that name (GH for a GitHub entry under its own name). A pull request always reads `repo#number` with its title, with or without a ticket key. Unread items highlighted until clicked plus "Mark all read". Only written things expand. "Open formatted" renders the Markdown safely from DOM nodes, never innerHTML. A "@you" tag marks mentions.
|
|
207
208
|
- To do: an input with an Add button, checkboxes, an Open/Done switch in the head; ticket keys become links. Claude adds items through the API.
|
|
208
|
-
- Status line: live dot, "last check Xs ago" ("Stale · last data X ago" with a red dot when the data is old; "No data: <sources> failing" with a red dot when every source fails, an amber dot when only some do), Refresh now, "Open as app" in a browser tab or "Open in browser" in the app window, and a banner for source, config and health alerts. `open.sh` opens the app window on `/?mode=app`, which is how the page knows it is one; a reloaded app window keeps that, a tab stays a tab. The app window reports its size after a resize and the next one opens at it.
|
|
209
|
+
- Status line: live dot, "last check Xs ago" ("Stale · last data X ago" with a red dot when the data is old; "No data: <sources> failing" with a red dot when every source fails, an amber dot when only some do), Refresh now, the update chip when the install has a newer version waiting ("1.0.0-alpha.3 is out · Update", then "Updating…" and "Updated to …" once the stream reconnects to the restarted server; "Update failed · Retry" with npm's message as its tooltip), "Open as app" in a browser tab or "Open in browser" in the app window, and a banner for source, config and health alerts. `open.sh` opens the app window on `/?mode=app`, which is how the page knows it is one; a reloaded app window keeps that, a tab stays a tab. The app window reports its size after a resize and the next one opens at it.
|
|
209
210
|
- A rail down the left edge lists the workspaces, like a browser's side tabs: icons only, names on hover. Each icon is generated from the workspace id and tinted by its palette, with the open "your move" count as a badge. The choice is remembered in the browser and `?ws=<id>` opens one directly. Drag an icon up or down to reorder the workspaces; the order is saved to the config file.
|
|
210
211
|
- "Report" in the top bar opens the backlog sheet: one text field, write the problem or the wish in your own words; an assistant composes the title, the kind and the description, the current workspace's summary is attached; Send or Cmd+Enter files it into the monitor's own Linear project and shows the issue link.
|
|
211
212
|
- The gear at the bottom of the rail opens the settings sheet: the workspace list on the left, one workspace's form on the right (id, name, key pattern, theme colours, connector entries with the fields each connector declares (a boolean field is a checkbox showing its default when unset), rule texts and status words). Save writes the config file through `/api/config`; a token typed into a secret field goes to `/api/secrets` first and never into the file.
|
|
@@ -214,7 +215,7 @@ Requests must carry the local `Host`; a POST must be `application/json`. An erro
|
|
|
214
215
|
|
|
215
216
|
The Claude Code plugin is `whose-turn@bladeforge` in the [BladeForge marketplace](https://github.com/aquivalabs/BladeForge); this repository holds the monitor only. The plugin brings:
|
|
216
217
|
|
|
217
|
-
- the skill `whose-turn:whose-turn`: answers "what's on me" from `/api/summary`, sets up a first workspace
|
|
218
|
+
- the skill `whose-turn:whose-turn`: answers "what's on me" from `/api/summary`, sets up a first workspace, edits the config, opens the window, files feedback and manages to-dos;
|
|
218
219
|
- `/whose-turn`, which opens the window;
|
|
219
220
|
- the SessionStart hook, which starts the monitor if it is down and puts "N your move · M unread · K to-do" into context, marked as data from other people;
|
|
220
221
|
- the prompt hook, which takes waiting hand-offs from the inbox queue into your next message;
|
|
@@ -232,7 +233,8 @@ The queue is for agents that pull on their own: `POST /api/inbox` hands an item
|
|
|
232
233
|
|
|
233
234
|
- `npm install` once: it points `core.hooksPath` at `.githooks/`.
|
|
234
235
|
- The plugin's own code (skill, command, hooks, MCP server) is in BladeForge, `plugins/whose-turn`; a change to what Claude can do lands here as an API change first (ADR 0007).
|
|
235
|
-
- Releasing: bump `version` in `package.json`, add the changelog entry, commit, tag `v<version>` and push the tag; `.github/workflows/release.yml` checks, builds, publishes to npm
|
|
236
|
+
- Releasing: bump `version` in `package.json`, add the changelog entry, commit, tag `v<version>` and push the tag; `.github/workflows/release.yml` checks, builds, publishes to npm under `latest` on every tag, prereleases included, plus the channel tag such as `alpha` for a prerelease (npm trusts the workflow through OIDC, no token) and attaches the tarball to a GitHub Release.
|
|
237
|
+
- A checkout never offers the in-app update. To exercise it, lay out a packed copy as `<scratch>/app/node_modules/whose-turn`, start it with `WHOSE_TURN_DATA=<scratch>` and a free `WHOSE_TURN_PORT`, and point two test hooks at stubs: `WHOSE_TURN_REGISTRY` replaces `https://registry.npmjs.org` and `WHOSE_TURN_NPM` names the npm executable `POST /api/update` runs.
|
|
236
238
|
- `npm run dev` starts the server on port 8766 with data and config in `.users-files/dev/` (gitignored), so nothing touches the real config. It serves the last `npm run build`; for live reloading of the window run `npm run dev:web` next to it and open port 5173, which proxies the API to 8766.
|
|
237
239
|
- A fresh checkout has no window until `npm run build`; the server then says so on `/` while the API keeps working. A released package ships `dist/` built (RMA-126).
|
|
238
240
|
- `npm test` runs the suite (Node strips the types of the `web/src/lib/*.ts` modules it imports). `npm run test:web` runs the window's store and component tests on Vitest. `npm run check` runs `vue-tsc` and `tsc` over the server, ESLint over the window and the server, Stylelint and both suites (CI runs it on every pull request and push to `main`), `npm run format` runs Prettier over `web/src`; with `python3 scripts/docs-check.py` (the docs gate) and `/review` (the review gate) it runs on push.
|
package/config.example.json
CHANGED
|
@@ -107,22 +107,6 @@
|
|
|
107
107
|
"team": "PROJ"
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"id": "demo",
|
|
113
|
-
"name": "Demo",
|
|
114
|
-
"key_pattern": "\\bNW-\\d+\\b",
|
|
115
|
-
"theme": {
|
|
116
|
-
"seed": "#3d7a5c",
|
|
117
|
-
"accent": "#c27a2c"
|
|
118
|
-
},
|
|
119
|
-
"connectors": [
|
|
120
|
-
{
|
|
121
|
-
"type": "demo",
|
|
122
|
-
"scenario": "northwind",
|
|
123
|
-
"label": "Demo tracker"
|
|
124
|
-
}
|
|
125
|
-
]
|
|
126
110
|
}
|
|
127
111
|
],
|
|
128
112
|
"feedback": {
|
package/connectors/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/** The connector registry: every source type the server knows, and the catalogue the settings form and Claude read. */
|
|
2
2
|
import * as github from './github.mjs';
|
|
3
3
|
import * as jira from './jira.mjs';
|
|
4
|
-
import * as demo from './demo.mjs';
|
|
5
4
|
import * as linear from './linear.mjs';
|
|
6
5
|
|
|
7
|
-
export const CONNECTORS = { github, jira, linear
|
|
6
|
+
export const CONNECTORS = { github, jira, linear };
|
|
8
7
|
|
|
9
8
|
/** What a workspace calls each of its connectors: the entry's own label, else its id when another entry has the same type, else the type's label (RMA-167). */
|
|
10
9
|
export const connectorSummaries = (entries) => entries.map((entry) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const E=e=>fetch(e).then(t=>t.json(),()=>Promise.reject(new Error("unreachable"))),T=(e,t)=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t||{})}).then(n=>n.json(),()=>Promise.reject(new Error("unreachable"))),r=(e,t)=>(e.details||[]).find(n=>n["@type"]===t),S=e=>{var o,s;const t=r(e,"BadRequest");if(t&&t.field_violations)return t.field_violations.map(i=>i.description);const n=(s=(o=r(e,"ErrorInfo"))==null?void 0:o.metadata)==null?void 0:s.detail;return[n?String(n):e.message]},h=e=>{var t;return((t=r(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.",A=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,y=(e,t)=>{const n=e.filter(a=>a.group==="you").map(a=>({row:a,handled:!!a.handled})).sort((a,d)=>a.row.rank-d.row.rank||String(a.row.updated).localeCompare(String(d.row.updated))),o=n.filter(a=>!a.handled).length,s=t==="handled";return{rows:n.filter(a=>a.handled===s),open:o,handled:n.length-o}},c=e=>String(e.status_since||e.updated),R=e=>e.filter(t=>t.group==="done").sort((t,n)=>c(n).localeCompare(c(t))).slice(0,15),P=e=>e.filter(t=>t.group==="others").sort((t,n)=>String(n.updated).localeCompare(String(t.updated))),D=e=>e==="failing"?"bad":e==="running"?"warn":e==="passing"?"ok":"",g={code_review:"info",qa:"info",on_hold:"warn"},b=e=>g[e||""]||"",v=e=>({open:"info",done:"ok",dropped:"warn"})[e||""]||"info",U=(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.",k=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.",UPDATE_IN_PROGRESS:"An update is already installing; the monitor restarts when it is done.",UPDATE_UNAVAILABLE:"There is no update to install right now.",INTERNAL:"The local server failed; see its log."},w=(e,t="Not saved")=>m[h(e)]||t+": "+(e.message||"the server refused the change."),C=(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):""},L=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"}):""},I=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}},j=e=>e?new Date(e).toLocaleString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"",M=(e,t)=>t?`${e}--${t}`:"";export{l as S,k as T,A as a,C as b,L as c,D as d,w as e,b as f,E as g,O as h,j as i,R as j,I as k,U as l,M as m,P as n,v as o,T as p,S as q,h as r,_ as s,N as w,y};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.rail[data-v-be863916]{position:fixed;inset:0 auto 0 0;z-index:30;box-sizing:border-box;display:flex;flex-direction:column;gap:4px;width:60px;padding:12px 8px;overflow:hidden;border-right:1px solid var(--glass-edge);background:rgb(var(--glass),.72);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);font-family:var(--md-font);color:var(--md-on-surface);transition:width .18s ease,box-shadow .18s ease}.rail[data-v-be863916]:hover,.rail[data-v-be863916]:has(:focus-visible){width:232px;box-shadow:12px 0 32px #0000001f}.app:has(.modal) .rail[data-v-be863916]{z-index:0}.rail__item[data-v-be863916]{position:relative;display:flex;align-items:center;gap:12px;height:44px;padding:0 4px;border-radius:12px;color:inherit;text-decoration:none;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none}.rail__item[data-v-be863916]:focus-visible{outline:var(--focus-ring);outline-offset:-2px}.rail__item--dragging[data-v-be863916]{opacity:.4}.rail__item[data-drop=before][data-v-be863916]{box-shadow:0 -2px 0 0 var(--md-primary)}.rail__item[data-drop=after][data-v-be863916]{box-shadow:0 2px 0 0 var(--md-primary)}.rail__item[data-v-be863916]:hover{background:var(--state-hover)}.rail__item[aria-selected=true][data-v-be863916]{background:var(--md-secondary-container);color:var(--md-on-secondary-container)}.rail__icon[data-v-be863916]{flex:none;display:block;width:36px;height:36px;border-radius:10px}.rail__gear[data-v-be863916]{flex:none;display:grid;place-items:center;width:36px;height:36px;color:var(--md-on-surface-variant)}.rail__label[data-v-be863916]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:14px;font-weight:500;opacity:0;transition:opacity .12s ease}.rail:hover .rail__label[data-v-be863916],.rail:has(:focus-visible) .rail__label[data-v-be863916]{opacity:1}.rail__badge[data-v-be863916]{position:absolute;top:2px;left:30px;box-sizing:border-box;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--md-primary);color:var(--md-on-primary);font-size:10px;font-weight:700;line-height:18px;text-align:center}.rail:hover .rail__badge[data-v-be863916],.rail:has(:focus-visible) .rail__badge[data-v-be863916]{position:static;flex:none}.rail__spacer[data-v-be863916]{flex:1}.ai-button[data-v-d32a60c7]{display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%;cursor:pointer;color:var(--md-primary);background:rgb(var(--md-state),.06);vertical-align:middle;transition:background .12s ease,transform .12s ease}.ai-button__icon[data-v-d32a60c7]{width:15px;height:15px}.ai-button[data-v-d32a60c7]:hover{background:var(--md-secondary-container);transform:scale(1.08)}.ai-button[data-v-d32a60c7]:focus-visible{outline:var(--focus-ring);outline-offset:1px}.ai-button.ai-button--sent[data-v-d32a60c7]{background:var(--md-tertiary-container);color:var(--md-on-tertiary-container)}.ai-button.ai-button--failed[data-v-d32a60c7]{background:var(--md-error-container);color:var(--md-on-error-container)}.ai-button--big[data-v-d32a60c7]{width:32px;height:32px;background:rgb(var(--glass),.7)}.ai-button--big .ai-button__icon[data-v-d32a60c7]{width:18px;height:18px}.update-chip[data-v-f70d2aed]{display:inline-flex;align-items:center;height:26px;padding:0 12px;font:inherit;color:var(--md-on-primary-container);cursor:pointer;background:var(--md-primary-container);border:0;border-radius:var(--md-r-chip)}.update-chip--busy[data-v-f70d2aed],.update-chip--done[data-v-f70d2aed]{cursor:default}.update-chip--busy[data-v-f70d2aed]{opacity:.7}.update-chip--failed[data-v-f70d2aed]{color:var(--md-on-error-container);background:var(--md-error-container)}.status-bar[data-v-eefe1de8]{display:flex;align-items:center;gap:10px;margin:0 4px 8px;font-family:var(--md-font);font-size:12px;color:var(--md-on-surface-variant)}.status-bar__btn[data-v-eefe1de8]{height:26px;padding:0 12px;font-size:12px;text-decoration:none}.status-bar__btn--busy[data-v-eefe1de8]{opacity:.6;pointer-events:none}.status-bar__report[data-v-eefe1de8]{gap:6px;color:var(--md-error);border-color:var(--md-error)}.status-bar__icon[data-v-eefe1de8]{width:16px;height:16px}.status-bar__live[data-v-eefe1de8]{width:8px;height:8px;border-radius:50%;background:var(--live-on);box-shadow:0 0 0 0 var(--live-pulse-rest);animation:status-bar-pulse-eefe1de8 2s infinite}.status-bar__live--off[data-v-eefe1de8]{background:var(--live-off);animation:none}.status-bar__live--warn[data-v-eefe1de8]{background:var(--live-warn);animation:none}.status-bar__notice[data-v-eefe1de8]{color:var(--live-off)}.status-bar__ai-hint[data-v-eefe1de8]{margin-left:-4px}@keyframes status-bar-pulse-eefe1de8{0%{box-shadow:0 0 0 0 var(--live-pulse-from)}70%{box-shadow:0 0 0 8px var(--live-pulse-to)}to{box-shadow:0 0 0 0 var(--live-pulse-to)}}@media(prefers-reduced-motion:reduce){.status-bar__live[data-v-eefe1de8]{animation:none}}.workspace-header[data-v-0f4b6d6e]{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,2fr) 32px;gap:8px;padding:8px;background:linear-gradient(135deg,var(--blob-1),var(--blob-2));color:var(--header-ink, var(--md-on-primary-container))}@media(prefers-reduced-transparency:reduce){.workspace-header[data-v-0f4b6d6e]{background:var(--md-s-low)}}.workspace-header__title[data-v-0f4b6d6e]{display:grid;align-content:center;gap:4px;padding:12px 16px}.workspace-header__name[data-v-0f4b6d6e]{margin:0;font-size:28px;font-weight:400;line-height:36px;color:var(--md-on-primary-container)}.workspace-header__line[data-v-0f4b6d6e]{margin:0;font-size:14px;opacity:.85}.workspace-header__count[data-v-0f4b6d6e]{font-weight:700;font-variant-numeric:tabular-nums}.workspace-header__give[data-v-0f4b6d6e]{display:flex;align-items:center;gap:8px;margin-top:8px}.workspace-header__links[data-v-0f4b6d6e]{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:10px}.workspace-header__links a[data-v-0f4b6d6e]{display:inline-flex;align-items:center;height:24px;padding:0 10px;border:1px solid var(--glass-edge);border-radius:999px;background:rgb(var(--glass),.55);color:inherit;font-size:12px;font-weight:500}.workspace-header__links a[data-v-0f4b6d6e]:hover{background:rgb(var(--glass),.85)}.workspace-header__fold[data-v-0f4b6d6e]{grid-row:1;grid-column:3;place-self:center center;margin:0;color:inherit}.workspace-header__tiles[data-v-0f4b6d6e]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.workspace-header__tile[data-v-0f4b6d6e]{display:grid;align-content:start;gap:4px;padding:12px 16px;border:1px solid var(--glass-edge);border-radius:var(--md-r-card);background:rgb(var(--glass),calc(var(--glass-a) + .1));-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);color:var(--md-on-surface)}@media(prefers-reduced-transparency:reduce){.workspace-header__tile[data-v-0f4b6d6e]{background:var(--md-s-low);-webkit-backdrop-filter:none;backdrop-filter:none}}.workspace-header__tile-label[data-v-0f4b6d6e]{color:var(--md-on-surface-variant);font-size:12px;font-weight:500;line-height:16px;letter-spacing:.03em}.workspace-header__tile-value[data-v-0f4b6d6e]{min-height:40px;font-size:32px;font-weight:400;line-height:40px;font-variant-numeric:tabular-nums}.workspace-header__tile--you[data-v-0f4b6d6e]{border-color:transparent;background:var(--md-primary);color:var(--md-on-primary)}.workspace-header__tile--you .workspace-header__tile-label[data-v-0f4b6d6e]{color:var(--md-on-primary);opacity:.85}.workspace-header__tile--fail .workspace-header__tile-value[data-v-0f4b6d6e]{color:var(--md-error)}.workspace-header--collapsed[data-v-0f4b6d6e]{grid-template-columns:minmax(0,1fr) 32px;padding:4px 8px}.workspace-header--collapsed .workspace-header__title[data-v-0f4b6d6e]{display:flex;flex-wrap:wrap;align-items:baseline;gap:14px;padding:4px 8px}.workspace-header--collapsed .workspace-header__name[data-v-0f4b6d6e]{font-size:18px;line-height:28px}.workspace-header--collapsed .workspace-header__tiles[data-v-0f4b6d6e],.workspace-header--collapsed .workspace-header__give[data-v-0f4b6d6e],.workspace-header--collapsed .workspace-header__sentence[data-v-0f4b6d6e]{display:none}.workspace-header--collapsed .workspace-header__fold[data-v-0f4b6d6e]{grid-column:2}.workspace-header--collapsed .workspace-header__strip[data-v-0f4b6d6e]{font-size:13px}@media(max-width:900px){.workspace-header[data-v-0f4b6d6e],.workspace-header--collapsed[data-v-0f4b6d6e]{grid-template-columns:minmax(0,1fr) 32px}.workspace-header__fold[data-v-0f4b6d6e]{grid-column:2}.workspace-header__tiles[data-v-0f4b6d6e]{grid-column:1 / -1}}@media(max-width:640px){.workspace-header__tiles[data-v-0f4b6d6e]{grid-template-columns:repeat(2,minmax(0,1fr))}}.island__head[data-v-a03b7f62]{display:flex;flex-wrap:nowrap;align-items:center;justify-content:space-between;gap:16px;padding:12px 20px}.island__title[data-v-a03b7f62]{flex:none;margin:0;font-size:16px;line-height:24px;font-weight:500;letter-spacing:.01em;white-space:nowrap}.island__note[data-v-a03b7f62]{display:flex;flex:1 1 auto;align-items:center;justify-content:flex-end;gap:6px;min-width:0;margin:0;overflow:visible;color:var(--md-on-surface-variant);font-size:12px;line-height:16px;letter-spacing:.03em;text-align:right;white-space:nowrap;text-overflow:ellipsis}.island__note-text[data-v-a03b7f62]{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.island__body[data-v-a03b7f62]{padding:0 20px 12px}.island--column .island__head[data-v-a03b7f62]{gap:4px}.island--column .island__body[data-v-a03b7f62]{display:flex;flex-direction:column;min-height:0;padding-top:4px}.island.island--collapsed .island__body[data-v-a03b7f62]{display:none}@media(min-width:901px){.dashboard--fit .dashboard__main .island__body[data-v-a03b7f62]{display:flex;flex:1 1 auto;flex-direction:column;min-height:0;padding-bottom:0}.dashboard--fit .dashboard__main .island.island--collapsed .island__body[data-v-a03b7f62]{display:none}}.segmented[data-v-7901b5d6]{display:inline-flex;overflow:hidden;border:1px solid var(--md-outline);border-radius:999px;vertical-align:middle}.segmented--chips[data-v-7901b5d6]{flex:none;align-self:flex-start;background:rgb(var(--glass),.4)}.segmented__option[data-v-7901b5d6]{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 11px;border-left:1px solid var(--md-outline);font-size:12px;font-weight:500;color:var(--md-on-surface);cursor:pointer;-webkit-user-select:none;user-select:none}.segmented__option[data-v-7901b5d6]:first-child{border-left:0}.segmented__count[data-v-7901b5d6]{font-weight:700;font-variant-numeric:tabular-nums}.segmented__option[aria-pressed=true][data-v-7901b5d6]{background:var(--md-secondary-container);color:var(--md-on-secondary-container)}.segmented__option[data-v-7901b5d6]:focus-visible{outline:var(--focus-ring);outline-offset:-2px}.segmented--chips .segmented__option[data-v-7901b5d6]{gap:normal;height:32px;padding:0 14px;font-family:var(--md-font);font-size:14px;letter-spacing:.01em}.segmented--chips .segmented__option[data-v-7901b5d6]:hover{background:var(--state-hover)}.segmented--chips .segmented__option[aria-pressed=true][data-v-7901b5d6]{background:var(--md-secondary-container);color:var(--md-on-secondary-container)}.ticket-cell__summary[data-v-8436de88]{display:block;max-width:44ch;margin-top:2px;color:var(--md-on-surface-variant);font-size:12px;line-height:16px;letter-spacing:.03em}.pr-cell[data-v-75c443c3]{display:flex;flex-direction:column;align-items:flex-start;gap:4px}.pr-cell__link[data-v-75c443c3]{max-width:100%;overflow-wrap:anywhere}.recently-done[data-v-12ee20e8]{display:flex;flex:0 1 auto;flex-direction:column;min-height:33px;margin:0 -20px;padding:0 20px;overflow:hidden;border-top:1px solid var(--md-outline-variant)}.recently-done[open][data-v-12ee20e8]{min-height:128px}.recently-done__summary[data-v-12ee20e8]{display:flex;flex:none;align-items:center;gap:4px;min-height:40px;list-style:none;color:var(--md-on-surface-variant);font-size:14px;font-weight:500;cursor:pointer}.recently-done__summary[data-v-12ee20e8]::-webkit-details-marker{display:none}.recently-done__summary[data-v-12ee20e8]:before{content:"▾";display:inline-grid;place-items:center;width:24px;font-size:18px;transform:rotate(-90deg);transition:transform .15s ease}.recently-done[open] .recently-done__summary[data-v-12ee20e8]:before{transform:none}.recently-done__count[data-v-12ee20e8]{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;margin-left:6px;padding:0 6px;border-radius:10px;background:var(--md-s-high);color:var(--md-on-surface-variant);font-size:12px;font-weight:700}.recently-done__list[data-v-12ee20e8]{flex:1 1 auto;min-height:0;max-height:min(30vh,240px);margin:0 -20px;padding-bottom:8px;overflow:auto}.row-scroll[data-v-7bd9ace7]{overflow-x:auto;margin:0 -20px}@media(min-width:901px){.dashboard--fit .dashboard__main .row-scroll[data-v-7bd9ace7]{flex:1 1 auto;min-height:0;overflow:auto}.dashboard--fit .row-scroll[data-v-7bd9ace7]::-webkit-scrollbar{width:8px;height:8px}.dashboard--fit .row-scroll[data-v-7bd9ace7]::-webkit-scrollbar-thumb{border-radius:4px;background:var(--md-outline-variant)}}.dashboard--fit .dashboard__main .row-scroll--floor[data-v-7bd9ace7]{min-height:5rem}.row-scroll__more[data-v-7bd9ace7]{flex:none;display:block;margin:0 -20px;padding:4px 20px 6px;border-top:1px solid var(--md-outline-variant);color:var(--md-primary);font-size:13px;font-weight:500;cursor:pointer}.row-scroll__more[data-v-7bd9ace7]:hover{background:rgb(var(--md-state),.06)}.row-scroll__more[data-v-7bd9ace7]:focus-visible{outline:var(--focus-ring);outline-offset:-2px}.your-move__view[data-v-e3e1502a]{margin-left:10px}.your-move__next[data-v-e3e1502a]{font-weight:500}.your-move__quote[data-v-e3e1502a]{max-width:48ch;color:var(--md-on-surface-variant);font-size:12px;line-height:16px}.your-move__quote a[data-v-e3e1502a]{color:var(--md-on-surface-variant)}.your-move__problem[data-v-e3e1502a]{margin:4px 0 0;padding:4px 10px;font-size:12px}.your-move__mark[data-v-e3e1502a]{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 12px;border:1px solid var(--md-outline);border-radius:999px;color:var(--md-on-surface);font-size:12px;font-weight:600;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none}.your-move__mark[data-v-e3e1502a]:hover{background:var(--state-hover)}.your-move__mark[data-v-e3e1502a]:before{content:"";width:9px;height:5px;border-bottom:2px solid currentcolor;border-left:2px solid currentcolor;transform:translateY(-2px) rotate(-45deg)}.your-move__mark--undo[data-v-e3e1502a]:before{display:none}.your-move__mark[data-v-e3e1502a]:focus-visible{outline:var(--focus-ring);outline-offset:2px}.your-move__row--handled td[data-v-e3e1502a]{opacity:.55}.your-move__row--handled .your-move__next[data-v-e3e1502a]{text-decoration:line-through;text-decoration-color:var(--md-outline)}.your-move__row--handled .table__actions[data-v-e3e1502a]{opacity:1}.todos__view[data-v-6f034e1e]{margin-left:10px}.todos__add[data-v-6f034e1e]{display:flex;align-items:center;gap:8px;padding:8px 0}.todos__input[data-v-6f034e1e]{flex:1;min-width:0;height:32px;padding:0 12px;background:rgb(var(--md-state),.04);font-size:14px}.todos__list[data-v-6f034e1e]{margin:0 -20px;padding:0;overflow-y:auto;list-style:none}.todos__item[data-v-6f034e1e]{display:grid;grid-template-columns:20px minmax(0,1fr) auto 20px;align-items:center;gap:10px;padding:7px 20px;border-top:1px solid var(--md-outline-variant)}.todos__item[data-v-6f034e1e]:first-child{border-top:0}.todos__item[data-v-6f034e1e]:hover{background:rgb(var(--md-state),.06)}.todos__text[data-v-6f034e1e]{overflow-wrap:anywhere}.todos__item--done .todos__text[data-v-6f034e1e]{color:var(--md-on-surface-variant);text-decoration:line-through}.todos__empty[data-v-6f034e1e]{display:block;padding:6px 20px 10px}.todos__check[data-v-6f034e1e]{box-sizing:border-box;display:grid;place-items:center;width:18px;height:18px;border:2px solid var(--md-outline);border-radius:5px;cursor:pointer}.todos__check--on[data-v-6f034e1e]{border-color:var(--md-primary);background:var(--md-primary)}.todos__check--on[data-v-6f034e1e]:after{content:"";width:8px;height:4px;border-bottom:2px solid var(--md-on-primary);border-left:2px solid var(--md-on-primary);transform:translateY(-1px) rotate(-45deg)}.todos__meta[data-v-6f034e1e]{color:var(--md-on-surface-variant);font-size:11px;white-space:nowrap}.todos__delete[data-v-6f034e1e]{color:var(--md-on-surface-variant);font-size:16px;line-height:1;text-align:center;cursor:pointer;opacity:0}.todos__delete[data-v-6f034e1e]:focus-visible{opacity:1}.todos__item:hover .todos__delete[data-v-6f034e1e]{opacity:1}@media(min-width:901px){.dashboard--fit .dashboard__todo .todos__list[data-v-6f034e1e]{flex:1 1 auto;min-height:0}.dashboard--fit .dashboard__todo .todos__list[data-v-6f034e1e]:not(:has(.todos__item)){display:none}}.waiting__table[data-v-a632447c]{table-layout:fixed}.waiting__col--ticket[data-v-a632447c]{width:22%}.waiting__col--status[data-v-a632447c],.waiting__col--with[data-v-a632447c]{width:6.5rem}.waiting__col--pr[data-v-a632447c]{width:4.5rem}.waiting__col--actions[data-v-a632447c]{width:2rem}.waiting__reason[data-v-a632447c]{display:block;min-width:0;max-width:44ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feed-item[data-v-27e556e1]{display:grid;grid-template-columns:32px minmax(0,1fr);align-items:start;gap:12px;padding:10px 20px;cursor:default}.feed-item[data-v-27e556e1]:hover{background:var(--state-hover)}.feed-item.feed-item--open[data-v-27e556e1]{background:rgb(var(--md-state),.05)}.feed-item.feed-item--new[data-v-27e556e1]{background:var(--new-bg);box-shadow:inset 4px 0 0 var(--new),inset 0 0 0 1px var(--new-edge)}.feed-item__icon[data-v-27e556e1]{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--md-s-highest);color:var(--md-on-surface-variant);font-size:11px;font-weight:700;letter-spacing:.02em}.feed-item__icon--jira[data-v-27e556e1]{background:var(--md-primary-container);color:var(--md-on-primary-container)}.feed-item__icon--bad[data-v-27e556e1]{background:var(--md-error-container);color:var(--md-on-error-container)}.feed-item__icon--ok[data-v-27e556e1]{background:var(--md-tertiary-container);color:var(--md-on-tertiary-container)}.feed-item__body[data-v-27e556e1]{display:grid;gap:2px;min-width:0}.feed-item__top[data-v-27e556e1]{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;font-size:14px}.feed-item__right[data-v-27e556e1]{flex:none;display:inline-flex;align-items:center;gap:4px;margin-left:auto}.feed-item__kind[data-v-27e556e1]{font-weight:500}.feed-item__kind--bad[data-v-27e556e1]{color:var(--md-error)}.feed-item__who[data-v-27e556e1]{color:var(--md-on-surface-variant)}.feed-item__text[data-v-27e556e1]{display:-webkit-box;overflow:hidden;color:var(--md-on-surface-variant);font-size:12px;line-height:16px;letter-spacing:.03em;overflow-wrap:anywhere;-webkit-line-clamp:2;-webkit-box-orient:vertical}.feed-item__toggle[data-v-27e556e1]{display:inline-grid;place-items:center;width:24px;height:24px;margin-left:2px;border-radius:50%;color:var(--md-on-surface-variant);font-size:0;line-height:1;cursor:pointer;-webkit-user-select:none;user-select:none;transition:transform .15s ease}.feed-item__toggle[data-v-27e556e1]:before{content:"";width:7px;height:7px;border-right:2px solid currentcolor;border-bottom:2px solid currentcolor;transform:translateY(-2px) rotate(45deg)}.feed-item__toggle[data-v-27e556e1]:hover{background:rgb(var(--md-state),.1)}.feed-item__toggle[aria-expanded=true][data-v-27e556e1]{transform:rotate(180deg)}.feed-item__toggle[data-v-27e556e1]:focus-visible{outline:var(--focus-ring)}.feed-item__full[data-v-27e556e1]{max-height:16rem;margin-top:6px;padding:10px 12px;overflow:auto;border:1px solid var(--glass-edge);border-radius:var(--md-r-chip);background:rgb(var(--glass),.55);color:var(--md-on-surface);font-size:13px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}.feed-item__actions[data-v-27e556e1]{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}.feed-item--new .feed-item__kind[data-v-27e556e1],.feed-item--lit .feed-item__kind[data-v-27e556e1]{color:var(--md-on-surface);font-weight:700}.feed-item--new .feed-item__text[data-v-27e556e1],.feed-item--lit .feed-item__text[data-v-27e556e1]{color:var(--md-on-surface)}.feed-item--new .feed-item__icon[data-v-27e556e1],.feed-item--lit .feed-item__icon[data-v-27e556e1]{box-shadow:0 0 0 2px var(--new)}.feed-item--new .feed-item__when[data-v-27e556e1]:before,.feed-item--lit .feed-item__when[data-v-27e556e1]:before{content:"NEW";display:inline-block;margin-right:6px;padding:1px 6px;border-radius:4px;background:var(--new);color:var(--new-ink);font-size:10px;font-weight:800;letter-spacing:.06em;vertical-align:1px}.feed-item:not(.feed-item--new) .feed-item__body[data-v-27e556e1]{opacity:.78}.feed__bar[data-v-d92a7c74]{display:flex;flex:none;flex-wrap:nowrap;align-items:center;justify-content:space-between;gap:8px;margin:0 0 8px}.feed__new-count[data-v-d92a7c74]{display:inline-flex;align-items:center;height:22px;padding:0 9px;border-radius:999px;background:var(--new);color:var(--new-ink);box-shadow:0 0 12px var(--new-glow);font-size:12px;font-weight:800}.feed__mark-read[data-v-d92a7c74]{flex:none;padding:4px 8px;border-radius:999px;color:var(--md-primary);font-size:13px;font-weight:500;cursor:pointer;-webkit-user-select:none;user-select:none}.feed__mark-read[data-v-d92a7c74]:hover{background:var(--state-hover)}.feed__mark-read[data-v-d92a7c74]:focus-visible{outline:var(--focus-ring)}.feed__search[data-v-d92a7c74]{display:flex;flex:none;align-items:center;gap:8px;margin:0 0 8px}.feed__search-input[data-v-d92a7c74]{flex:1;min-width:0;height:30px;padding:0 10px;font-size:13px}.feed__search-count[data-v-d92a7c74]{flex:none;color:var(--md-on-surface-variant);font-size:12px}.feed__list[data-v-d92a7c74]{flex:1 1 auto;min-height:12rem;margin:0 -20px;padding:0 0 4px;overflow-y:auto;list-style:none}.feed__group[data-v-d92a7c74]{display:grid;grid-template-columns:minmax(0,1fr);align-items:start;gap:0;padding:0;cursor:default}.feed__group[data-v-d92a7c74]:hover{background:var(--state-hover)}.feed__group.feed__group--open[data-v-d92a7c74]{background:rgb(var(--md-state),.05)}.feed__group.feed__group--new[data-v-d92a7c74]{background:var(--new-bg);box-shadow:inset 4px 0 0 var(--new),inset 0 0 0 1px var(--new-edge)}.feed__group-head[data-v-d92a7c74]{display:grid;grid-template-columns:32px minmax(0,1fr) auto;align-items:center;gap:12px;padding:10px 20px;cursor:pointer}.feed__group-copy[data-v-d92a7c74]{opacity:.55}.feed__group-head:hover .feed__group-copy[data-v-d92a7c74]{opacity:1}.feed__group-head[data-v-d92a7c74]:hover{background:var(--state-hover)}.feed__group--new>.feed__group-head[data-v-d92a7c74]{background:var(--new-bg);box-shadow:inset 4px 0 0 var(--new)}.feed__group--move[data-v-d92a7c74],.feed__group--move>.feed__group-head[data-v-d92a7c74]{box-shadow:inset 4px 0 0 var(--move)}.feed__group.feed__group--new.feed__group--move[data-v-d92a7c74]{box-shadow:inset 4px 0 0 var(--move),inset 0 0 0 1px var(--new-edge)}.feed__group-text[data-v-d92a7c74]{min-width:0}.feed__group-key[data-v-d92a7c74]{display:flex;align-items:baseline;gap:8px;font-size:14px}.feed__group-ticket[data-v-d92a7c74]{flex:none;font-weight:600}.feed__group-sum[data-v-d92a7c74]{overflow:hidden;color:var(--md-on-surface-variant);font-size:13px;text-overflow:ellipsis;white-space:nowrap}.feed__group-meta[data-v-d92a7c74]{display:flex;align-items:center;gap:8px;margin-top:2px;color:var(--md-on-surface-variant);font-size:12px}.feed__group-kinds[data-v-d92a7c74]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.feed__group-when[data-v-d92a7c74]{margin-left:auto}.feed__group-count[data-v-d92a7c74]{flex:none;display:inline-flex;align-items:center;height:18px;padding:0 7px;border-radius:999px;background:var(--md-s-highest);color:var(--md-on-surface-variant);font-size:11px;font-weight:700}.feed__group-count--new[data-v-d92a7c74]{background:var(--new);color:var(--new-ink)}.feed__children[data-v-d92a7c74]{margin:0 0 0 35px;padding:0 0 4px;border-left:2px solid var(--md-outline-variant);list-style:none}.feed__children .feed-item[data-v-d92a7c74]{padding-left:14px}.feed__group-caret[data-v-d92a7c74]{flex:none;display:grid;place-items:center;width:32px;height:32px;border-radius:50%;background:var(--md-s-highest);color:var(--md-on-surface-variant);font-size:14px;transition:transform .15s ease}.feed__group-caret[data-v-d92a7c74]:focus-visible{outline:var(--focus-ring)}.feed__group--open .feed__group-caret[data-v-d92a7c74]{transform:rotate(90deg)}@media(min-width:901px){.dashboard--fit .feed__list[data-v-d92a7c74]::-webkit-scrollbar{width:8px;height:8px}.dashboard--fit .feed__list[data-v-d92a7c74]::-webkit-scrollbar-thumb{border-radius:4px;background:var(--md-outline-variant)}}@media(max-width:900px){.feed__list[data-v-d92a7c74]{max-height:28rem}}.first-run[data-v-84359c2d]{display:grid;place-self:start center;gap:14px;width:min(640px,100%);padding:28px 32px}.first-run__title[data-v-84359c2d]{margin:0;font-size:22px;font-weight:400;line-height:28px}.first-run__text[data-v-84359c2d]{margin:0;color:var(--md-on-surface-variant)}.first-run__actions[data-v-84359c2d]{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}.first-run__ai[data-v-84359c2d]{font-size:13px}.markdown-view[data-v-dcf42e95]{padding:16px 20px 20px;overflow:auto;overflow-wrap:anywhere;font-size:14px;line-height:1.6}.markdown-view[data-v-dcf42e95] h4,.markdown-view[data-v-dcf42e95] h5{margin:16px 0 6px;font-weight:600}.markdown-view[data-v-dcf42e95] h4{font-size:16px}.markdown-view[data-v-dcf42e95] h5{font-size:14px}.markdown-view[data-v-dcf42e95] p{margin:0 0 10px}.markdown-view[data-v-dcf42e95] ul,.markdown-view[data-v-dcf42e95] ol{margin:0 0 10px;padding-left:22px}.markdown-view[data-v-dcf42e95] li{margin:2px 0}.markdown-view[data-v-dcf42e95] code{padding:1px 5px;border-radius:4px;background:rgb(var(--md-state),.1);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}.markdown-view[data-v-dcf42e95] pre{margin:0 0 12px;padding:12px 14px;overflow:auto;border-radius:8px;background:#00000047}.markdown-view[data-v-dcf42e95] pre code{padding:0;background:none;font-size:12.5px;line-height:1.5;white-space:pre}.markdown-view[data-v-dcf42e95] blockquote{margin:0 0 10px;padding:4px 12px;border-left:3px solid var(--md-primary);color:var(--md-on-surface-variant)}.markdown-view[data-v-dcf42e95] strong{font-weight:600}.markdown-view[data-v-dcf42e95] .markdown-view__mention{color:var(--md-primary);font-weight:500}.feedback-sheet__card[data-v-ec34adcc]{width:min(640px,100%)}.feedback-sheet__text[data-v-ec34adcc]{min-height:7rem;resize:vertical}.feedback-sheet__note[data-v-ec34adcc]{margin:0;color:var(--md-on-surface-variant);font-size:12px}.feedback-sheet__report[data-v-ec34adcc]{display:flex;align-items:baseline;gap:8px;padding:4px 0;border-top:1px solid var(--md-outline-variant);font-size:13px}.feedback-sheet__report-title[data-v-ec34adcc]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.field-row[data-v-2ba0ba86]{display:grid;grid-template-columns:170px minmax(0,1fr);align-items:center;gap:4px 14px}.field-row__label[data-v-2ba0ba86]{color:var(--md-on-surface);font-size:13px}.field-row__hint[data-v-2ba0ba86]{grid-column:2;color:var(--md-on-surface-variant);font-size:11px}.field-row__check[data-v-2ba0ba86]{display:flex;align-items:center;gap:8px;color:var(--md-on-surface-variant);font-size:11px}.field-row__colour[data-v-2ba0ba86]{width:44px;height:30px;padding:2px}.field-row__inline[data-v-2ba0ba86]{display:flex;align-items:center;gap:8px}.settings-sheet__card[data-v-779d567d]{width:min(1040px,100%);height:min(760px,100%)}.settings-sheet__dirty[data-v-779d567d]{color:var(--md-error);font-size:12px;font-weight:600}.settings-sheet__confirm[data-v-779d567d]{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:12px 20px 0;padding:8px 12px;border-radius:10px;background:var(--md-warn-container);color:var(--md-on-warn-container);font-size:13px}.settings-sheet__confirm-text[data-v-779d567d]{flex:1}.settings-sheet__alert[data-v-779d567d]{margin:12px 20px 0}.settings-sheet__body[data-v-779d567d]{display:grid;flex:1;grid-template-columns:220px minmax(0,1fr);gap:0;min-height:0}.settings-sheet__list[data-v-779d567d]{display:flex;flex-direction:column;gap:4px;margin:0;padding:12px;overflow:auto;border-right:1px solid var(--md-outline-variant);list-style:none}.settings-sheet__workspace[data-v-779d567d]{box-sizing:border-box;display:block;width:100%;padding:8px 12px;overflow:hidden;border-radius:10px;font-size:14px;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.settings-sheet__workspace[data-v-779d567d]:hover{background:var(--state-hover)}.settings-sheet__workspace[aria-current=true][data-v-779d567d]{background:var(--md-secondary-container);color:var(--md-on-secondary-container)}.settings-sheet__workspace[data-v-779d567d]:focus-visible{outline:var(--focus-ring);outline-offset:-2px}.settings-sheet__workspace--add[data-v-779d567d]{color:var(--md-primary);font-weight:500}.settings-sheet__connector[data-v-779d567d]{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border:1px solid var(--md-outline-variant);border-radius:12px;background:rgb(var(--glass),.35)}.settings-sheet__connector-head[data-v-779d567d]{display:flex;align-items:center;gap:10px}.settings-sheet__connector-name[data-v-779d567d]{flex:1;font-weight:600}.settings-sheet__actions[data-v-779d567d]{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.settings-sheet__rules[data-v-779d567d]{display:flex;flex-direction:column;gap:6px;margin:0;padding:0 0 0 28px;font-size:13px}.settings-sheet__rule[data-v-779d567d]{display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:12px;padding:4px 0;border-bottom:1px solid var(--md-outline-variant)}.settings-sheet__rule-when[data-v-779d567d]{color:var(--md-on-surface-variant);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow-wrap:anywhere}.settings-sheet__rule-then[data-v-779d567d]{overflow-wrap:anywhere}.settings-sheet__rules-note[data-v-779d567d]{margin:0;color:var(--md-on-surface-variant)}.app__shell[data-v-f146b085]{box-sizing:border-box;display:flex;flex-direction:column;height:100%;padding:12px 12px 12px 72px}.app__shell>.dashboard[data-v-f146b085]{flex:1 1 auto;min-height:0}@media(min-width:901px){.app__shell>.dashboard.dashboard--fit[data-v-f146b085]{height:auto}}@media(max-width:900px){.app__shell[data-v-f146b085]{height:auto}}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{w as X,t as qe,r as Ye,s as Kt,c as g,d as W,o as l,a as i,F as V,b as N,e as K,u as p,n as U,f as o,g as h,h as C,i as I,_ as j,j as F,k as O,l as q,m as Ue,p as vt,q as We,v as te,x as Gt,y as Le,z as Ne,A as He,B as Jt,C as mt,D as Zt,E as Qt,G as es,H as ts}from"./_plugin-vue_export-helper-DHr5RHTx.js";import{s as ss,m as kt,u as yt,a as gt,r as bt,p as ns,b as os,i as nt,l as wt,c as as,M as G,d as ge,e as me,f as ls,g as Xe,h as Te,j as rs,k as Me,n as is,o as cs,q as ds,t as us,v as ot,w as ps}from"./scheme-DJ3WNw60.js";import{g as Ae,r as _s,a as hs,e as fs,w as xt,p as ae,b as oe,T as je,S as we,c as ne,h as vs,s as Ke,m as he,d as ms,f as $t,i as Ge,j as ks,k as Je,l as ys,y as gs,n as bs,o as ws,q as Ct}from"./bem-COKTjsky.js";const pe=ss(bt),at=(e,t)=>{const s=G,a=new Float32Array(s*s);for(let r=0;r<a.length;r+=1)a[r]=e[r]===t?1e6:0;const n=(r,c,u)=>{a[c]+u<a[r]&&(a[r]=a[c]+u)};for(let r=0;r<s;r+=1)for(let c=0;c<s;c+=1){const u=r*s+c;c>0&&n(u,u-1,1),r>0&&n(u,u-s,1),c>0&&r>0&&n(u,u-s-1,Math.SQRT2),c<s-1&&r>0&&n(u,u-s+1,Math.SQRT2)}for(let r=s-1;r>=0;r-=1)for(let c=s-1;c>=0;c-=1){const u=r*s+c;c<s-1&&n(u,u+1,1),r<s-1&&n(u,u+s,1),c<s-1&&r<s-1&&n(u,u+s+1,Math.SQRT2),c>0&&r<s-1&&n(u,u+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},xs=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:nt()?s:s.map((n,r)=>wt(n,a[r],.35)),rim:De(t.getPropertyValue("--blob-3"))||[23,223,220],strength:nt()?.9:1}},$s=(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)}),Cs=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,r=Math.floor(a/G);t.minX=Math.min(t.minX,n),t.maxX=Math.max(t.maxX,n),t.minY=Math.min(t.minY,r),t.maxY=Math.max(t.maxY,r)}const s=a=>(a-G/2)/ge;return{minX:s(t.minX),maxX:s(t.maxX),minY:s(t.minY),maxY:s(t.maxY)}},Ss=(e,t,s,a)=>{const n=t.maxX-t.minX,r=t.maxY-t.minY,c=Math.min(e.size,2*s*.95/n,2*a*.98/r),u=c/2,v=-e.overflowX*c,_=-e.overflowY*c;return{size:c,centerX:e.right?s-v-t.maxX*u:v-t.minX*u,centerY:e.bottom?a-_-t.maxY*u:_-t.minY*u}},Ts=(e,t,s)=>"rgba("+e.core.map((n,r)=>Math.round(wt(n,e.rim[r],t))).join(",")+","+s.toFixed(3)+")",Ms=(e,t,s)=>{const a=[],n=t.size/62,r=t.size/2/ge,c=t.size*ls/4,u=n*.866;for(let v=0,_=t.centerY-c;_<t.centerY+c;v+=1,_+=u)for(let y=t.centerX-c+v%2*n/2;y<t.centerX+c;y+=n){const A=y+e.range(-.3,.3)*n,E=_+e.range(-.3,.3)*n,R=Math.floor((A-t.centerX)/r+G/2),S=Math.floor((E-t.centerY)/r+G/2);if(R<0||S<0||R>=G||S>=G)continue;const d=S*G+R,k=e.next(),f=e.range(.75,1.25),B=e.range(-.15,.15);if(s.inside[d]){const z=s.depth[d]/ge,L=me(z/.035),J=me(z/.3);if(k>.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:me(.65-.65*J+B)})}else{const z=s.gap[d]/ge;if(k>.14*Math.exp(-z/.05)+.004)continue;a.push({x:A,y:E,radius:n*e.range(.1,.24),alpha:.35,tint:me(.8+B)})}}return a},As=()=>document.getElementById("wt"),Rs=()=>document.getElementById("backdrop"),St=()=>Math.floor(Math.random()*4294967296),ke={},D={workspace:"",seed:St(),species:"",wantedSpecies:"",wantedPose:"",built:null},Es=(e,t)=>{const s=as(D.seed),a=pe[Math.floor(s.next()*pe.length)];D.species=pe.includes(D.wantedSpecies)?D.wantedSpecies:a;const n=$s(s,e,t),r={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)&&(r.facing=-r.facing);const c=ns(os(bt,D.species,s,{pose:D.wantedPose}).shapes,r),u={inside:c,depth:at(c,1),gap:at(c,0)},v=Ss(n,Cs(c),e,t);return{key:D.seed+":"+D.species+":"+e+"x"+t,dots:Ms(s,v,u)}},xe=()=>{const e=Rs();if(!e)return;const t=e.clientWidth,s=e.clientHeight,a=window.devicePixelRatio||1,n=Math.round(t*a),r=Math.round(s*a);(e.width!==n||e.height!==r)&&(e.width=n,e.height=r);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 u=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=Es(t,s));const v=As();if(!v)return u;const _=xs(v);return D.built.dots.forEach(y=>{c.fillStyle=Ts(_,y.tint,y.alpha*_.strength),c.beginPath(),c.arc(y.x,y.y,y.radius,0,Math.PI*2),c.fill()}),u};window.whoseTurnBackdrop={species:pe.slice(),seeded:kt,get current(){return{workspace:D.workspace,species:D.species,seed:D.seed}},show:e=>{const t=e||{},s=String(t.workspace||"");ke[s]||(ke[s]=St());const a=t.seed!==void 0?Number(t.seed)>>>0:ke[s];ke[s]=a;const n=t.species&&pe.includes(t.species)?t.species:"",r=String(t.pose||"");return s===D.workspace&&a===D.seed&&n===D.wantedSpecies&&r===D.wantedPose&&D.built||(D.workspace=s,D.seed=a,D.wantedSpecies=n,D.wantedPose=r,D.built=null,xe()),window.whoseTurnBackdrop.current},repaint:xe};X(Xe,xe);yt(window,"resize",gt(xe,200));const{show:Tt,repaint:Os,seeded:Pr}=window.whoseTurnBackdrop,Is=()=>{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})},Ds=()=>{let e=0;return{next:()=>++e,accepts:t=>t===e}},lt=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,r])=>({type:n,...r})),rules:a,theme:t.theme||{}}},Bs=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},rt=new WeakMap;let Vs=0;const it=e=>{const t=qe(e);let s=rt.get(t);return s||(s=++Vs,rt.set(t,s)),s},ct=e=>JSON.stringify(e),Us=(e,t)=>{const s=[];return e.forEach(a=>a.connectors.forEach(n=>{const r=t.get(n)||{};Object.keys(r).forEach(c=>{r[c]&&s.push({ws:a.id,connector:n.id||n.type,key:c,value:r[c]})})})),s},le=(e,t,s)=>{typeof s=="boolean"||s&&s.length?e[t]=s:delete e[t]},Ws=(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))},Ns=(e,t,s,a)=>{const n=e.filter(r=>r!==t);return n.splice(n.indexOf(s)+(a?0:1),0,t),n},js={gt:">",gte:"≥",lt:"<",lte:"≤",prefix:"starts with"},Ps=(e,t)=>`${e} is ${t?"present":"absent"}`,Fs=(e,t)=>Array.isArray(t)?`${e} in ${t.join(", ")}`:t&&typeof t=="object"?Object.entries(t).map(([s,a])=>s==="present"?Ps(e,a):`${e} ${js[s]} ${JSON.stringify(a)}`).join(" and "):`${e} = ${t}`,zs=e=>({when:Object.entries(e.when).map(([t,s])=>Fs(t,s)).join(" and ")||"anything else",then:e.then.whose==="hidden"?"hidden":`${e.then.whose}: ${e.then.next}`}),qs=e=>e.modal?"modal":e.settings?"settings":e.feedback?"feedback":null,Ys=(e,t)=>{const s=new URLSearchParams(e).get("mode");return s==="app"||s==="tab"?s:t==="app"?"app":"tab"},Ls=e=>"/?mode=tab"+(e?"&ws="+encodeURIComponent(e):""),Hs=e=>({width:e.outerWidth,height:e.outerHeight}),Mt={writeDefaults:!1},At=Te("wt-ws","",Mt),Rt=rs("wt-mode","",Mt),m=Kt({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:"",update:{current:"",latest:"",available:!1,state:"idle"}}),b=Ye({ws:new URLSearchParams(location.search).get("ws")||At.value,open:new Set,mode:Ys(location.search,Rt.value),live:!1,reconnects:0,status:"Connecting…",modal:null,feedback:!1,settings:!1,settingsDirty:!1,settingsAsk:!1,settingsAt:""});Rt.value=b.mode;const $e=Me("",6e3),ie=(e,t)=>{const s=oe(e,t);return s&&($e.value=s),!s},Xs=()=>{b.mode==="app"&&yt(window,"resize",gt(()=>ae("/api/window",Hs(window)).catch(ie),600))},Et={resume:()=>{}},ce=cs({scheduler:e=>{const t=ds(e,3e4,{immediate:!1});return Et.resume=t.resume,t}}),dt=g(()=>{const e=m.workspace.theme;return e&&e.light&&e.dark?e[Xe.value?"dark":"light"]:{}}),H=g(()=>m.counts),ut=g(()=>[...m.problems||[],...m.health.alerts.map(e=>e.text),...Object.entries(m.errors||{}).map(([e,t])=>e+": "+t)]),Ks=(e="")=>{b.settingsAt=e,b.settings=!0},Ot=g(()=>qs(b)),Gs=()=>{const e=Ot.value;if(e==="modal"&&(b.modal=null),e==="feedback"&&(b.feedback=!1),e==="settings"){if(b.settingsDirty){b.settingsAsk=!0;return}b.settings=!1}},Pe=new Map,Js=(e,t)=>{t&&Pe.set(e,document.activeElement)},Zs=(e,t)=>{if(t)return;const s=Pe.get(e);Pe.delete(e),s instanceof HTMLElement&&s.focus()},Qs={modal:()=>!!b.modal,settings:()=>b.settings,feedback:()=>b.feedback};Object.entries(Qs).forEach(([e,t])=>{X(t,s=>Js(e,s),{flush:"sync"}),X(t,s=>Zs(e,s),{flush:"post"})});let Ce=-1,Fe=0;const pt=Ds(),fe=(e=b.ws)=>{const t=pt.next();return Ae("/api/state"+(e?"?ws="+encodeURIComponent(e):"")).then(s=>pt.accepts(t)?s.error?_s(s.error)!=="NO_SUCH_WORKSPACE"||!hs(e)?(b.status=fs(s.error,"Could not load"),m):fe(""):(Object.assign(m,s),b.ws=m.workspace.id,b.status="",Tt({workspace:m.workspace.id}),document.title="Whose Turn · "+m.workspace.name,m):m)},It=()=>(Ce=-1,fe()),_e=is("/api/stream",[],{immediate:!1,autoConnect:!1,autoReconnect:!0,serializer:{read:e=>JSON.parse(e||"null")}});X(_e.status,e=>{b.live=e==="OPEN",b.live&&(b.status="",b.reconnects++)},{flush:"sync"});X(_e.error,e=>{e&&(b.status="Disconnected from the local server, retrying…")},{flush:"sync"});X(_e.data,e=>{if(e){if("update"in e){m.update=e.update;return}typeof e.open_moves=="number"&&(m.workspaces=xt(m.workspaces,e.ws,e.open_moves)),!(m.workspace.id&&e.ws!==m.workspace.id)&&(b.status="",e.version!==Ce?(Ce=e.version,Fe===0&&fe()):m.polled_at=e.polled_at)}},{flush:"sync"});const en=()=>{_e.eventSource.value||_e.open()},tn=()=>(Et.resume(),fe().then(en)),sn=e=>e===m.workspace.id?Promise.resolve():(Ce=-1,Fe++,fe(e).then(()=>{b.ws===e&&(At.value=b.ws,b.open.clear())},()=>{$e.value=we+"; staying on "+m.workspace.name+"."}).finally(()=>{Fe--})),Dt=(e,t)=>ae(e,{ws:m.workspace.id,...t}),ze=e=>Dt("/api/marks",e).then(t=>{const s=oe(t);return s||(Object.assign(m,{marks:t.marks,turn:t.turn,counts:t.counts}),m.workspaces=xt(m.workspaces,m.workspace.id,t.counts.mine)),s},t=>oe(t)),nn=e=>Dt("/api/todos",e).then(t=>{const s=oe(t,{unreachable:je});return!s&&Array.isArray(t)&&(m.todos=t),s},t=>oe(t,{unreachable:je})),on=e=>Ae("/api/config").then(t=>ae("/api/config",{workspaces:Ws(t.workspaces,e),secrets:[]})).then(t=>{ie(t,{refused:"The order was not saved"})&&It()},t=>ie(t,{refused:"The order was not saved"})),_t=()=>{b.status="Checking now…",ae("/api/refresh").catch(e=>{b.status="",ie(e)}),Tt({workspace:m.workspace.id,seed:Math.floor(Math.random()*4294967296)})},an="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",ln="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",rn="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",cn=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},Bt=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()},dn=e=>({1:17,2:14,3:11})[e.length]||11,un=(e,t)=>{const s=t(cn(e.id)),a=[],n=3+Math.floor(s()*3);for(let c=0;c<n;c+=1){const u=s()*Math.PI*2,v=9+s()*7;a.push({cx:(18+Math.cos(u)*v).toFixed(1),cy:(18+Math.sin(u)*v).toFixed(1),r:(1.6+s()*2.2).toFixed(1),opacity:(.35+s()*.4).toFixed(2)})}const r=Bt(e.name||e.id);return{dots:a,glyph:r,fontSize:dn(r)}},pn={class:"rail","aria-label":"Workspaces"},_n=["data-drop","href","aria-label","aria-selected","onClick","onDragstart","onDragover","onDrop"],hn={viewBox:"0 0 36 36",class:"rail__icon","aria-hidden":"true"},fn=["fill"],vn=["cx","cy","r","fill","opacity"],mn=["y","font-size","fill"],kn={class:"rail__label"},yn={key:0,class:"rail__badge"},gn={viewBox:"0 0 24 24",class:"rail__gear","aria-hidden":"true"},bn=["d"],wn=W({__name:"Rail",emits:["settings"],setup(e,{emit:t}){const s=t,a=g(()=>Object.fromEntries(m.workspaces.map(d=>[d.id,un(d,kt)]))),n=g(()=>Object.fromEntries(m.workspaces.map(d=>{const k=d.theme?d.theme[Xe.value?"dark":"light"]:null;return[d.id,{fill:k?k["--md-primary-container"]:"var(--md-primary-container)",ink:k?k["--md-on-primary-container"]:"var(--md-on-primary-container)",dot:k?k["--md-primary"]:"var(--md-primary)"}]}))),r=d=>{d.detail>0&&d.currentTarget.blur()},c=(d,k)=>{r(d),sn(k)},u=d=>{s("settings"),r(d)},v=I(""),_=I({}),y=d=>{const k=d.currentTarget;return d.clientY-k.getBoundingClientRect().top<k.offsetHeight/2},A=(d,k)=>{v.value=k.id,d.dataTransfer&&(d.dataTransfer.effectAllowed="move",d.dataTransfer.setData("text/plain",k.id))},E=()=>{v.value="",_.value={}},R=(d,k)=>{if(!v.value||v.value===k.id)return;d.preventDefault(),d.dataTransfer&&(d.dataTransfer.dropEffect="move");const f=y(d)?"before":"after";(_.value.id!==k.id||_.value.side!==f)&&(_.value={id:k.id,side:f})},S=(d,k)=>{if(!v.value||v.value===k.id)return;d.preventDefault();const f=Ns(m.workspaces.map(B=>B.id),v.value,k.id,y(d));_.value={},on(f)};return(d,k)=>(l(),i("nav",pn,[(l(!0),i(V,null,N(p(m).workspaces,f=>(l(),i("a",{key:f.id,class:U(["rail__item",{"rail__item--dragging":v.value===f.id}]),"data-drop":_.value.id===f.id?_.value.side:null,href:"?ws="+encodeURIComponent(f.id),"aria-label":f.name,"aria-selected":f.id===p(m).workspace.id,draggable:"true",onClick:K(B=>c(B,f.id),["prevent"]),onDragstart:B=>A(B,f),onDragend:E,onDragover:B=>R(B,f),onDragleave:k[0]||(k[0]=B=>_.value={}),onDrop:B=>S(B,f)},[(l(),i("svg",hn,[o("rect",{x:"0",y:"0",width:"36",height:"36",rx:"10",fill:n.value[f.id].fill},null,8,fn),(l(!0),i(V,null,N(a.value[f.id].dots,(B,z)=>(l(),i("circle",{key:z,cx:B.cx,cy:B.cy,r:B.r,fill:n.value[f.id].dot,opacity:B.opacity},null,8,vn))),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},h(a.value[f.id].glyph),9,mn)])),o("span",kn,h(f.name),1),f.open_moves?(l(),i("span",yn,h(f.open_moves),1)):C("",!0)],42,_n))),128)),k[2]||(k[2]=o("span",{class:"rail__spacer"},null,-1)),o("a",{class:"rail__item",href:"#settings","aria-label":"Settings",onClick:K(u,["prevent"])},[(l(),i("svg",gn,[o("path",{d:p(ln),fill:"currentColor"},null,8,bn)])),k[1]||(k[1]=o("span",{class:"rail__label"},"Settings",-1))])]))}}),xn=j(wn,[["__scopeId","data-v-be863916"]]),Be=" · checks every minute",Vt=(e,t,s,a)=>{const n=e.failing.map(c=>c.source),r=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 "+r+Be,failing:n,noData:!1}:{dot:a?"ok":"off",text:"Live · last check "+r+Be,failing:n,noData:!1}},$n=["data-tip"],Cn={class:"ai-button__icon",viewBox:"0 0 24 24","aria-hidden":"true"},Sn=["d"],Tn=W({__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=Me("",1600),n=g(()=>s[a.value]),r=()=>vs(t.query).then(c=>navigator.clipboard.writeText(c)).then(()=>{a.value="sent"},()=>{a.value="failed"});return(c,u)=>(l(),i("button",{type:"button",class:U(["ai-button",{"ai-button--big":e.big,"ai-button--sent":p(a)==="sent","ai-button--failed":p(a)==="failed"}]),"aria-label":"Copy for AI","data-tip":n.value,onClick:K(r,["stop"])},[(l(),i("svg",Cn,[o("path",{d:p(an),fill:"currentColor"},null,8,Sn)]))],10,$n))}}),de=j(Tn,[["__scopeId","data-v-d32a60c7"]]),Mn={key:0,class:"update-chip update-chip--done",role:"status"},An={key:1,class:"update-chip update-chip--busy",role:"status"},Rn=["title"],En=15e3,On=W({__name:"UpdateChip",setup(e){const t=I(!1),s=I(""),a=Me("",En),n=g(()=>m.update),r=g(()=>t.value||n.value.state==="installing"||n.value.state==="restarting");X(()=>n.value.current,u=>{s.value&&u&&u!==s.value&&(a.value=u,s.value="")});const c=()=>{if(r.value)return;t.value=!0,s.value=n.value.current;const u=v=>{if(t.value=!1,!ie(v,{refused:"The update did not start"})){s.value="";return}m.update=v.update};ae("/api/update").then(u,u)};return(u,v)=>p(a)?(l(),i("span",Mn,"Updated to "+h(p(a)),1)):r.value?(l(),i("span",An,"Updating…")):n.value.available&&n.value.state==="failed"?(l(),i("button",{key:2,type:"button",class:"update-chip update-chip--failed",title:n.value.reason,onClick:c}," Update failed · Retry ",8,Rn)):n.value.available?(l(),i("button",{key:3,type:"button",class:"update-chip",onClick:c},h(n.value.latest)+" is out · Update ",1)):C("",!0)}}),In=j(On,[["__scopeId","data-v-f70d2aed"]]),Dn={class:"status-bar"},Bn={class:"status-bar__icon",viewBox:"0 0 24 24","aria-hidden":"true"},Vn=["d"],Un={key:0,class:"status-bar__notice",role:"alert"},Wn=["href"],Nn=["aria-disabled"],jn=5e3,Pn=W({__name:"StatusBar",emits:["report"],setup(e,{emit:t}){const s=t,a=g(()=>(ce.value,Vt(m.health,m.polled_at,m.workspace.connectors.length,b.live))),n=I(!1),r=I(!1),c=g(()=>r.value?"Opened in the app window. You can close this tab.":b.status||a.value.text),u=_=>ie(_,{refused:"The app window did not open"}),v=()=>{if(n.value)return;n.value=!0,setTimeout(()=>n.value=!1,jn);const _=y=>{u(y),r.value=!oe(y)};ae("/api/open",{mode:"app"}).then(_,_)};return(_,y)=>(l(),i("div",Dn,[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"))},[(l(),i("svg",Bn,[o("path",{d:p(rn),fill:"currentColor"},null,8,Vn)])),y[2]||(y[2]=F("Report ",-1))]),o("span",{class:U(["status-bar__live",{"status-bar__live--off":a.value.dot==="off","status-bar__live--warn":a.value.dot==="warn"}])},null,2),o("span",null,h(c.value),1),p($e)?(l(),i("span",Un,h(p($e)),1)):C("",!0),O(In),o("button",{type:"button",class:"btn status-bar__btn",onClick:y[1]||(y[1]=(...A)=>p(_t)&&p(_t)(...A))},"Refresh now"),p(b).mode==="app"?(l(),i("a",{key:1,class:"btn status-bar__btn",href:p(Ls)(p(b).ws),target:"_blank",rel:"noopener"},"Open in browser",8,Wn)):(l(),i("button",{key:2,type:"button",class:U(["btn status-bar__btn",{"status-bar__btn--busy":n.value}]),"aria-disabled":n.value,onClick:v},h(n.value?"Opening…":"Open as app"),11,Nn)),O(de,{query:{all:"1"}}),y[3]||(y[3]=o("span",{class:"ai-hint status-bar__ai-hint"},"AI: copy everything",-1))]))}}),Fn=j(Pn,[["__scopeId","data-v-eefe1de8"]]),Ut=e=>{const t=Te("wt-collapsed",[],{writeDefaults:!1}),s=g(()=>t.value.includes(e));return{collapsed:s,toggle:()=>{t.value=s.value?t.value.filter(n=>n!==e):[...t.value,e]}}},zn=["href"],qn={key:1},se=W({__name:"ExtLink",props:{text:{default:""},url:{default:""}},setup(e){const t=e,s=g(()=>Ke(t.url));return(a,n)=>s.value?(l(),i("a",{key:0,href:s.value,target:"_blank",rel:"noopener"},h(e.text),9,zn)):(l(),i("span",qn,h(e.text),1))}}),Yn={class:"workspace-header__title"},Ln={class:"workspace-header__name"},Hn={class:"workspace-header__line workspace-header__sentence"},Xn={class:"workspace-header__count"},Kn={class:"workspace-header__count"},Gn={key:0},Jn={class:"workspace-header__count"},Zn={key:0,class:"workspace-header__line workspace-header__strip"},Qn={class:"workspace-header__count"},eo={class:"workspace-header__count"},to={class:"workspace-header__count"},so={class:"workspace-header__count"},no={class:"workspace-header__line workspace-header__give"},oo={class:"workspace-header__links"},ao={class:"workspace-header__tiles"},lo={class:"workspace-header__tile workspace-header__tile--you"},ro={class:"workspace-header__tile-value"},io={class:"workspace-header__tile"},co={class:"workspace-header__tile-value"},uo={class:"workspace-header__tile-value"},po={class:"workspace-header__tile"},_o={class:"workspace-header__tile-value"},ho=W({__name:"WorkspaceHeader",setup(e){const{collapsed:t,toggle:s}=Ut("header");return(a,n)=>(l(),i("header",{id:"summary",class:U(["island workspace-header",{"workspace-header--collapsed":p(t)}])},[o("div",Yn,[o("h1",Ln,h(p(m).workspace.name||"Whose Turn"),1),o("p",Hn,[o("b",Xn,h(p(H).mine),1),n[3]||(n[3]=F(" waiting on you, ",-1)),o("b",Kn,h(p(H).others),1),n[4]||(n[4]=F(" waiting on someone else",-1)),p(H).failing?(l(),i("span",Gn,[n[1]||(n[1]=F(", ",-1)),o("b",Jn,h(p(H).failing),1),n[2]||(n[2]=F(" failing",-1))])):C("",!0),n[5]||(n[5]=F(". ",-1))]),p(t)?(l(),i("p",Zn,[n[6]||(n[6]=F(" Your move ",-1)),o("b",Qn,h(p(H).mine),1),n[7]||(n[7]=F(" · Waiting on others ",-1)),o("b",eo,h(p(H).others),1),n[8]||(n[8]=F(" · Failing ",-1)),o("b",to,h(p(H).failing),1),n[9]||(n[9]=F(" · Done ",-1)),o("b",so,h(p(H).done),1)])):C("",!0),o("p",no,[O(de,{query:{ws:p(m).workspace.id,kind:"workspace"},big:""},null,8,["query"]),n[10]||(n[10]=o("span",{class:"ai-hint"},"AI: copy this workspace",-1)),o("span",oo,[(l(!0),i(V,null,N(p(m).workspace.links||[],r=>(l(),q(se,{key:r.url,text:r.label,url:r.url,title:r.url},null,8,["text","url","title"]))),128))])])]),o("button",{type:"button",class:U(["fold-toggle workspace-header__fold",{"fold-toggle--folded":p(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]=K((...r)=>p(s)&&p(s)(...r),["stop"]))}," ▾ ",2),o("div",ao,[o("div",lo,[n[11]||(n[11]=o("span",{class:"workspace-header__tile-label"},"Your move",-1)),o("strong",ro,h(p(H).mine),1)]),o("div",io,[n[12]||(n[12]=o("span",{class:"workspace-header__tile-label"},"Waiting on others",-1)),o("strong",co,h(p(H).others),1)]),o("div",{class:U(["workspace-header__tile",{"workspace-header__tile--fail":p(H).failing}])},[n[13]||(n[13]=o("span",{class:"workspace-header__tile-label"},"Open PRs with failing checks",-1)),o("strong",uo,h(p(H).failing),1)],2),o("div",po,[n[14]||(n[14]=o("span",{class:"workspace-header__tile-label"},"Done in the last 30 days",-1)),o("strong",_o,h(p(H).done),1)])])],2))}}),fo=j(ho,[["__scopeId","data-v-0f4b6d6e"]]),Ze=e=>{X(()=>b.reconnects,e)},vo=["id"],mo={class:"island__head"},ko={class:"island__title"},yo={class:"island__note"},go={class:"island__note-text"},bo=["data-tip"],wo=["aria-label"],xo={class:"island__body"},$o=W({__name:"Island",props:{id:{},title:{},note:{default:""},tip:{default:""},column:{type:Boolean}},setup(e){const t=e,{collapsed:s,toggle:a}=Ut(t.id);return(n,r)=>(l(),i("section",{id:e.id,class:U(["island",{"island--collapsed":p(s),"island--column":e.column}])},[o("div",mo,[o("h2",ko,[F(h(e.title)+" ",1),Ue(n.$slots,"head",{},void 0)]),o("p",yo,[o("span",go,h(e.note),1),e.tip?(l(),i("button",{key:0,type:"button",class:"hint-icon","aria-label":"More info","data-tip":e.tip}," ? ",8,bo)):C("",!0),o("button",{type:"button",class:U(["fold-toggle",{"fold-toggle--folded":p(s)}]),"aria-label":"Collapse or expand "+e.id,"data-tip":"Fold this panel; it stays folded until you open it again",onClick:r[0]||(r[0]=K((...c)=>p(a)&&p(a)(...c),["stop"]))}," ▾ ",10,wo)])]),o("div",xo,[Ue(n.$slots,"default",{},void 0)])],10,vo))}}),Re=j($o,[["__scopeId","data-v-a03b7f62"]]),Co=["aria-label"],So=["aria-pressed","onClick"],To={key:0,class:"segmented__count"},Mo=W({__name:"Segmented",props:We({options:{},label:{default:""},chips:{type:Boolean}},{modelValue:{required:!0},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const t=vt(e,"modelValue");return(s,a)=>(l(),i("span",{class:U(["segmented",{"segmented--chips":e.chips}]),role:"group","aria-label":e.label},[(l(!0),i(V,null,N(e.options,n=>(l(),i("button",{key:n.id,type:"button",class:"segmented__option","aria-pressed":t.value===n.id,onClick:r=>t.value=n.id},[F(h(n.label),1),n.count!==void 0?(l(),i("b",To,h(n.count),1)):C("",!0)],8,So))),128))],10,Co))}}),Se=j(Mo,[["__scopeId","data-v-7901b5d6"]]),Ao={class:"ticket-cell"},Ro={class:"ticket-cell__summary"},Eo=W({__name:"TicketCell",props:{row:{}},setup(e){return(t,s)=>(l(),i("td",Ao,[O(se,{text:e.row.key,url:e.row.ticket_url,class:"ticket-key"},null,8,["text","url"]),o("span",Ro,h(e.row.summary),1)]))}}),Qe=j(Eo,[["__scopeId","data-v-8436de88"]]),Oo={key:0,class:"pr-cell"},Io={key:1,class:"pill"},Do=W({__name:"PrCell",props:{row:{}},setup(e){return(t,s)=>(l(),i("td",null,[e.row.pr?(l(),i("span",Oo,[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?(l(),i("span",{key:0,class:U(["pill",p(he)("pill",p(ms)(e.row.checks))])},h(e.row.checks),3)):e.row.pr_state?(l(),i("span",Io,h(e.row.pr_state.toLowerCase()),1)):C("",!0)])):C("",!0)]))}}),Wt=j(Do,[["__scopeId","data-v-75c443c3"]]),Bo=["open"],Vo={class:"recently-done__summary"},Uo={class:"recently-done__count"},Wo={class:"recently-done__list"},No={class:"table"},jo=["data-tip"],Po={class:"table__actions"},Fo=W({__name:"RecentlyDone",setup(e){const t=Te("wt-recent-open",!1,{writeDefaults:!1,serializer:{read:n=>n==="1",write:n=>n?"1":"0"}}),s=n=>{t.value=n.target.open},a=g(()=>(ce.value,ks(m.turn).map(n=>({row:n,since:Je(n.status_since||n.updated)}))));return(n,r)=>a.value.length?(l(),i("details",{key:0,class:"recently-done",open:p(t),onToggle:s},[o("summary",Vo,[r[0]||(r[0]=F(" Recently done ",-1)),o("span",Uo,h(a.value.length),1)]),o("div",Wo,[o("table",No,[r[1]||(r[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,[(l(!0),i(V,null,N(a.value,({row:c,since:u})=>(l(),i("tr",{key:c.row_id},[O(Qe,{row:c},null,8,["row"]),o("td",null,[o("span",{class:U(["pill",p(he)("pill",p($t)(c.status_group))])},h(c.status),3)]),o("td",null,[o("span",{class:"age","data-tip":"Done "+p(Ge)(c.status_since||c.updated)},h(u.text),9,jo)]),o("td",Po,[O(de,{class:"table__ai",query:{ws:p(m).workspace.id,kind:"row",ref:c.row_id}},null,8,["query"])])]))),128))])])])],40,Bo)):C("",!0)}}),zo=j(Fo,[["__scopeId","data-v-12ee20e8"]]),qo=13,Yo=W({__name:"RowScroll",props:{floor:{type:Boolean}},setup(e){const t=I(null),s=I(null),{y:a}=us(t),{height:n}=ot(t),{height:r}=ot(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)||[]]},u=()=>{const y=t.value;return y?y.getBoundingClientRect().top+y.clientTop+y.clientHeight:0},v=g(()=>(a.value,n.value,r.value,ys(c().map(y=>y.getBoundingClientRect().bottom),u(),qo))),_=()=>{var d,k;const y=t.value,A=c(),E=A[A.length-v.value];if(!y||!E)return;const S=((k=(d=s.value)==null?void 0:d.querySelector("thead"))==null?void 0:k.getBoundingClientRect().bottom)??y.getBoundingClientRect().top;y.scrollBy({top:E.getBoundingClientRect().top-S,behavior:"smooth"})};return(y,A)=>(l(),i(V,null,[o("div",{ref_key:"scroller",ref:t,class:U(["row-scroll",{"row-scroll--floor":e.floor}])},[o("div",{ref_key:"content",ref:s},[Ue(y.$slots,"default",{},void 0)],512)],2),v.value?(l(),i("button",{key:0,type:"button",class:"row-scroll__more",onClick:_}," +"+h(v.value)+" more ",1)):C("",!0)],64))}}),Nt=j(Yo,[["__scopeId","data-v-7bd9ace7"]]),Lo=I("open"),Ho=()=>Lo,Xo={class:"table"},Ko={class:"hint-row"},Go={class:"your-move__next"},Jo=["data-tip"],Zo=["data-tip"],Qo=["data-tip"],ea={key:0,class:"alert alert--inline your-move__problem",role:"alert"},ta={class:"table__actions"},sa=["aria-label","onClick"],na={key:0,class:"empty"},oa=W({__name:"YourMove",setup(e){const t=Ho(),s=g(()=>{ce.value;const u=gs(m.turn,t.value);return{...u,rows:u.rows.map(v=>({...v,age:Je(v.row.activity_at)}))}}),a=g(()=>[{id:"open",label:"Open",count:s.value.open},{id:"handled",label:"Handled",count:s.value.handled}]),n=g(()=>{if(t.value==="handled")return"Nothing marked as handled.";const{noData:u,failing:v}=Vt(m.health,m.polled_at,m.workspace.connectors.length,b.live);return u?"No data: "+v.join(", ")+(v.length===1?" is":" are")+" failing. Check Settings.":"Nothing is waiting on you."}),r=Ye(new Map);Ze(()=>r.clear());const c=async u=>{const v=u.row.row_id,_=await ze({handled:{[v]:u.handled?null:new Date().toISOString()}});_?r.set(v,_):r.delete(v)};return(u,v)=>(l(),q(Re,{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(Se,{modelValue:p(t),"onUpdate:modelValue":v[0]||(v[0]=_=>Gt(t)?t.value=_:null),options:a.value,label:"Show",class:"your-move__view"},null,8,["modelValue","options"])]),default:te(()=>[O(Nt,{floor:""},{default:te(()=>[o("table",Xo,[v[1]||(v[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,[(l(!0),i(V,null,N(s.value.rows,_=>(l(),i("tr",{key:_.row.row_id,class:U(["your-move__row",{"your-move__row--handled":_.handled}])},[O(Qe,{row:_.row},null,8,["row"]),o("td",null,[o("span",Ko,[o("span",Go,h(_.row.next),1),_.age.text?(l(),i("span",{key:0,class:U(["age",{"age--old":_.age.old}]),"data-tip":"Waiting since "+p(Ge)(_.row.activity_at)},h(_.age.text),11,Jo)):C("",!0),_.row.why?(l(),i("button",{key:1,type:"button",class:"hint-icon","aria-label":"Why this is your move","data-tip":_.row.why}," ? ",8,Zo)):C("",!0),_.handled?(l(),i("span",{key:2,class:"handled-chip","data-tip":"Marked "+p(ne)(p(m).marks.handled[_.row.row_id]||"")+". Comes back in 7 days if it is still open, or sooner when something new happens."},"handled",8,Qo)):C("",!0)]),r.get(_.row.row_id)?(l(),i("p",ea,h(r.get(_.row.row_id)),1)):C("",!0)]),O(Wt,{row:_.row},null,8,["row"]),o("td",{class:U(["hide-narrow",{"your-move__quote":_.row.comment}])},[_.row.comment?(l(),q(se,{key:0,text:_.row.comment,url:_.row.comment_url},null,8,["text","url"])):C("",!0)],2),o("td",ta,[O(de,{class:"table__ai",query:{ws:p(m).workspace.id,kind:"row",ref:_.row.row_id}},null,8,["query"]),o("button",{type:"button",class:U(["your-move__mark",{"your-move__mark--undo":_.handled}]),"aria-label":(_.handled?"Move back to open: ":"Mark as handled: ")+_.row.key,onClick:K(y=>c(_),["stop"])},h(_.handled?"Undo":"Handled"),11,sa)])],2))),128))])])]),_:1}),s.value.rows.length?C("",!0):(l(),i("p",na,h(n.value),1)),p(t)==="open"?(l(),q(zo,{key:1})):C("",!0)]),_:1}))}}),aa=j(oa,[["__scopeId","data-v-e3e1502a"]]),la=I(!1),ra=()=>la,ia={class:"todos__add"},ca={key:0,class:"alert alert--inline",role:"alert"},da={class:"todos__list"},ua=["aria-checked","aria-label","onClick"],pa={class:"todos__text"},_a={class:"todos__meta"},ha=["onClick"],fa={key:0,class:"empty todos__empty"},va=W({__name:"Todos",setup(e){const t=ra(),s=I(""),a=g(()=>(m.todos||[]).filter(d=>!d.done)),n=g(()=>(m.todos||[]).filter(d=>d.done)),r=g(()=>new RegExp(m.workspace.key_pattern||"(?!)","g")),c=d=>{const k=[];let f=0;const B=d.text;for(const z of B.matchAll(r.value))z.index>f&&k.push({text:B.slice(f,z.index)}),k.push({text:z[0],url:z[0]===d.key?d.ticket_url:""}),f=z.index+z[0].length;return f<B.length&&k.push({text:B.slice(f)}),k},u=g(()=>(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)})))),v=I("");Ze(()=>{v.value=""});const _=async d=>(v.value=await nn(d),!v.value),y=async()=>{const d=s.value.trim();d&&await _({action:"add",text:d})&&s.value.trim()===d&&(s.value="")},A=d=>_({action:d.done?"undo":"done",id:d.id}),E=d=>_({action:"delete",id:d.id}),R=g({get:()=>t.value?"done":"open",set:d=>{t.value=d==="done"}}),S=g(()=>[{id:"open",label:"Open",count:a.value.length},{id:"done",label:"Done",count:n.value.length}]);return(d,k)=>(l(),q(Re,{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(Se,{modelValue:R.value,"onUpdate:modelValue":k[0]||(k[0]=f=>R.value=f),options:S.value,label:"Show",class:"todos__view"},null,8,["modelValue","options"])]),default:te(()=>[o("div",ia,[Le(o("input",{"onUpdate:modelValue":k[1]||(k[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:Ne(y,["enter"])},null,544),[[He,s.value]]),o("button",{type:"button",class:"btn btn--tonal",onClick:y},"Add")]),v.value?(l(),i("p",ca,h(v.value),1)):C("",!0),o("ul",da,[(l(!0),i(V,null,N(u.value,({todo:f,parts:B,meta:z})=>(l(),i("li",{key:f.id,class:U(["todos__item",{"todos__item--done":f.done}])},[o("button",{type:"button",class:U(["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,ua),o("span",pa,[(l(!0),i(V,null,N(B,(L,J)=>(l(),i(V,{key:J},[L.url!==void 0?(l(),q(se,{key:0,text:L.text,url:L.url},null,8,["text","url"])):(l(),i(V,{key:1},[F(h(L.text),1)],64))],64))),128))]),o("span",_a,h(z),1),o("button",{type:"button",class:"todos__delete","aria-label":"Delete to-do",onClick:L=>E(f)}," × ",8,ha)],2))),128)),u.value.length?C("",!0):(l(),i("span",fa,h(p(t)?"Nothing here yet.":"Nothing to do. Add one above, or ask Claude."),1))])]),_:1}))}}),ma=j(va,[["__scopeId","data-v-6f034e1e"]]),ka={key:0,class:"empty"},ya={class:"table waiting__table"},ga=["data-tip"],ba={class:"hide-narrow"},wa={key:0,class:"hint-row"},xa=["data-tip"],$a={class:"table__actions"},Ca=W({__name:"Waiting",setup(e){const t=g(()=>(ce.value,bs(m.turn).map(s=>({row:s,since:s.status_since?Je(s.status_since):null}))));return(s,a)=>(l(),q(Re,{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?(l(),q(Nt,{key:1},{default:te(()=>[o("table",ya,[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,[(l(!0),i(V,null,N(t.value,({row:n,since:r})=>(l(),i("tr",{key:n.row_id},[O(Qe,{row:n},null,8,["row"]),o("td",null,[o("span",{class:U(["pill",p(he)("pill",p($t)(n.status_group))])},h(n.status),3)]),o("td",null,[F(h(n.whose)+" ",1),r?(l(),i("span",{key:0,class:U(["age",{"age--old":r.hours>=72}]),"data-tip":n.status+" since "+p(Ge)(n.status_since)},h(r.text),11,ga)):C("",!0)]),o("td",ba,[n.hold_url?(l(),i("span",wa,[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,xa)])):(l(),i(V,{key:1},[F(h(n.next),1)],64))]),O(Wt,{row:n},null,8,["row"]),o("td",$a,[O(de,{class:"table__ai",query:{ws:p(m).workspace.id,kind:"row",ref:n.row_id}},null,8,["query"])])]))),128))])])]),_:1})):(l(),i("p",ka,"Nothing is waiting on someone else."))]),_:1}))}}),Sa=j(Ca,[["__scopeId","data-v-a632447c"]]),Ta=(e,t)=>e.seen instanceof Set?e.seen.has(t):e.seen.includes(t),jt=(e,t)=>new Date(e.at).getTime()>(t.base||0)&&!Ta(t,e.id),Ma=(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))},Aa=e=>e.key||e.ref||e.id,Ra=(e,t)=>{const s=new Map;for(const a of e){const n=Aa(a),r=s.get(n)||{id:n,items:[],newest:a.at,unread:0,title:""};s.set(n,r),r.items.push(a),r.title||(r.title=a.title||""),a.at>r.newest&&(r.newest=a.at),jt(a,t)&&(r.unread+=1)}return[...s.values()].sort((a,n)=>String(n.newest).localeCompare(String(a.newest)))},Ea=e=>new Set(e.filter(t=>t.group==="you"&&!t.handled).map(t=>t.key)),Oa=(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}`:"")},Ia=e=>/fail|changes/.test(e)?"bad":/approved|merged|passed/.test(e)?"ok":"",Da=e=>/comment|approved|changes|mentioned|review requested/.test(e.kind)&&String(e.body||"").trim()!=="",Pt=e=>e.ref!==e.key&&(e.key||e.title)||"",Ba=e=>[Pt(e),e.text===e.title?"":e.text].filter(Boolean).join(" · "),Va=e=>e.type==="github"&&e.label==="GitHub"?"GH":Bt(e.label),Ua={class:"feed-item__body"},Wa={class:"feed-item__top"},Na={key:0,class:"feed-item__who"},ja={key:1,class:"at-you"},Pa={class:"feed-item__right"},Fa={class:"timestamp feed-item__when"},za=["aria-label","aria-expanded"],qa={key:0,class:"feed-item__text"},Ya={class:"feed-item__full"},La={class:"feed-item__actions"},Ha={key:1,class:"feed-item__text"},Xa=W({__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=g(()=>Va(s.source)),r=g(()=>Pt(s.item)),c=g(()=>Ba(s.item)),u=g(()=>Ia(s.item.kind)),v=g(()=>u.value||(s.source.type==="github"?"":"jira")),_=g(()=>Da(s.item)),y=g(()=>_.value&&s.expanded),A=g(()=>(ce.value,ne(s.item.at))),E=()=>{s.fresh&&a("seen",s.item.id)},R=()=>{a("toggle",s.item.id),E()};return(S,d)=>(l(),i("li",{class:U(["feed-item",{"feed-item--new":e.fresh,"feed-item--lit":e.lit,"feed-item--open":y.value}]),onClick:E},[o("span",{class:U(["feed-item__icon",p(he)("feed-item__icon",v.value)])},h(n.value),3),o("div",Ua,[o("div",Wa,[O(se,{text:e.item.ref,url:e.item.url},null,8,["text","url"]),o("span",{class:U(["feed-item__kind",{"feed-item__kind--bad":u.value==="bad"}])},h(e.item.kind),3),e.item.who?(l(),i("span",Na,h(e.item.who),1)):C("",!0),e.item.mention?(l(),i("span",ja,"@you")):C("",!0),o("span",Pa,[o("span",Fa,h(A.value),1),_.value?(l(),i("button",{key:0,type:"button",class:"feed-item__toggle","aria-label":y.value?"Collapse":"Expand","aria-expanded":y.value,onClick:K(R,["stop"])},null,8,za)):C("",!0)])]),y.value?(l(),i(V,{key:0},[r.value?(l(),i("span",qa,h(r.value),1)):C("",!0),o("div",Ya,h(e.item.body),1),o("div",La,[o("button",{type:"button",class:"btn btn--tonal",onClick:d[0]||(d[0]=K(k=>a("open",e.item),["stop"]))}," Open formatted "),O(de,{query:{ws:p(m).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?(l(),i("span",Ha,h(c.value),1)):C("",!0)])],2))}}),ht=j(Xa,[["__scopeId","data-v-27e556e1"]]),Ka=Ye({source:"all",view:Te("wt-feed-view","ticket",{writeDefaults:!1}),query:"",openGroups:new Set}),Ga=()=>Ka,Ja={key:0,class:"feed__new-count"},Za={class:"feed__bar"},Qa={key:0,class:"alert alert--inline",role:"alert"},el={class:"feed__search"},tl={class:"feed__search-count"},sl={key:1,class:"empty"},nl={key:2,class:"feed__list"},ol=["title","onClick"],al=["aria-expanded","aria-label"],ll={class:"feed__group-text"},rl={class:"feed__group-key"},il={key:0,class:"feed__group-sum"},cl={class:"feed__group-meta"},dl={class:"feed__group-kinds"},ul={class:"timestamp feed__group-when"},pl={key:0,class:"feed__children"},_l=W({__name:"Feed",emits:["open"],setup(e,{emit:t}){const s=t,a=Ga(),n=[{id:"ticket",label:"By ticket"},{id:"flat",label:"Flat"}],r=g(()=>m.workspace.connectors||[]),c=g(()=>Object.fromEntries(r.value.map(x=>[x.id,x]))),u=x=>c.value[x]||{id:x,type:x,label:x},v=g(()=>r.value.length>1?[{id:"all",label:"All"}].concat(r.value.map(x=>({id:x.id,label:x.label}))):[]),_=g({get:()=>v.value.some(x=>x.id===a.source)?a.source:"all",set:x=>{a.source=x}}),y=g(()=>({base:m.marks.base,seen:new Set(m.marks.seen)})),A=x=>jt(x,y.value),E=g(()=>m.feed.filter(A).length),R=g(()=>m.feed.filter(x=>_.value==="all"||x.source===_.value)),S=g(()=>R.value.filter(x=>Ma(x,a.query)).slice(0,200)),d=g(()=>new Map(m.turn.map(x=>[x.key,x]))),k=g(()=>Ea(m.turn)),f=g(()=>(ce.value,Ra(S.value,y.value).map(x=>({...x,row:d.value.get(x.id),move:k.value.has(x.id),when:ne(x.newest),kinds:Oa(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(b.open,x),J=x=>ze({seen:[x]}).then(ie),ue=I("");Ze(()=>{ue.value=""});const Q=async()=>{ue.value=await ze({seen:m.feed.map(x=>x.id),base:Date.now()})};return(x,Y)=>(l(),q(Re,{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?(l(),i("span",Ja,h(E.value)+" new",1)):C("",!0)]),default:te(()=>[o("div",Za,[v.value.length?(l(),q(Se,{key:0,modelValue:_.value,"onUpdate:modelValue":Y[0]||(Y[0]=M=>_.value=M),options:v.value,label:"Feed source"},null,8,["modelValue","options"])):C("",!0),O(Se,{modelValue:p(a).view,"onUpdate:modelValue":Y[1]||(Y[1]=M=>p(a).view=M),options:n,label:"Feed view"},null,8,["modelValue"])]),ue.value?(l(),i("p",Qa,h(ue.value),1)):C("",!0),o("div",el,[Le(o("input",{"onUpdate:modelValue":Y[2]||(Y[2]=M=>p(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,p(a).query,void 0,{trim:!0}]]),o("span",tl,h(p(a).query?S.value.length+" of "+R.value.length:""),1),o("button",{type:"button",class:"feed__mark-read",onClick:Q},"Mark all read")]),p(m).feed.length?(l(),i("ol",nl,[p(a).view==="ticket"?(l(!0),i(V,{key:0},N(f.value,M=>(l(),i("li",{key:M.id,class:U(["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,al),o("div",ll,[o("div",rl,[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?(l(),i("span",il,h(M.row?M.row.summary:M.title),1)):C("",!0)]),o("div",cl,[o("span",{class:U(["feed__group-count",{"feed__group-count--new":M.unread}])},h(M.unread?M.unread+" new":M.items.length),3),o("span",dl,h(M.kinds),1),o("span",ul,h(M.when),1)])]),O(de,{class:"feed__group-copy",query:{ws:p(m).workspace.id,kind:"thread",ref:M.id}},null,8,["query"])],8,ol),M.open?(l(),i("ul",pl,[(l(!0),i(V,null,N(M.items,Z=>(l(),q(ht,{key:Z.id,item:Z,source:u(Z.source),fresh:A(Z),lit:!!M.unread,expanded:p(b).open.has(Z.id),onOpen:Y[3]||(Y[3]=Ee=>s("open",Ee)),onToggle:L,onSeen:J},null,8,["item","source","fresh","lit","expanded"]))),128))])):C("",!0)],2))),128)):(l(!0),i(V,{key:1},N(S.value,M=>(l(),q(ht,{key:M.id,item:M,source:u(M.source),fresh:A(M),expanded:p(b).open.has(M.id),onOpen:Y[4]||(Y[4]=Z=>s("open",Z)),onToggle:L,onSeen:J},null,8,["item","source","fresh","expanded"]))),128))])):(l(),i("p",sl," No changes yet. New comments, reviews and status changes show up here. "))]),_:1}))}}),hl=j(_l,[["__scopeId","data-v-d92a7c74"]]),fl={id:"first-run",class:"island first-run","aria-labelledby":"first-run-title"},vl={class:"first-run__text"},ml=W({__name:"FirstRun",setup(e){const t=()=>Ks("connectors");return(s,a)=>(l(),i("section",fl,[a[1]||(a[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",vl,[o("b",null,h(p(m).workspace.name),1),a[0]||(a[0]=F(" has no sources yet, so there is nothing to watch. Connect a repository or a tracker. ",-1))]),o("div",{class:"first-run__actions"},[o("button",{type:"button",class:"btn btn--tonal",onClick:t},"Add a source")]),a[2]||(a[2]=o("p",{class:"first-run__text first-run__ai"},' Or ask Claude: "add my GitHub repository to Whose Turn." ',-1))]))}}),kl=j(ml,[["__scopeId","data-v-84359c2d"]]),yl=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"),ft=(e,t)=>{const s=Ke(t);return s?{type:"link",text:e,href:s}:{type:"text",text:e}},gl=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 ft(t.slice(1,s),t.slice(s+2,-1))}return e[4]?ft(t,t):e[5]?{type:"mention",text:t}:{type:"em",text:t.slice(1,-1)}},ye=e=>{const t=new RegExp(yl),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(gl(n)),a=n.index+n[0].length;return a<e.length&&s.push({type:"text",text:e.slice(a)}),s},bl=e=>{const t=String(e||"").replace(/<!--[\s\S]*?-->/g,"").split(`
|
|
2
|
+
`),s=[];let a=[],n=null;const r=()=>{a.length&&(s.push({type:"paragraph",lines:a.map(ye)}),a=[]),n=null};for(let c=0;c<t.length;c++){const u=t[c];if(/^\s*```/.test(u)){r();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 v=u.match(/^(#{1,6})\s+(.*)$/);if(v){r(),s.push({type:"heading",level:v[1].length<=2?4:5,inline:ye(v[2])});continue}const _=u.match(/^>\s?(.*)$/);if(_){r(),s.push({type:"quote",inline:ye(_[1])});continue}const y=u.match(/^\s*([-*+]|\d+[.)])\s+(.*)$/);if(y){const A=n;a.length&&(r(),n=A);const E=/\d/.test(y[1]),R=n,S=R&&R.ordered===E?R:{type:"list",ordered:E,items:[]};S!==R&&s.push(S),n=S,S.items.push(ye(y[2]));continue}if(!u.trim()){r();continue}n=null,a.push(u)}return r(),s},be=(e,t,s="")=>{const a=document.createElement(e);return a.textContent=t,s&&(a.className=s),a},wl=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)},Ft=(e,t)=>{e.forEach(s=>t.append(wl(s)))},Ve=(e,t)=>{const s=document.createElement(e);return Ft(t,s),s},xl=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")),Ft(s,t)}),t},$l=e=>{const t=document.createDocumentFragment();return bl(e).forEach(s=>t.append(xl(s))),t},Cl=W({__name:"MarkdownView",props:{source:{default:""}},setup(e){const t=e,s=I(null);return Jt(()=>{s.value&&s.value.replaceChildren($l(t.source))}),(a,n)=>(l(),i("div",{ref_key:"host",ref:s,class:"markdown-view"},null,512))}}),Sl=j(Cl,[["__scopeId","data-v-dcf42e95"]]),Tl={class:"modal__card"},Ml={class:"modal__head"},Al={id:"modal-title",class:"modal__title"},Rl=["href"],El=W({__name:"CommentModal",setup(e){const t=g(()=>b.modal),s=g(()=>t.value?[t.value.ref,t.value.kind,t.value.who].filter(Boolean).join(" · "):""),a=g(()=>t.value?Ke(t.value.url):""),n=g(()=>{var u;const c=(m.workspace.connectors||[]).find(v=>{var _;return v.id===((_=t.value)==null?void 0:_.source)});return"Open in "+(c?c.label:(u=t.value)==null?void 0:u.source)}),r=()=>{b.modal=null};return(c,u)=>t.value?(l(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"modal-title",onClick:K(r,["self"])},[o("div",Tl,[o("div",Ml,[o("h3",Al,h(s.value),1),a.value?(l(),i("a",{key:0,class:"btn",href:a.value,target:"_blank",rel:"noopener"},h(n.value),9,Rl)):C("",!0),o("button",{type:"button",class:"btn btn--tonal",onClick:r},"Close")]),O(Sl,{source:t.value.body||t.value.text||""},null,8,["source"])])])):C("",!0)}}),Ol={class:"modal__card feedback-sheet__card"},Il={class:"modal__head"},Dl={class:"modal__meta"},Bl=["aria-disabled"],Vl={key:0,class:"alert"},Ul={class:"sheet-form"},Wl=["placeholder","onKeydown"],Nl={key:0,class:"feedback-sheet__note"},jl={key:1,class:"feedback-sheet__note"},Pl={key:2,class:"empty"},Fl=["href"],zl={key:3,class:"feedback-sheet__reports"},ql=["href"],Yl={class:"feedback-sheet__report-title"},Ll={class:"timestamp"},Hl=W({__name:"FeedbackSheet",setup(e){const t=I(""),s=I(null),a=I(!1),n=I([]),r=I(null),c=I(""),u=I(!0),v=Me("",1600),_=I([]),y=d=>"/report?id="+encodeURIComponent(d),A=()=>Ae("/api/feedback").then(d=>{u.value=d.configured,c.value=d.configured?"Linear · "+d.project:"not connected",_.value=d.reports||[]},()=>{n.value=[we+"; past reports cannot be loaded."]}),E=()=>{b.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(()=>{v.value="sent"},()=>{v.value="failed"})},S=()=>{if(!u.value||a.value)return;const d=t.value.trim();if(!d){n.value=["Write at least one line."];return}a.value=!0,ae("/api/feedback",{text:d,from:"the window",ws:m.workspace.id,kind_ref:{kind:"workspace",ref:m.workspace.id}}).then(k=>{if(a.value=!1,k.error){n.value=Ct(k.error);return}r.value=k.issue||null,t.value="",_.value=k.reports||_.value}).catch(k=>{a.value=!1,n.value=[oe(k,{refused:"Not sent",unreachable:je})]})};return X(()=>b.feedback,d=>{d&&(n.value=[],r.value=null,A().then(()=>mt(()=>{var k;return(k=s.value)==null?void 0:k.focus()})))}),(d,k)=>p(b).feedback?(l(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"feedback-title",onClick:K(E,["self"])},[o("div",Ol,[o("div",Il,[k[1]||(k[1]=o("h3",{id:"feedback-title",class:"modal__title"},"To the Whose Turn backlog",-1)),o("span",Dl,h(c.value),1),o("button",{type:"button",class:"btn btn--tonal","aria-disabled":!u.value||a.value,onClick:S},h(a.value?"Composing…":"Send"),9,Bl),o("button",{type:"button",class:"btn",onClick:E},"Close")]),n.value.length?(l(),i("div",Vl,h(n.value.join(" · ")),1)):C("",!0),o("div",Ul,[Le(o("textarea",{ref_key:"field",ref:s,"onUpdate:modelValue":k[0]||(k[0]=f=>t.value=f),class:"input feedback-sheet__text",rows:"5",placeholder:"Describe the problem or the wish in your own words."+(u.value?" Cmd+Enter sends.":""),autocomplete:"off",onKeydown:[Ne(K(S,["meta","prevent"]),["enter"]),Ne(K(S,["ctrl","prevent"]),["enter"])]},null,40,Wl),[[He,t.value]]),u.value?(l(),i("p",jl," 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. ")):(l(),i("p",Nl,[k[2]||(k[2]=F(" 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},h(p(v)==="sent"?"Copied":p(v)==="failed"?"Could not copy":"Copy remark"),1)])),r.value?(l(),i("p",Pl,[k[3]||(k[3]=F(" Filed as ",-1)),o("a",{href:y(r.value.identifier),target:"_blank",rel:"noopener"},h(r.value.identifier),9,Fl),F(" · "+h(r.value.title),1)])):C("",!0),_.value.length?(l(),i("div",zl,[k[4]||(k[4]=o("h4",{class:"sheet-form__heading"},"Your reports",-1)),(l(!0),i(V,null,N(_.value,f=>(l(),i("div",{key:f.id,class:"feedback-sheet__report"},[o("a",{href:y(f.id),target:"_blank",rel:"noopener"},h(f.id),9,ql),o("span",Yl,h(f.title),1),o("span",{class:U(["pill",p(he)("pill",p(ws)(f.outcome))])},h(f.state||"filed"),3),o("span",Ll,h(p(ne)(f.changed_at||f.filed_at)),1)]))),128))])):C("",!0)])])])):C("",!0)}}),Xl=j(Hl,[["__scopeId","data-v-ec34adcc"]]),Kl={class:"field-row"},Gl={class:"field-row__label"},Jl={key:0,class:"field-row__check"},Zl=["checked"],Ql={key:2,class:"field-row__inline"},er=["value"],tr=["value"],sr=["value"],nr=["placeholder","value"],or={key:5,class:"field-row__hint"},ar=W({__name:"FieldRow",props:We({spec:{},placeholder:{default:""},hint:{default:""}},{modelValue:{type:[String,Array,Boolean]},modelModifiers:{}}),emits:We(["secret"],["update:modelValue"]),setup(e,{emit:t}){const s=e,a=t,n=vt(e,"modelValue"),r=/^#[0-9a-f]{6}$/i,c=()=>String(n.value??""),u=()=>n.value||[],v=R=>{n.value=R},_=()=>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=>v(R.split(",").map(S=>S.trim()).filter(Boolean));return(R,S)=>(l(),i("div",Kl,[o("label",Gl,h(e.spec.label)+h(e.spec.required?" *":""),1),e.spec.boolean?(l(),i("label",Jl,[o("input",{type:"checkbox",checked:_(),onChange:S[0]||(S[0]=d=>y(d.target.checked))},null,40,Zl),F(" "+h(e.hint||e.spec.hint),1)])):e.spec.secret?(l(),i("input",{key:1,class:"input",type:"password",placeholder:"paste to replace",autocomplete:"off",onInput:S[1]||(S[1]=d=>a("secret",e.spec.key,A(d)))},null,32)):e.spec.colour?(l(),i("div",Ql,[o("input",{class:"input field-row__colour",type:"color",value:r.test(c())?c():"#888888",onInput:S[2]||(S[2]=d=>v(A(d)))},null,40,er),o("input",{class:"input",type:"text",placeholder:"#rrggbb",value:c(),onInput:S[3]||(S[3]=d=>v(A(d)))},null,40,tr),o("button",{type:"button",class:"btn",onClick:S[4]||(S[4]=d=>v(""))},"None")])):e.spec.list?(l(),i("input",{key:3,class:"input",type:"text",value:u().join(", "),onInput:S[5]||(S[5]=d=>E(A(d)))},null,40,sr)):(l(),i("input",{key:4,class:"input",type:"text",placeholder:e.placeholder,value:c(),onInput:S[6]||(S[6]=d=>v(A(d)))},null,40,nr)),!e.spec.boolean&&(e.hint||e.spec.hint)?(l(),i("small",or,h(e.hint||e.spec.hint),1)):C("",!0)]))}}),re=j(ar,[["__scopeId","data-v-2ba0ba86"]]),lr={class:"modal__card settings-sheet__card"},rr={class:"modal__head"},ir={class:"modal__meta"},cr={key:0,class:"settings-sheet__dirty"},dr={key:0,class:"settings-sheet__confirm",role:"alertdialog"},ur={class:"settings-sheet__confirm-text"},pr={key:1,class:"alert settings-sheet__alert",role:"alert"},_r={key:2,class:"alert settings-sheet__alert"},hr={key:3,class:"settings-sheet__body"},fr={class:"settings-sheet__list"},vr=["aria-current","onClick"],mr={class:"sheet-form"},kr={key:0,class:"empty"},yr={ref:"connectors-head",class:"sheet-form__heading"},gr={class:"settings-sheet__connector-head"},br={class:"settings-sheet__connector-name"},wr=["onClick"],xr={class:"settings-sheet__actions"},$r=["onClick"],Cr={key:0,class:"settings-sheet__rules"},Sr={class:"settings-sheet__rule-when"},Tr={class:"settings-sheet__rule-then"},Mr={key:1,class:"empty"},Ar=W({__name:"SettingsSheet",setup(e){const t=I([]),s=I(0),a=I([]),n=I({workspace:[],theme:[],rules:[],statuses:[]}),r=I(""),c=I([]),u=I({}),v=Zt("connectors-head"),_=new Map,y=I(0),A=I(""),E=I(null),R=I(""),S=g(()=>b.settings&&!R.value&&(y.value>0||ct(t.value)!==A.value)),d={key:"id",label:"Entry id"},k={key:"label",label:"Label"},f=g(()=>t.value[s.value]),B=g(()=>(f.value&&u.value[f.value.id]||[]).map(zs)),z=w=>a.value.find(T=>T.type===w.type)||{type:w.type,label:w.type,fields:[]},L=g(()=>t.value.map((w,T)=>({uid:it(w),label:w.name||w.id||"Workspace "+(T+1)}))),J=g(()=>{var w;return(((w=f.value)==null?void 0:w.connectors)||[]).map(T=>({entry:T,uid:it(T),spec:z(T)}))}),ue=w=>{var $;const T=($=m.workspaces.find(P=>P.id===w))==null?void 0:$.theme_seed;return T?`empty: ${T}, derived from the workspace id`:"empty derives one from the workspace id"},Q=(w,T)=>w[T],x=(w,T,$)=>{_.set(w,Object.assign(_.get(w)||{},{[T]:$})),y.value+=1},Y=()=>{t.value.push(lt({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})},Ee=w=>{var T;return(T=f.value)==null?void 0:T.connectors.push({type:w.type})},zt=w=>{f.value&&(_.delete(f.value.connectors[w]),f.value.connectors.splice(w,1))},qt=w=>{if(!f.value)return;const T=z(f.value.connectors[w]).label;E.value={text:`Remove the ${T} connector from ${f.value.name||f.value.id}?`,yes:"Remove",no:"Keep",run:()=>zt(w)}},Yt=()=>Ae("/api/config").then(w=>{R.value="",t.value=(w.workspaces||[]).map(lt),a.value=w.connectors||[],n.value=w.fields,_.clear(),y.value=0,E.value=null,s.value=Math.max(0,t.value.findIndex(T=>T.id===m.workspace.id)),r.value=w.config_file||"",c.value=w.problems||[],u.value=w.tables||{},A.value=ct(t.value)}).catch(()=>{R.value=we+"; settings cannot be loaded."}).then(mt).then(()=>{var w;b.settingsAt==="connectors"&&((w=v.value)==null||w.scrollIntoView({block:"start"})),b.settingsAt=""}),Oe=()=>{b.settings=!1},et=()=>{E.value={text:"Discard changes?",yes:"Discard",no:"Keep editing",run:Oe}},tt=()=>S.value?et():Oe(),st=w=>{const T=E.value;E.value=null,w&&(T==null||T.run())},Lt=async()=>{try{const w=await ae("/api/config",{workspaces:t.value.map(T=>Bs(T)),secrets:Us(t.value,_)});if(w.error){c.value=Ct(w.error);return}Oe(),It()}catch(w){c.value=[oe(w,{unreachable:we+"; your changes are kept, save again."})]}};return X(S,w=>{b.settingsDirty=w},{immediate:!0}),X(()=>b.settingsAsk,w=>{w&&(b.settingsAsk=!1,et())}),X(()=>b.settings,w=>{w&&Yt()}),(w,T)=>p(b).settings?(l(),i("div",{key:0,class:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"settings-title",onClick:K(tt,["self"])},[o("div",lr,[o("div",rr,[T[2]||(T[2]=o("h3",{id:"settings-title",class:"modal__title"},"Workspaces",-1)),o("span",ir,h(r.value),1),S.value?(l(),i("span",cr,"Unsaved changes")):C("",!0),R.value?C("",!0):(l(),i("button",{key:1,type:"button",class:"btn btn--tonal",onClick:Lt},"Save")),o("button",{type:"button",class:"btn",onClick:tt},"Close")]),E.value?(l(),i("div",dr,[o("span",ur,h(E.value.text),1),o("button",{type:"button",class:"btn btn--danger",onClick:T[0]||(T[0]=$=>st(!0))},h(E.value.yes),1),o("button",{type:"button",class:"btn",onClick:T[1]||(T[1]=$=>st(!1))},h(E.value.no),1)])):C("",!0),R.value?(l(),i("div",pr,h(R.value),1)):c.value.length?(l(),i("div",_r,h(c.value.join(" · ")),1)):C("",!0),R.value?C("",!0):(l(),i("div",hr,[o("ul",fr,[(l(!0),i(V,null,N(L.value,($,P)=>(l(),i("li",{key:$.uid},[o("button",{type:"button",class:"settings-sheet__workspace","aria-current":P===s.value,onClick:ve=>s.value=P},h($.label),9,vr)]))),128)),o("li",null,[o("button",{type:"button",class:"settings-sheet__workspace settings-sheet__workspace--add",onClick:Y}," + Add workspace ")])]),o("div",mr,[f.value?(l(),i(V,{key:1},[T[3]||(T[3]=o("h4",{class:"sheet-form__heading"},"Workspace",-1)),(l(!0),i(V,null,N(n.value.workspace,$=>(l(),q(re,{key:$.key,spec:$,"model-value":Q(f.value,$.key),"onUpdate:modelValue":P=>p(le)(f.value,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),T[4]||(T[4]=o("h4",{class:"sheet-form__heading"},"Theme",-1)),(l(!0),i(V,null,N(n.value.theme,$=>{var P;return l(),q(re,{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=>p(le)(f.value.theme,$.key,ve)},null,8,["spec","hint","model-value","onUpdate:modelValue"])}),128)),o("h4",yr,"Connectors",512),(l(!0),i(V,null,N(J.value,({entry:$,spec:P,uid:ve},Ht)=>(l(),i("div",{key:ve,class:"settings-sheet__connector"},[o("div",gr,[o("strong",br,h(P.label),1),o("button",{type:"button",class:"btn btn--danger",onClick:ee=>qt(Ht)}," Remove ",8,wr)]),O(re,{spec:d,placeholder:P.type,hint:"only needed when one type appears twice","model-value":Q($,"id"),"onUpdate:modelValue":ee=>p(le)($,"id",ee)},null,8,["placeholder","model-value","onUpdate:modelValue"]),O(re,{spec:k,placeholder:P.label,hint:"shown in the feed filter","model-value":Q($,"label"),"onUpdate:modelValue":ee=>p(le)($,"label",ee)},null,8,["placeholder","model-value","onUpdate:modelValue"]),(l(!0),i(V,null,N(P.fields,ee=>(l(),q(re,{key:ee.key,spec:ee,"model-value":Q($,ee.key),"onUpdate:modelValue":Ie=>p(le)($,ee.key,Ie),onSecret:(Ie,Xt)=>x($,Ie,Xt)},null,8,["spec","model-value","onUpdate:modelValue","onSecret"]))),128))]))),128)),o("div",xr,[(l(!0),i(V,null,N(a.value,$=>(l(),i("button",{key:$.type,type:"button",class:"btn",onClick:P=>Ee($)}," + "+h($.label),9,$r))),128))]),T[5]||(T[5]=o("h4",{class:"sheet-form__heading"},"Rules",-1)),(l(!0),i(V,null,N(n.value.rules,$=>(l(),q(re,{key:$.key,spec:$,"model-value":Q(f.value.rules,$.key),"onUpdate:modelValue":P=>p(le)(f.value.rules,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),(l(!0),i(V,null,N(n.value.statuses,$=>(l(),q(re,{key:$.key,spec:$,"model-value":Q(f.value.rules.statuses,$.key),"onUpdate:modelValue":P=>p(le)(f.value.rules.statuses,$.key,P)},null,8,["spec","model-value","onUpdate:modelValue"]))),128)),T[6]||(T[6]=o("h4",{class:"sheet-form__heading"},"Whose turn table",-1)),T[7]||(T[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?(l(),i("ol",Cr,[(l(!0),i(V,null,N(B.value,($,P)=>(l(),i("li",{key:P,class:"settings-sheet__rule"},[o("span",Sr,h($.when),1),o("span",Tr,h($.then),1)]))),128))])):(l(),i("p",Mr,"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)):(l(),i("p",kr,"No workspaces yet. Add one on the left."))])]))])])):C("",!0)}}),Rr=j(Ar,[["__scopeId","data-v-779d567d"]]),Er={class:"app__shell"},Or=["inert"],Ir={id:"wt",class:"dashboard dashboard--fit"},Dr=["inert"],Br={class:"dashboard__main"},Vr=W({__name:"App",setup(e){const t=g(()=>!!m.workspace.id&&!m.workspace.connectors.length),s=g(()=>Ot.value!==null);return X(dt,Os,{flush:"post"}),ps("Escape",Gs,{target:document}),Qt(()=>{Xs()}),(a,n)=>(l(),i("div",{class:"app",style:es(p(dt))},[O(xn,{inert:s.value,onSettings:n[0]||(n[0]=r=>p(b).settings=!0)},null,8,["inert"]),o("div",Er,[O(Fn,{inert:s.value,onReport:n[1]||(n[1]=r=>p(b).feedback=!0)},null,8,["inert"]),p(ut).length?(l(),i("div",{key:0,class:"alert",inert:s.value},h(p(ut).join(" · ")),9,Or)):C("",!0),o("div",Ir,[n[3]||(n[3]=o("canvas",{id:"backdrop",class:"dashboard__backdrop","aria-hidden":"true"},null,-1)),O(fo,{inert:s.value},null,8,["inert"]),t.value?(l(),q(kl,{key:0,inert:s.value},null,8,["inert"])):(l(),i("div",{key:1,class:"dashboard__layout",inert:s.value},[o("div",Br,[O(aa,{class:"dashboard__move"}),O(ma,{class:"dashboard__todo"}),O(Sa,{class:"dashboard__waiting"})]),O(hl,{class:"dashboard__feed",onOpen:n[2]||(n[2]=r=>p(b).modal=r)})],8,Dr)),O(Xl),O(Rr),O(El)])])],4))}}),Ur=j(Vr,[["__scopeId","data-v-f146b085"]]);ts(Ur).mount("#app");tn();Is();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as v,E as f,o as s,a as o,g as a,F as c,f as t,n as w,u as n,j as u,h,b as y,i as g,_ as k,H as x}from"./_plugin-vue_export-helper-DHr5RHTx.js";import{g as N,S as R,m as b,o as C,i as d}from"./bem-
|
|
1
|
+
import{d as v,E as f,o as s,a as o,g as a,F as c,f as t,n as w,u as n,j as u,h,b as y,i as g,_ as k,H as x}from"./_plugin-vue_export-helper-DHr5RHTx.js";import{g as N,S as R,m as b,o as C,i as d}from"./bem-COKTjsky.js";const E={class:"app report-page"},S={key:0,class:"empty"},T={class:"report-page__head"},V={class:"report-page__id"},B={class:"report-page__title"},L={class:"report-page__meta"},P={class:"report-page__section"},W={class:"report-page__text"},A={class:"report-page__section"},D={key:0,class:"report-page__unanswered"},F={key:1,class:"empty"},I={class:"report-page__answer-meta"},U={class:"report-page__text"},j=v({__name:"ReportPage",setup(H){const r=new URLSearchParams(location.search).get("id")||"",e=g(null),p=g(r?"Loading…":"No report id in the link.");return f(()=>{r&&(document.title=r+" · Your report · Whose Turn",N("/api/feedback?id="+encodeURIComponent(r)).then(l=>{e.value=l.report||null,p.value=l.report?"":"No report "+r+" was filed from this machine."}).catch(()=>{p.value=R+"; reload this page once it is back."}))}),(l,i)=>(s(),o("main",E,[e.value?(s(),o(c,{key:1},[t("header",T,[t("span",V,a(e.value.id),1),t("h1",B,a(e.value.title),1),t("span",{class:w(["pill",n(b)("pill",n(C)(e.value.outcome))])},a(e.value.state||"filed, not yet seen"),3)]),t("p",L,[u(a(e.value.kind||"report")+" · filed "+a(n(d)(e.value.filed_at)),1),e.value.changed_at?(s(),o(c,{key:0},[u(" · last change "+a(n(d)(e.value.changed_at)),1)],64)):h("",!0)]),t("section",P,[i[0]||(i[0]=t("h2",{class:"report-page__heading"},"What you reported",-1)),t("p",W,a(e.value.body||"(no text kept)"),1)]),t("section",A,[i[1]||(i[1]=t("h2",{class:"report-page__heading"},"Answers",-1)),e.value.unanswered?(s(),o("p",D," Closed without an answer. The maintainer owes you a reason; this page will show it once it is written. ")):e.value.answers.length?h("",!0):(s(),o("p",F,"No answer yet.")),(s(!0),o(c,null,y(e.value.answers,(_,m)=>(s(),o("article",{key:m,class:"report-page__answer"},[t("p",I,a(_.by||"maintainer")+" · "+a(n(d)(_.at)),1),t("p",U,a(_.text),1)]))),128))])],64)):(s(),o("p",S,a(p.value),1))]))}}),z=k(j,[["__scopeId","data-v-167de311"]]);x(z).mount("#app");
|
package/dist/index.html
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
<title>Whose Turn</title>
|
|
7
7
|
<script src="/vendor/popper.min.js"></script>
|
|
8
8
|
<script src="/vendor/tippy-bundle.umd.min.js"></script>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-D-P2XKhJ.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DHr5RHTx.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/scheme-DJ3WNw60.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/bem-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/bem-COKTjsky.js">
|
|
13
13
|
<link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-BKveAJb2.css">
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B23uRijc.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body class="page">
|
|
17
17
|
<div id="app" class="page__mount"></div>
|
package/dist/report.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Your report · Whose Turn</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/report-
|
|
7
|
+
<script type="module" crossorigin src="/assets/report-BtGnm9ax.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-DHr5RHTx.js">
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/bem-
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/bem-COKTjsky.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/_plugin-vue_export-helper-BKveAJb2.css">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/report-xVOo7qip.css">
|
|
12
12
|
</head>
|
package/lib/api.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { createFeedback, isConfigured } from './feedback.mjs';
|
|
|
10
10
|
import { composeIssue, DEFAULT_COMPOSER } from './compose.mjs';
|
|
11
11
|
import { readSize, saveSize, checkMode, spawnOpener } from './window.mjs';
|
|
12
12
|
import { frameHandOff } from './handoff.mjs';
|
|
13
|
+
import { noUpdates } from './update.mjs';
|
|
13
14
|
|
|
14
15
|
const send = (response, code, body, contentType = 'application/json', cacheControl = 'no-store') => {
|
|
15
16
|
const data = Buffer.isBuffer(body) ? body : Buffer.from(contentType === 'application/json' ? JSON.stringify(body) : body);
|
|
@@ -88,7 +89,7 @@ const aggregateSummary = (workspaces) => {
|
|
|
88
89
|
|
|
89
90
|
const OPEN_COOLDOWN_MS = 5000;
|
|
90
91
|
|
|
91
|
-
export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir, opener = spawnOpener(appDir) }) => {
|
|
92
|
+
export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir, opener = spawnOpener(appDir), updater = noUpdates() }) => {
|
|
92
93
|
const windowFile = createStatic(join(appDir, 'dist'));
|
|
93
94
|
const feedbackCache = {};
|
|
94
95
|
const pick = (id) => {
|
|
@@ -119,11 +120,14 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
119
120
|
for (const workspace of registry.workspaces) {
|
|
120
121
|
write({ ws: workspace.id, version: workspace.store.state.version, polled_at: workspace.store.state.polled_at });
|
|
121
122
|
}
|
|
123
|
+
write({ update: updater.status() });
|
|
122
124
|
const unsubscribe = registry.subscribe(write);
|
|
125
|
+
const unwatch = updater.subscribe(write);
|
|
123
126
|
const keepAlive = setInterval(() => response.write(': keep-alive\n\n'), 25000);
|
|
124
127
|
response.on('close', () => {
|
|
125
128
|
clearInterval(keepAlive);
|
|
126
129
|
unsubscribe();
|
|
130
|
+
unwatch();
|
|
127
131
|
});
|
|
128
132
|
};
|
|
129
133
|
|
|
@@ -137,13 +141,13 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
137
141
|
}
|
|
138
142
|
if (url.pathname === '/api/state') {
|
|
139
143
|
if (!registry.workspaces.length) {
|
|
140
|
-
return send(response, 200, { workspaces: [], problems: registry.problems });
|
|
144
|
+
return send(response, 200, { workspaces: [], problems: registry.problems, update: updater.status() });
|
|
141
145
|
}
|
|
142
146
|
const workspace = pick(url.searchParams.get('ws'));
|
|
143
|
-
return send(response, 200, { ...workspace.snapshot(url.searchParams.get('full') === '1'), workspaces: registry.workspaces.map((item) => item.describe()), problems: registry.problems });
|
|
147
|
+
return send(response, 200, { ...workspace.snapshot(url.searchParams.get('full') === '1'), workspaces: registry.workspaces.map((item) => item.describe()), problems: registry.problems, update: updater.status() });
|
|
144
148
|
}
|
|
145
149
|
if (url.pathname === '/api/summary') {
|
|
146
|
-
return send(response, 200, { ...aggregateSummary(registry.workspaces), problems: registry.problems, handed: inbox.pending().length });
|
|
150
|
+
return send(response, 200, { ...aggregateSummary(registry.workspaces), problems: registry.problems, handed: inbox.pending().length, update: updater.status() });
|
|
147
151
|
}
|
|
148
152
|
if (url.pathname === '/api/config') {
|
|
149
153
|
const tables = Object.fromEntries(registry.workspaces.map((workspace) => [workspace.id, workspace.rulesTable]));
|
|
@@ -348,6 +352,9 @@ export const startApi = async ({ port, appDir, registry, requestPoll, saveConfig
|
|
|
348
352
|
opener({ mode, port });
|
|
349
353
|
return send(response, 200, { ok: true });
|
|
350
354
|
}
|
|
355
|
+
if (url.pathname === '/api/update') {
|
|
356
|
+
return send(response, 200, { update: updater.apply() });
|
|
357
|
+
}
|
|
351
358
|
if (url.pathname === '/api/refresh') {
|
|
352
359
|
requestPoll();
|
|
353
360
|
return send(response, 200, { ok: true });
|
package/lib/config.mjs
CHANGED
|
@@ -206,7 +206,7 @@ export const loadConfig = (env = process.env) => {
|
|
|
206
206
|
const raw = readJson(configFile);
|
|
207
207
|
const problems = [];
|
|
208
208
|
if (raw === null) {
|
|
209
|
-
problems.push(`No workspace config yet: open Settings (the gear) to add a source
|
|
209
|
+
problems.push(`No workspace config yet: open Settings (the gear) to add a source. It will be written to ${configFile}.`);
|
|
210
210
|
} else if (raw.parse_error) {
|
|
211
211
|
problems.push(`Cannot parse ${configFile}: ${raw.parse_error}`);
|
|
212
212
|
}
|