Compare commits

...
12 Commits
Author SHA1 Message Date
github-actions[bot]andGitHub eff9559aee chore(deps): update pnpm to v11.28.4
Automated dependency update. All tests passed.
2026-10-06 03:16:32 +00:00
lukaszraczylo 5c5416f6f0 fix(web): keep the action status fetch alive when a client disconnects
The status fetch used the request context. A client that dropped the
connection, such as a readiness probe with a one second timeout,
cancelled the fetch. The failure was then cached and every later
request showed no action status. The fetch now ignores the caller
cancellation and keeps its own timeout.
2026-10-05 15:53:52 +01:00
lukaszraczylo a0fd6d6856 feat(ui): make the alert action button prominent
The action button was a small outline control that was easy to miss.
It is now a solid primary button with an icon. The spinner still
replaces the icon while the action runs.
2026-10-05 14:05:12 +01:00
lukaszraczylo ac5eb2f16a fix(ui): stack the project list below 1280px and use two columns from 90rem
At 1024px the project list took 24rem and squeezed the alert column to
about 500px, which clipped the search box and filter counts. The list
now sits beside the alerts from 1280px. The alert cards use two columns
from 90rem, so a 1440px screen no longer shows one very wide column.
2026-10-05 14:05:12 +01:00
lukaszraczylo 66ed84bdbf fix(collector): publish each repository as its scan finishes
A rescan held every result until the last repository finished. The
dashboard therefore showed new and resolved alerts only at the end.
Each repository now enters the view when its fetch completes. The
last scan time, loaded flag and refresh hooks still update at the end.
2026-10-05 14:05:12 +01:00
lukaszraczylo e6f28a223a fix(web): move to TypeScript 6 without baseUrl 2026-10-05 11:50:37 +01:00
lukaszraczylo 0a2469ef7d fix(gh): bypass the stale run cache with a created filter
GitHub answered the confirming run query with a weeks-old cached result
even with page=1, so alerts for fixed workflows stayed. A created filter
changes the query each day and reads current runs.
2026-10-05 11:16:37 +01:00
github-actions[bot]andGitHub 06922abe05 chore(deps): update dependency @lucide/vue to v1.51.0
Automated dependency update. All tests passed.
2026-10-05 09:37:40 +00:00
lukaszraczylo 2100458973 chore(renovate): hold pnpm below 12
The pinned pnpm 11 in workflow-prepare.sh and ci.yaml cannot switch to the
pnpm 12 native binary. Node runs the binary as JavaScript and the install fails.
2026-10-05 10:34:16 +01:00
github-actions[bot]andGitHub 6b08d09431 chore(deps): update dependencies
Automated dependency update. All tests passed.
2026-10-05 03:22:21 +00:00
lukaszraczylo 78a6723143 fix(web): run automatic actions after a repository rescan
The rescan button of one repository now triggers automatic actions too. The webhook calls run in the background so the response does not wait for them.
2026-10-05 01:57:05 +01:00
lukaszraczylo c2ce5391c0 fix(collector): include fork pull requests in pr_open
External contributors open pull requests from forks. The pr_open alert now covers them, so the review action sees them. The consumer must treat fork pull requests as untrusted.
2026-10-05 01:00:18 +01:00
18 changed files with 288 additions and 170 deletions
+2 -2
View File
@@ -96,7 +96,7 @@ actions:
### Automatic actions ### Automatic actions
An action with `auto: true` fires without a click. After every scan, and once at startup, the server sends the webhook for each current alert whose kind is in `kinds`. It sends once per action, repo, alert URL and head commit. A new commit on a pull request is a new send. An action with `auto: true` fires without a click. After every scan, after every single-repository rescan, and once at startup, the server sends the webhook for each current alert whose kind is in `kinds`. It sends once per action, repo, alert URL and head commit. A new commit on a pull request is a new send.
```yaml ```yaml
actions: actions:
@@ -110,7 +110,7 @@ actions:
token_env: ACTION_TOKEN token_env: ACTION_TOKEN
``` ```
- `pr_open` is an informational alert kind. It exists for each open, non-draft pull request from the same repository (not a fork) in a non-archived repo. `subject` is `#N title`, `url` is the pull request, `ref` is the head commit SHA and `detail` is `open for <age>`. It has severity `ok`: it never changes repo health, the counts, the metrics or the dashboard, and the UI hides it. It is in the `alerts` array of `GET /api/state`, which gains an optional `ref` field on every alert. - `pr_open` is an informational alert kind. It exists for each open, non-draft pull request including pull requests from forks, in a non-archived repo. Fork pull requests come from external contributors, so a consumer of these alerts, including an auto action webhook, must treat them as untrusted input. `subject` is `#N title`, `url` is the pull request, `ref` is the head commit SHA and `detail` is `open for <age>`. It has severity `ok`: it never changes repo health, the counts, the metrics or the dashboard, and the UI hides it. It is in the `alerts` array of `GET /api/state`, which gains an optional `ref` field on every alert.
- `authors` limits auto sends to alerts that carry an author, which today means `pr_open`. Without `authors`, any alert of a listed kind fires. - `authors` limits auto sends to alerts that carry an author, which today means `pr_open`. Without `authors`, any alert of a listed kind fires.
- The webhook request is the same as for a button press, plus `alert.ref` when the alert has one. - The webhook request is the same as for a button press, plus `alert.ref` when the alert has one.
- The server keeps the sent set in memory and drops an entry when its alert disappears, so a restart sends again for every open pull request. A failed send is not recorded and retries on the next scan. That covers a network error, a non-2xx answer and a `409` (the consumer is busy). Any other 2xx answer counts as sent. - The server keeps the sent set in memory and drops an entry when its alert disappears, so a restart sends again for every open pull request. A failed send is not recorded and retries on the next scan. That covers a network error, a non-2xx answer and a `409` (the consumer is busy). Any other 2xx answer counts as sent.
+1 -1
View File
@@ -26,7 +26,7 @@ thresholds: # negative disables a check
# status_url: https://example.internal/hooks/rerun/status # optional # status_url: https://example.internal/hooks/rerun/status # optional
# confirm: "Run this action for the selected alert?" # optional; omit for no dialog # confirm: "Run this action for the selected alert?" # optional; omit for no dialog
# - id: review # - id: review
# kinds: [pr_open] # required with auto; one alert per open, non-draft, non-fork PR # kinds: [pr_open] # required with auto; one alert per open, non-draft PR, forks included; consumers must treat fork PRs as untrusted
# auto: true # fire without a click, once per repo, PR and head commit # auto: true # fire without a click, once per repo, PR and head commit
# authors: [lukaszraczylo] # optional; case-insensitive; omit for any author # authors: [lukaszraczylo] # optional; case-insensitive; omit for any author
# webhook: # webhook:
+17 -9
View File
@@ -119,10 +119,13 @@ func (c *Collector) OnRefresh(fn func(context.Context, View)) { c.onRefresh = ap
func (c *Collector) refreshAndNotify(ctx context.Context) { func (c *Collector) refreshAndNotify(ctx context.Context) {
c.Refresh(ctx) c.Refresh(ctx)
if len(c.onRefresh) == 0 || ctx.Err() != nil { if ctx.Err() != nil {
return return
} }
v := c.View() c.notify(ctx, c.View())
}
func (c *Collector) notify(ctx context.Context, v View) {
for _, fn := range c.onRefresh { for _, fn := range c.onRefresh {
fn(ctx, v) fn(ctx, v)
} }
@@ -152,25 +155,25 @@ func (c *Collector) Refresh(ctx context.Context) {
c.limited.Store(false) c.limited.Store(false)
c.scanDone.Store(0) c.scanDone.Store(0)
c.scanTotal.Store(int64(len(c.names))) c.scanTotal.Store(int64(len(c.names)))
results := make([]model.Repo, len(c.names))
sem := make(chan struct{}, c.concurrency) sem := make(chan struct{}, c.concurrency)
var wg sync.WaitGroup var wg sync.WaitGroup
for i, name := range c.names { for _, name := range c.names {
wg.Add(1) wg.Add(1)
sem <- struct{}{} sem <- struct{}{}
go func() { go func() {
defer wg.Done() defer wg.Done()
defer func() { <-sem }() defer func() { <-sem }()
results[i] = c.refreshOne(ctx, name, start, true) r := c.refreshOne(ctx, name, start, true)
// Publish per repo so a running scan shows new and resolved alerts as they land.
c.mu.Lock()
c.repos[r.FullName] = r
c.mu.Unlock()
c.scanDone.Add(1) c.scanDone.Add(1)
}() }()
} }
wg.Wait() wg.Wait()
c.mu.Lock() c.mu.Lock()
for _, r := range results {
c.repos[r.FullName] = r
}
c.lastRefresh = start c.lastRefresh = start
c.lastDur = c.now().Sub(start) c.lastDur = c.now().Sub(start)
c.loaded = true c.loaded = true
@@ -239,7 +242,12 @@ func (c *Collector) RefreshRepo(ctx context.Context, name string) (View, error)
c.mu.Lock() c.mu.Lock()
c.repos[canon] = r c.repos[canon] = r
c.mu.Unlock() c.mu.Unlock()
return c.View(), nil v := c.View()
if len(c.onRefresh) > 0 {
// The request context ends with the response, which must not wait for webhooks.
go c.notify(context.WithoutCancel(ctx), v)
}
return v, nil
} }
func (c *Collector) configured(name string) (string, bool) { func (c *Collector) configured(name string) (string, bool) {
+83 -6
View File
@@ -93,7 +93,7 @@ func TestEvaluate(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
r := fresh() r := fresh()
tt.mutate(&r) tt.mutate(&r)
alerts := Evaluate(&r, t0, th()) alerts := withoutPROpen(Evaluate(&r, t0, th()))
got := kinds(alerts) got := kinds(alerts)
if len(got) != len(tt.want) { if len(got) != len(tt.want) {
t.Fatalf("alerts = %+v, want kinds %v", alerts, tt.want) t.Fatalf("alerts = %+v, want kinds %v", alerts, tt.want)
@@ -110,11 +110,22 @@ func TestEvaluate(t *testing.T) {
} }
} }
// withoutPROpen drops the informational pr_open alerts so tests of other kinds stay focused.
func withoutPROpen(in []model.Alert) []model.Alert {
var out []model.Alert
for _, a := range in {
if a.Kind != KindPROpen {
out = append(out, a)
}
}
return out
}
func TestEvaluateDisabledThresholds(t *testing.T) { func TestEvaluateDisabledThresholds(t *testing.T) {
r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-100 * 24 * time.Hour)}}, r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-100 * 24 * time.Hour)}},
Issues: []model.Issue{{UpdatedAt: t0.Add(-100 * 24 * time.Hour)}}} Issues: []model.Issue{{UpdatedAt: t0.Add(-100 * 24 * time.Hour)}}}
off := config.Thresholds{PRWarnAfter: -1, PRCritAfter: -1, IssueStaleAfter: -1, WorkflowStuck: -1, DataStaleAfter: -1} off := config.Thresholds{PRWarnAfter: -1, PRCritAfter: -1, IssueStaleAfter: -1, WorkflowStuck: -1, DataStaleAfter: -1}
if a := Evaluate(&r, t0, off); len(a) != 0 { if a := withoutPROpen(Evaluate(&r, t0, off)); len(a) != 0 {
t.Fatalf("disabled checks produced %+v", a) t.Fatalf("disabled checks produced %+v", a)
} }
} }
@@ -123,10 +134,10 @@ func TestAgeAdvancesWithoutRefresh(t *testing.T) {
r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-40 * time.Hour)}}} r := model.Repo{FullName: "o/r", RefreshedAt: t0, PRs: []model.PullRequest{{Number: 1, Fork: true, CreatedAt: t0.Add(-40 * time.Hour)}}}
cfg := th() cfg := th()
cfg.DataStaleAfter = -1 cfg.DataStaleAfter = -1
if a := Evaluate(&r, t0, cfg); len(a) != 0 { if a := withoutPROpen(Evaluate(&r, t0, cfg)); len(a) != 0 {
t.Fatalf("unexpected %+v", a) t.Fatalf("unexpected %+v", a)
} }
if a := Evaluate(&r, t0.Add(10*time.Hour), cfg); len(a) != 1 || a[0].Severity != model.Warn { if a := withoutPROpen(Evaluate(&r, t0.Add(10*time.Hour), cfg)); len(a) != 1 || a[0].Severity != model.Warn {
t.Fatalf("PR must cross the warn threshold as time passes: %+v", a) t.Fatalf("PR must cross the warn threshold as time passes: %+v", a)
} }
} }
@@ -240,7 +251,8 @@ func TestViewSortsWorstFirstAndAlertsBySeverity(t *testing.T) {
if v.Repos[0].FullName != "o/crit" || v.Repos[1].FullName != "o/warn" || v.Repos[2].FullName != "o/ok" { if v.Repos[0].FullName != "o/crit" || v.Repos[1].FullName != "o/warn" || v.Repos[2].FullName != "o/ok" {
t.Fatalf("order: %s %s %s", v.Repos[0].FullName, v.Repos[1].FullName, v.Repos[2].FullName) t.Fatalf("order: %s %s %s", v.Repos[0].FullName, v.Repos[1].FullName, v.Repos[2].FullName)
} }
if len(v.Alerts) != 2 || v.Alerts[0].Severity != model.Crit || v.Crit != 1 || v.Warn != 1 { alerts := withoutPROpen(v.Alerts)
if len(alerts) != 2 || alerts[0].Severity != model.Crit || v.Crit != 1 || v.Warn != 1 {
t.Fatalf("alerts: %+v", v.Alerts) t.Fatalf("alerts: %+v", v.Alerts)
} }
} }
@@ -470,7 +482,7 @@ func TestEvaluatePROpen(t *testing.T) {
want bool want bool
}{ }{
"open": {pr(func(*model.PullRequest) {}), false, true}, "open": {pr(func(*model.PullRequest) {}), false, true},
"fork": {pr(func(p *model.PullRequest) { p.Fork = true }), false, false}, "fork": {pr(func(p *model.PullRequest) { p.Fork = true }), false, true},
"draft": {pr(func(p *model.PullRequest) { p.Draft = true }), false, false}, "draft": {pr(func(p *model.PullRequest) { p.Draft = true }), false, false},
"archived": {pr(func(*model.PullRequest) {}), true, false}, "archived": {pr(func(*model.PullRequest) {}), true, false},
} }
@@ -507,3 +519,68 @@ func TestPROpenDoesNotChangeCounts(t *testing.T) {
t.Fatalf("view = %+v", v) t.Fatalf("view = %+v", v)
} }
} }
func TestRefreshRepoRunsRefreshHooksWithTheNewView(t *testing.T) {
f := &fakeFetcher{fn: func(name string) (model.Repo, error) {
return model.Repo{FullName: name, URL: "u", Issues: make([]model.Issue, 3)}, nil
}}
c := newCollector(f, "o/a")
got := make(chan View, 1)
c.OnRefresh(func(_ context.Context, v View) { got <- v })
if _, err := c.RefreshRepo(context.Background(), "o/a"); err != nil {
t.Fatal(err)
}
select {
case v := <-got:
if len(v.Repos) != 1 || len(v.Repos[0].Issues) != 3 {
t.Fatalf("hook saw a stale view: %+v", v.Repos)
}
case <-time.After(2 * time.Second):
t.Fatal("refresh hook did not run after a repository refresh")
}
}
func TestRefreshRepoFailureSkipsRefreshHooks(t *testing.T) {
c := newCollector(&fakeFetcher{fn: func(string) (model.Repo, error) { return model.Repo{}, errors.New("nope") }}, "o/a")
called := make(chan struct{}, 1)
c.OnRefresh(func(context.Context, View) { called <- struct{}{} })
if _, err := c.RefreshRepo(context.Background(), "unknown/x"); err == nil {
t.Fatal("unknown repository must fail")
}
select {
case <-called:
t.Fatal("hook ran for a refused refresh")
case <-time.After(200 * time.Millisecond):
}
}
func TestRefreshPublishesEachRepoBeforeTheScanEnds(t *testing.T) {
release := make(chan struct{})
f := &fakeFetcher{fn: func(name string) (model.Repo, error) {
if name == "o/slow" {
<-release
}
return model.Repo{FullName: name, Err: ""}, nil
}}
cfg := &config.Config{Repos: []string{"o/fast", "o/slow"}, RefreshInterval: time.Hour, Concurrency: 2, Thresholds: th()}
c := New(f, cfg, slog.New(slog.NewTextHandler(io.Discard, nil)))
c.now = func() time.Time { return t0 }
done := make(chan struct{})
go func() { c.Refresh(context.Background()); close(done) }()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if v := c.View(); len(v.Repos) == 1 && v.Repos[0].FullName == "o/fast" {
break
}
time.Sleep(5 * time.Millisecond)
}
v := c.View()
if len(v.Repos) != 1 || v.Repos[0].FullName != "o/fast" || !v.Refreshing || v.Loaded {
t.Fatalf("mid-scan view: repos=%d refreshing=%v loaded=%v", len(v.Repos), v.Refreshing, v.Loaded)
}
close(release)
<-done
if v := c.View(); len(v.Repos) != 2 || !v.Loaded {
t.Fatalf("after scan: repos=%d loaded=%v", len(v.Repos), v.Loaded)
}
}
+2 -4
View File
@@ -64,10 +64,8 @@ func Evaluate(r *model.Repo, now time.Time, th config.Thresholds) []model.Alert
} }
age := now.Sub(pr.CreatedAt) age := now.Sub(pr.CreatedAt)
subject := fmt.Sprintf("#%d %s", pr.Number, pr.Title) subject := fmt.Sprintf("#%d %s", pr.Number, pr.Title)
if !pr.Fork { alerts = append(alerts, model.Alert{Severity: model.OK, Repo: r.FullName, Kind: KindPROpen, Subject: subject,
alerts = append(alerts, model.Alert{Severity: model.OK, Repo: r.FullName, Kind: KindPROpen, Subject: subject, Detail: "open for " + Age(age), URL: pr.URL, Ref: pr.HeadSHA, Author: pr.Author, Since: pr.CreatedAt})
Detail: "open for " + Age(age), URL: pr.URL, Ref: pr.HeadSHA, Author: pr.Author, Since: pr.CreatedAt})
}
switch { switch {
case th.PRCritAfter > 0 && age >= th.PRCritAfter: case th.PRCritAfter > 0 && age >= th.PRCritAfter:
add(model.Crit, KindPRWaiting, subject, "open "+Age(age), pr.URL, pr.CreatedAt) add(model.Crit, KindPRWaiting, subject, "open "+Age(age), pr.URL, pr.CreatedAt)
+14 -10
View File
@@ -14,14 +14,16 @@ import (
) )
const ( const (
maxFailingNames = 5 maxFailingNames = 5
runsPerWorkflow = 5 runsPerWorkflow = 5
maxWorkflows = 100 // freshRunsWindowDays bounds the created filter of the confirming run query.
eventPullRequest = "pull_request" freshRunsWindowDays = 30
eventPullReqTgt = "pull_request_target" maxWorkflows = 100
statusStateFail = "failure" eventPullRequest = "pull_request"
statusStateError = "error" eventPullReqTgt = "pull_request_target"
statusStatePend = "pending" statusStateFail = "failure"
statusStateError = "error"
statusStatePend = "pending"
) )
// backgroundEvents are triggers that run without a person pushing code. // backgroundEvents are triggers that run without a person pushing code.
@@ -374,9 +376,11 @@ func (c *Client) workflowRuns(ctx context.Context, full string, id int64, branch
path := fmt.Sprintf("/repos/%s/actions/workflows/%d/runs", full, id) path := fmt.Sprintf("/repos/%s/actions/workflows/%d/runs", full, id)
get := c.getJSON get := c.getJSON
if fresh { if fresh {
// GitHub can keep answering one exact query from an old cached result, for weeks, whatever // GitHub can keep answering a branch query from an old cached result, for weeks, whatever
// the connection. A different query string skips that entry and reads current runs. // the connection, and page=1 alone does not skip it. A created filter changes the query
// every day and reads current runs.
q.Set("page", "1") q.Set("page", "1")
q.Set("created", ">="+c.now().UTC().AddDate(0, 0, -freshRunsWindowDays).Format(time.DateOnly))
get = c.getJSONFresh get = c.getJSONFresh
} }
if err := get(ctx, full, path, q, &out); err != nil { if err := get(ctx, full, path, q, &out); err != nil {
+2 -2
View File
@@ -461,12 +461,12 @@ func TestAPIErrorMessageParsing(t *testing.T) {
} }
// staleThenFresh models GitHub answering one exact query from an old cached result: the plain // staleThenFresh models GitHub answering one exact query from an old cached result: the plain
// query returns an old failing run every time, and the same query with page=1 returns current runs. // query returns an old failing run every time, and the same query with a created filter returns current runs.
func staleThenFresh(f *fakeGitHub, path string) *atomic.Int64 { func staleThenFresh(f *fakeGitHub, path string) *atomic.Int64 {
var calls atomic.Int64 var calls atomic.Int64
f.routes[path] = func(w http.ResponseWriter, r *http.Request) { f.routes[path] = func(w http.ResponseWriter, r *http.Request) {
calls.Add(1) calls.Add(1)
if r.URL.Query().Get("page") == "" { if r.URL.Query().Get("created") == "" {
_, _ = w.Write([]byte(`{"workflow_runs":[ _, _ = w.Write([]byte(`{"workflow_runs":[
{"id":1,"html_url":"old","event":"schedule","head_branch":"main","status":"completed","conclusion":"failure","created_at":"2026-09-11T03:02:00Z"}]}`)) {"id":1,"html_url":"old","event":"schedule","head_branch":"main","status":"completed","conclusion":"failure","created_at":"2026-09-11T03:02:00Z"}]}`))
return return
+2 -1
View File
@@ -158,7 +158,8 @@ func (a *Actions) statusRows(ctx context.Context, rt *actionRuntime) (map[alertR
if !rt.fetched.IsZero() && a.now().Sub(rt.fetched) < statusCacheTTL { if !rt.fetched.IsZero() && a.now().Sub(rt.fetched) < statusCacheTTL {
return rt.rows, rt.err return rt.rows, rt.err
} }
rows, err := a.fetchStatus(ctx, rt.cfg) // A client that disconnects mid-request must not fail the fetch that every later request reuses.
rows, err := a.fetchStatus(context.WithoutCancel(ctx), rt.cfg)
if err != nil { if err != nil {
a.log.Warn("action status unavailable", "action", rt.cfg.ID, "err", err) a.log.Warn("action status unavailable", "action", rt.cfg.ID, "err", err)
} }
+13
View File
@@ -2,6 +2,7 @@ package web
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"io" "io"
"log/slog" "log/slog"
@@ -407,3 +408,15 @@ func TestRedirectDoesNotForwardToken(t *testing.T) {
t.Fatal("bearer token forwarded across a redirect") t.Fatal("bearer token forwarded across a redirect")
} }
} }
func TestStatusFetchSurvivesCancelledRequest(t *testing.T) {
fh := newFakeHook(t)
fh.statBody = `[{"repo":"o/r","url":"` + alertURL + `","state":"running"}]`
h := newActionServer(t, nil, fh.srv.URL)
ctx, cancel := context.WithCancel(context.Background())
cancel()
rows, err := h.actions.statusRows(ctx, h.actions.list[0])
if err != nil || len(rows) != 1 {
t.Fatalf("rows=%d err=%v, want the fetch to complete despite the cancelled request", len(rows), err)
}
}
+4 -1
View File
@@ -43,7 +43,10 @@ func TestAutoRunner(t *testing.T) {
authors []string authors []string
steps []autoStep steps []autoStep
}{ }{
"fork skipped": {steps: []autoStep{{[]model.PullRequest{autoPR(1, "a", "x", fork)}, 200, 0}}}, "fork triggers once per sha": {steps: []autoStep{
{[]model.PullRequest{autoPR(1, "a", "x", fork)}, 200, 1},
{[]model.PullRequest{autoPR(1, "a", "x", fork)}, 200, 0},
}},
"draft skipped": {steps: []autoStep{{[]model.PullRequest{autoPR(1, "a", "x", draft)}, 200, 0}}}, "draft skipped": {steps: []autoStep{{[]model.PullRequest{autoPR(1, "a", "x", draft)}, 200, 0}}},
"author filter": {authors: []string{"Alice"}, steps: []autoStep{ "author filter": {authors: []string{"Alice"}, steps: []autoStep{
{[]model.PullRequest{autoPR(1, "a", "bob", nil), autoPR(2, "b", "alice", nil)}, 200, 1}, {[]model.PullRequest{autoPR(1, "a", "bob", nil), autoPR(2, "b", "alice", nil)}, 200, 1},
+2 -2
View File
@@ -37,11 +37,11 @@
"happy-dom": "^20.14.5", "happy-dom": "^20.14.5",
"prettier": "^3.9.9", "prettier": "^3.9.9",
"tailwindcss": "^4.3.3", "tailwindcss": "^4.3.3",
"typescript": "~5.9.3", "typescript": "~6.0.3",
"typescript-eslint": "^8.70.1", "typescript-eslint": "^8.70.1",
"vite": "^8.3.1", "vite": "^8.3.1",
"vitest": "^5.0.1", "vitest": "^5.0.1",
"vue-tsc": "^3.3.11" "vue-tsc": "^3.3.11"
}, },
"packageManager": "[email protected].3" "packageManager": "[email protected].4"
} }
+122 -122
View File
@@ -22,13 +22,13 @@ importers:
version: 7.3.1 version: 7.3.1
'@fortawesome/vue-fontawesome': '@fortawesome/vue-fontawesome':
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3(@fortawesome/[email protected])([email protected](typescript@5.9.3)) version: 3.3.3(@fortawesome/[email protected])([email protected](typescript@6.0.3))
'@lucide/vue': '@lucide/vue':
specifier: ^1.49.0 specifier: ^1.49.0
version: 1.49.0([email protected](typescript@5.9.3)) version: 1.51.0([email protected](typescript@6.0.3))
'@vueuse/core': '@vueuse/core':
specifier: ^15.0.0 specifier: ^15.0.0
version: 15.0.0([email protected](typescript@5.9.3)) version: 15.0.0([email protected](typescript@6.0.3))
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@@ -37,20 +37,20 @@ importers:
version: 2.1.1 version: 2.1.1
pinia: pinia:
specifier: ^4.0.3 specifier: ^4.0.3
version: 4.0.3(@vue/[email protected])(typescript@5.9.3)([email protected](typescript@5.9.3)) version: 4.0.3(@vue/[email protected])(typescript@6.0.3)([email protected](typescript@6.0.3))
reka-ui: reka-ui:
specifier: ^2.10.5 specifier: ^2.10.5
version: 2.10.5([email protected](typescript@5.9.3)) version: 2.10.5([email protected](typescript@6.0.3))
tailwind-merge: tailwind-merge:
specifier: ^3.7.0 specifier: ^3.7.0
version: 3.7.0 version: 3.7.0
vue: vue:
specifier: ^3.5.43 specifier: ^3.5.43
version: 3.5.43(typescript@5.9.3) version: 3.5.43(typescript@6.0.3)
devDependencies: devDependencies:
'@eslint/js': '@eslint/js':
specifier: ^10.0.1 specifier: ^10.0.1
version: 10.0.1([email protected]1.0([email protected])) version: 10.0.1([email protected]2.0([email protected]))
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.3.3 specifier: ^4.3.3
version: 4.3.3([email protected](@types/[email protected])([email protected])) version: 4.3.3([email protected](@types/[email protected])([email protected]))
@@ -59,16 +59,16 @@ importers:
version: 26.6.4 version: 26.6.4
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^6.0.9 specifier: ^6.0.9
version: 6.0.9([email protected](@types/[email protected])([email protected]))([email protected](typescript@5.9.3)) version: 6.0.9([email protected](@types/[email protected])([email protected]))([email protected](typescript@6.0.3))
'@vue/test-utils': '@vue/test-utils':
specifier: ^2.5.1 specifier: ^2.5.1
version: 2.5.1(@vue/[email protected])(@vue/[email protected])([email protected](typescript@5.9.3)) version: 2.5.1(@vue/[email protected])(@vue/[email protected])([email protected](typescript@6.0.3))
eslint: eslint:
specifier: ^10.11.0 specifier: ^10.11.0
version: 10.11.0([email protected]) version: 10.12.0([email protected])
eslint-plugin-vue: eslint-plugin-vue:
specifier: ^10.11.1 specifier: ^10.11.1
version: 10.11.1(@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3))([email protected]1.0([email protected]))([email protected]([email protected]1.0([email protected]))) version: 10.11.1(@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3))([email protected]2.0([email protected]))([email protected]([email protected]2.0([email protected])))
globals: globals:
specifier: ^17.12.0 specifier: ^17.12.0
version: 17.13.0 version: 17.13.0
@@ -82,11 +82,11 @@ importers:
specifier: ^4.3.3 specifier: ^4.3.3
version: 4.3.3 version: 4.3.3
typescript: typescript:
specifier: ~5.9.3 specifier: ~6.0.3
version: 5.9.3 version: 6.0.3
typescript-eslint: typescript-eslint:
specifier: ^8.70.1 specifier: ^8.70.1
version: 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) version: 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
vite: vite:
specifier: ^8.3.1 specifier: ^8.3.1
version: 8.3.2(@types/[email protected])([email protected]) version: 8.3.2(@types/[email protected])([email protected])
@@ -95,7 +95,7 @@ importers:
version: 5.0.3(@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected])) version: 5.0.3(@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected]))
vue-tsc: vue-tsc:
specifier: ^3.3.11 specifier: ^3.3.11
version: 3.3.11(typescript@5.9.3) version: 3.3.12(typescript@6.0.3)
packages: packages:
@@ -248,8 +248,8 @@ packages:
'@keyv/[email protected]': '@keyv/[email protected]':
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
'@lucide/vue@1.49.0': '@lucide/vue@1.51.0':
resolution: {integrity: sha512-7xS5gYnLoIx0jlmlRE3Rb71fIHaNqhVBiKMpOlqrYt6nMPy3C/62HJHo5XNsV3HTK+9l3M0hXICrIA7S5v6y5Q==} resolution: {integrity: sha512-zMkTAMP8OsPESz/GDedsZfr3VUF2IiHlj25mBZG6Pc8A8J8BdygkE8k1HhRFXEUNzytp5DqxiBQNm3RG/U2bVg==}
peerDependencies: peerDependencies:
vue: '>=3.0.1' vue: '>=3.0.1'
@@ -605,8 +605,8 @@ packages:
'@vue/[email protected]': '@vue/[email protected]':
resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==}
'@vue/[email protected]1': '@vue/[email protected]2':
resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==} resolution: {integrity: sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA==}
'@vue/[email protected]': '@vue/[email protected]':
resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==} resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==}
@@ -806,8 +806,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
[email protected]1.0: [email protected]2.0:
resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==} resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24} engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -1314,8 +1314,8 @@ packages:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0' typescript: '>=4.8.4 <6.1.0'
typescript@5.9.3: typescript@6.0.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
@@ -1435,8 +1435,8 @@ packages:
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
[email protected]1: [email protected]2:
resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==} resolution: {integrity: sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
typescript: '>=5.0.0' typescript: '>=5.0.0'
@@ -1514,9 +1514,9 @@ snapshots:
hashery: 1.5.1 hashery: 1.5.1
keyv: 5.6.0 keyv: 5.6.0
'@eslint-community/[email protected]([email protected]1.0([email protected]))': '@eslint-community/[email protected]([email protected]2.0([email protected]))':
dependencies: dependencies:
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
'@eslint-community/[email protected]': {} '@eslint-community/[email protected]': {}
@@ -1537,9 +1537,9 @@ snapshots:
dependencies: dependencies:
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@eslint/[email protected]([email protected]1.0([email protected]))': '@eslint/[email protected]([email protected]2.0([email protected]))':
optionalDependencies: optionalDependencies:
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
'@eslint/[email protected]': {} '@eslint/[email protected]': {}
@@ -1559,11 +1559,11 @@ snapshots:
'@floating-ui/[email protected]': {} '@floating-ui/[email protected]': {}
'@floating-ui/[email protected]([email protected](typescript@5.9.3))': '@floating-ui/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@floating-ui/dom': 1.8.0 '@floating-ui/dom': 1.8.0
'@floating-ui/utils': 0.2.12 '@floating-ui/utils': 0.2.12
vue-demi: 0.14.10([email protected](typescript@5.9.3)) vue-demi: 0.14.10([email protected](typescript@6.0.3))
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
@@ -1582,10 +1582,10 @@ snapshots:
dependencies: dependencies:
'@fortawesome/fontawesome-common-types': 7.3.1 '@fortawesome/fontawesome-common-types': 7.3.1
'@fortawesome/[email protected](@fortawesome/[email protected])([email protected](typescript@5.9.3))': '@fortawesome/[email protected](@fortawesome/[email protected])([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@fortawesome/fontawesome-svg-core': 7.3.1 '@fortawesome/fontawesome-svg-core': 7.3.1
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@humanfs/[email protected]': '@humanfs/[email protected]':
dependencies: dependencies:
@@ -1638,9 +1638,9 @@ snapshots:
'@keyv/[email protected]': {} '@keyv/[email protected]': {}
'@lucide/vue@1.49.0([email protected](typescript@5.9.3))': '@lucide/vue@1.51.0([email protected](typescript@6.0.3))':
dependencies: dependencies:
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@one-ini/[email protected]': {} '@one-ini/[email protected]': {}
@@ -1767,10 +1767,10 @@ snapshots:
'@tanstack/[email protected]': {} '@tanstack/[email protected]': {}
'@tanstack/[email protected]([email protected](typescript@5.9.3))': '@tanstack/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@tanstack/virtual-core': 3.17.11 '@tanstack/virtual-core': 3.17.11
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@types/[email protected]': '@types/[email protected]':
dependencies: dependencies:
@@ -1797,40 +1797,40 @@ snapshots:
dependencies: dependencies:
'@types/node': 26.6.4 '@types/node': 26.6.4
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3))([email protected]1.0([email protected]))(typescript@5.9.3)': '@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3))([email protected]2.0([email protected]))(typescript@6.0.3)':
dependencies: dependencies:
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.71.0 '@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/type-utils': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/type-utils': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.71.0 '@typescript-eslint/visitor-keys': 8.71.0
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
ignore: 7.0.11 ignore: 7.0.11
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@5.9.3) ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3)': '@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3)':
dependencies: dependencies:
'@typescript-eslint/scope-manager': 8.71.0 '@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.71.0 '@typescript-eslint/visitor-keys': 8.71.0
debug: 4.4.3 debug: 4.4.3
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/[email protected](typescript@5.9.3)': '@typescript-eslint/[email protected](typescript@6.0.3)':
dependencies: dependencies:
'@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3)
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
debug: 4.4.3 debug: 4.4.3
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -1839,47 +1839,47 @@ snapshots:
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
'@typescript-eslint/visitor-keys': 8.71.0 '@typescript-eslint/visitor-keys': 8.71.0
'@typescript-eslint/[email protected](typescript@5.9.3)': '@typescript-eslint/[email protected](typescript@6.0.3)':
dependencies: dependencies:
typescript: 5.9.3 typescript: 6.0.3
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3)': '@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3)':
dependencies: dependencies:
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
debug: 4.4.3 debug: 4.4.3
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
ts-api-utils: 2.5.0(typescript@5.9.3) ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/[email protected]': {} '@typescript-eslint/[email protected]': {}
'@typescript-eslint/[email protected](typescript@5.9.3)': '@typescript-eslint/[email protected](typescript@6.0.3)':
dependencies: dependencies:
'@typescript-eslint/project-service': 8.71.0(typescript@5.9.3) '@typescript-eslint/project-service': 8.71.0(typescript@6.0.3)
'@typescript-eslint/tsconfig-utils': 8.71.0(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.71.0(typescript@6.0.3)
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
'@typescript-eslint/visitor-keys': 8.71.0 '@typescript-eslint/visitor-keys': 8.71.0
debug: 4.4.3 debug: 4.4.3
minimatch: 10.2.6 minimatch: 10.2.6
semver: 7.8.5 semver: 7.8.5
tinyglobby: 0.2.17 tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@5.9.3) ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3)': '@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected])) '@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
'@typescript-eslint/scope-manager': 8.71.0 '@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -1888,11 +1888,11 @@ snapshots:
'@typescript-eslint/types': 8.71.0 '@typescript-eslint/types': 8.71.0
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.1
'@vitejs/[email protected]([email protected](@types/[email protected])([email protected]))([email protected](typescript@5.9.3))': '@vitejs/[email protected]([email protected](@types/[email protected])([email protected]))([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@rolldown/pluginutils': 1.0.1 '@rolldown/pluginutils': 1.0.1
vite: 8.3.2(@types/[email protected])([email protected]) vite: 8.3.2(@types/[email protected])([email protected])
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@vitest/[email protected]([email protected](@types/[email protected])([email protected]))': '@vitest/[email protected]([email protected](@types/[email protected])([email protected]))':
dependencies: dependencies:
@@ -1911,13 +1911,13 @@ snapshots:
'@volar/[email protected]': {} '@volar/[email protected]': {}
'@volar/[email protected](typescript@5.9.3)': '@volar/[email protected](typescript@6.0.3)':
dependencies: dependencies:
'@volar/language-core': 2.4.28 '@volar/language-core': 2.4.28
path-browserify: 1.0.1 path-browserify: 1.0.1
vscode-uri: 3.2.0 vscode-uri: 3.2.0
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 6.0.3
'@vue/[email protected]': '@vue/[email protected]':
dependencies: dependencies:
@@ -1962,7 +1962,7 @@ snapshots:
'@vue/[email protected]': {} '@vue/[email protected]': {}
'@vue/[email protected]1': '@vue/[email protected]2':
dependencies: dependencies:
'@volar/language-core': 2.4.28 '@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.43 '@vue/compiler-dom': 3.5.43
@@ -1996,40 +1996,40 @@ snapshots:
'@vue/[email protected]': {} '@vue/[email protected]': {}
'@vue/[email protected](@vue/[email protected])(@vue/[email protected])([email protected](typescript@5.9.3))': '@vue/[email protected](@vue/[email protected])(@vue/[email protected])([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@vue/compiler-dom': 3.5.43 '@vue/compiler-dom': 3.5.43
js-beautify: 2.0.3 js-beautify: 2.0.3
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
vue-component-type-helpers: 3.3.11 vue-component-type-helpers: 3.3.11
optionalDependencies: optionalDependencies:
'@vue/server-renderer': 3.5.43 '@vue/server-renderer': 3.5.43
'@vueuse/[email protected]([email protected](typescript@5.9.3))': '@vueuse/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@types/web-bluetooth': 0.0.21 '@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 14.4.0 '@vueuse/metadata': 14.4.0
'@vueuse/shared': 14.4.0([email protected](typescript@5.9.3)) '@vueuse/shared': 14.4.0([email protected](typescript@6.0.3))
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@vueuse/[email protected]([email protected](typescript@5.9.3))': '@vueuse/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
'@types/web-bluetooth': 0.0.21 '@types/web-bluetooth': 0.0.21
'@vueuse/metadata': 15.0.0 '@vueuse/metadata': 15.0.0
'@vueuse/shared': 15.0.0([email protected](typescript@5.9.3)) '@vueuse/shared': 15.0.0([email protected](typescript@6.0.3))
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@vueuse/[email protected]': {} '@vueuse/[email protected]': {}
'@vueuse/[email protected]': {} '@vueuse/[email protected]': {}
'@vueuse/[email protected]([email protected](typescript@5.9.3))': '@vueuse/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
'@vueuse/[email protected]([email protected](typescript@5.9.3))': '@vueuse/[email protected]([email protected](typescript@6.0.3))':
dependencies: dependencies:
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
[email protected]: {} [email protected]: {}
@@ -2129,18 +2129,18 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected](@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3))([email protected]1.0([email protected]))([email protected]([email protected]1.0([email protected]))): [email protected](@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3))([email protected]2.0([email protected]))([email protected]([email protected]2.0([email protected]))):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected])) '@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
natural-compare: 1.4.0 natural-compare: 1.4.0
nth-check: 2.1.1 nth-check: 2.1.1
postcss-selector-parser: 7.1.6 postcss-selector-parser: 7.1.6
semver: 7.8.5 semver: 7.8.5
vue-eslint-parser: 10.4.1([email protected]1.0([email protected])) vue-eslint-parser: 10.4.1([email protected]2.0([email protected]))
xml-name-validator: 5.0.0 xml-name-validator: 5.0.0
optionalDependencies: optionalDependencies:
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
[email protected]: [email protected]:
dependencies: dependencies:
@@ -2153,9 +2153,9 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]1.0([email protected]): [email protected]2.0([email protected]):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected])) '@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
'@eslint-community/regexpp': 4.12.2 '@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5 '@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.7.0 '@eslint/config-helpers': 0.7.0
@@ -2498,13 +2498,13 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected](@vue/[email protected])(typescript@5.9.3)([email protected](typescript@5.9.3)): [email protected](@vue/[email protected])(typescript@6.0.3)([email protected](typescript@6.0.3)):
dependencies: dependencies:
'@vue/devtools-api': 8.2.1 '@vue/devtools-api': 8.2.1
nostics: 1.3.0 nostics: 1.3.0
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 6.0.3
[email protected]: [email protected]:
dependencies: dependencies:
@@ -2529,19 +2529,19 @@ snapshots:
dependencies: dependencies:
hookified: 2.2.0 hookified: 2.2.0
[email protected]([email protected](typescript@5.9.3)): [email protected]([email protected](typescript@6.0.3)):
dependencies: dependencies:
'@floating-ui/dom': 1.8.0 '@floating-ui/dom': 1.8.0
'@floating-ui/vue': 1.1.11([email protected](typescript@5.9.3)) '@floating-ui/vue': 1.1.11([email protected](typescript@6.0.3))
'@internationalized/date': 3.12.4 '@internationalized/date': 3.12.4
'@internationalized/number': 3.6.8 '@internationalized/number': 3.6.8
'@tanstack/vue-virtual': 3.13.39([email protected](typescript@5.9.3)) '@tanstack/vue-virtual': 3.13.39([email protected](typescript@6.0.3))
'@vueuse/core': 14.4.0([email protected](typescript@5.9.3)) '@vueuse/core': 14.4.0([email protected](typescript@6.0.3))
'@vueuse/shared': 14.4.0([email protected](typescript@5.9.3)) '@vueuse/shared': 14.4.0([email protected](typescript@6.0.3))
aria-hidden: 1.2.6 aria-hidden: 1.2.6
defu: 6.1.7 defu: 6.1.7
ohash: 2.0.12 ohash: 2.0.12
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
@@ -2593,9 +2593,9 @@ snapshots:
fdir: 6.5.0([email protected]) fdir: 6.5.0([email protected])
picomatch: 4.0.7 picomatch: 4.0.7
[email protected](typescript@5.9.3): [email protected](typescript@6.0.3):
dependencies: dependencies:
typescript: 5.9.3 typescript: 6.0.3
[email protected]: {} [email protected]: {}
@@ -2603,18 +2603,18 @@ snapshots:
dependencies: dependencies:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
[email protected]([email protected]1.0([email protected]))(typescript@5.9.3): [email protected]([email protected]2.0([email protected]))(typescript@6.0.3):
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/[email protected]([email protected]1.0([email protected]))(typescript@5.9.3))([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/[email protected]([email protected]2.0([email protected]))(typescript@6.0.3))([email protected]2.0([email protected]))(typescript@6.0.3)
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.71.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.71.0(typescript@6.0.3)
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))(typescript@5.9.3) '@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))(typescript@6.0.3)
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
typescript: 5.9.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
typescript@5.9.3: {} typescript@6.0.3: {}
[email protected]: {} [email protected]: {}
@@ -2662,14 +2662,14 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]([email protected](typescript@5.9.3)): [email protected]([email protected](typescript@6.0.3)):
dependencies: dependencies:
vue: 3.5.43(typescript@5.9.3) vue: 3.5.43(typescript@6.0.3)
[email protected]([email protected]1.0([email protected])): [email protected]([email protected]2.0([email protected])):
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
eslint: 10.11.0([email protected]) eslint: 10.12.0([email protected])
eslint-scope: 9.1.2 eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.1
espree: 11.2.0 espree: 11.2.0
@@ -2678,13 +2678,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
[email protected]1(typescript@5.9.3): [email protected]2(typescript@6.0.3):
dependencies: dependencies:
'@volar/typescript': 2.4.28(typescript@5.9.3) '@volar/typescript': 2.4.28(typescript@6.0.3)
'@vue/language-core': 3.3.11 '@vue/language-core': 3.3.12
typescript: 5.9.3 typescript: 6.0.3
[email protected](typescript@5.9.3): [email protected](typescript@6.0.3):
dependencies: dependencies:
'@vue/compiler-dom': 3.5.43 '@vue/compiler-dom': 3.5.43
'@vue/compiler-sfc': 3.5.43 '@vue/compiler-sfc': 3.5.43
@@ -2692,7 +2692,7 @@ snapshots:
'@vue/server-renderer': 3.5.43 '@vue/server-renderer': 3.5.43
'@vue/shared': 3.5.43 '@vue/shared': 3.5.43
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 6.0.3
[email protected]: {} [email protected]: {}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"description": "pnpm 11 cannot switch to the pnpm 12 native binary: npx runs it with node",
"matchDepTypes": ["packageManager"],
"matchPackageNames": ["pnpm"],
"allowedVersions": "<12"
}
]
}
+2 -2
View File
@@ -116,7 +116,7 @@ onBeforeUnmount(() => {
/> />
</div> </div>
<div <div
class="mt-10 grid grid-cols-[minmax(0,1fr)] gap-x-10 gap-y-10 sm:mt-14 lg:grid-cols-[minmax(0,1fr)_24rem] 2xl:grid-cols-[minmax(0,1fr)_28rem]" class="mt-10 grid grid-cols-[minmax(0,1fr)] gap-x-10 gap-y-10 sm:mt-14 xl:grid-cols-[minmax(0,1fr)_24rem] 2xl:grid-cols-[minmax(0,1fr)_28rem]"
> >
<AttentionList <AttentionList
ref="list" ref="list"
@@ -133,7 +133,7 @@ onBeforeUnmount(() => {
@clear="store.clearFilters()" @clear="store.clearFilters()"
@refresh="store.refreshRepo" @refresh="store.refreshRepo"
/> />
<aside class="lg:sticky lg:top-6 lg:self-start"> <aside class="xl:sticky xl:top-6 xl:self-start">
<ProjectList <ProjectList
:repos="visibleRepos" :repos="visibleRepos"
:total="state.repos.length" :total="state.repos.length"
+1 -1
View File
@@ -89,7 +89,7 @@ const LABEL = { crit: 'Critical', warn: 'Warning', ok: 'Healthy' } as const
<ul <ul
v-else v-else
ref="scroller" ref="scroller"
class="border-border bg-surface divide-border max-h-[70vh] divide-y overflow-y-auto rounded-lg border [scrollbar-color:var(--border)_transparent] [scrollbar-width:thin] lg:max-h-[calc(100vh-7rem)]" class="border-border bg-surface divide-border max-h-[70vh] divide-y overflow-y-auto rounded-lg border [scrollbar-color:var(--border)_transparent] [scrollbar-width:thin] xl:max-h-[calc(100vh-7rem)]"
> >
<li <li
v-for="repo in visible" v-for="repo in visible"
+8 -4
View File
@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { faSpinner } from '@fortawesome/free-solid-svg-icons' import { faScrewdriverWrench, faSpinner } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { import {
AlertDialog, AlertDialog,
@@ -44,14 +44,18 @@ function press(a: ActionDef): void {
<div class="mt-2 flex flex-wrap items-center gap-2" data-test="row-actions"> <div class="mt-2 flex flex-wrap items-center gap-2" data-test="row-actions">
<template v-for="a in row.actions" :key="a.id"> <template v-for="a in row.actions" :key="a.id">
<Button <Button
variant="outline" size="sm"
size="xs" class="font-semibold shadow-sm"
:disabled="busy(a)" :disabled="busy(a)"
:aria-busy="busy(a)" :aria-busy="busy(a)"
:data-test="`action-${a.id}`" :data-test="`action-${a.id}`"
@click="press(a)" @click="press(a)"
> >
<FontAwesomeIcon v-if="busy(a)" :icon="faSpinner" spin aria-hidden="true" /> <FontAwesomeIcon
:icon="busy(a) ? faSpinner : faScrewdriverWrench"
:spin="busy(a)"
aria-hidden="true"
/>
{{ a.label }} {{ a.label }}
</Button> </Button>
<Badge v-if="busy(a)" variant="secondary" data-test="action-status">Sending</Badge> <Badge v-if="busy(a)" variant="secondary" data-test="action-status">Sending</Badge>
+1 -1
View File
@@ -1,7 +1,7 @@
import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue' import { onBeforeUnmount, onMounted, ref, type Ref } from 'vue'
/** 1 column below the breakpoint, 2 at or above it; follows the viewport live. */ /** 1 column below the breakpoint, 2 at or above it; follows the viewport live. */
export function useColumns(minWidth = '(min-width: 96rem)'): Ref<number> { export function useColumns(minWidth = '(min-width: 90rem)'): Ref<number> {
const columns = ref(1) const columns = ref(1)
let query: MediaQueryList | null = null let query: MediaQueryList | null = null
const update = () => { const update = () => {
+1 -2
View File
@@ -13,8 +13,7 @@
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"jsx": "preserve", "jsx": "preserve",
"types": ["vite/client", "node"], "types": ["vite/client", "node"],
"baseUrl": ".", "paths": { "@/*": ["./src/*"] }
"paths": { "@/*": ["src/*"] }
}, },
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"] "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
} }