// — mara's custom issue report: browse open issues // across every repo that has one, sortable/filterable her own way, with // "hide blocked (open dependency)" as the headline filter and a rank // column for how many other open issues depend on each one. Backed by // swarm-controller's `GET /api/repos` (dropdown source, only repos with // an open issue), `GET /api/issue-report` (default — every repo // combined) and `GET /api/repos/{org}/{repo}/issue-report` (once a // specific repo is picked) — `blocked`, `depended_on_by_count` and // `transitively_blocks_count` all arrive pre-resolved per row, so // nothing here does its own dependency-graph walk. // // Sorting and the label/hide-blocked filters are client-side over // whatever's currently loaded — only the repo selection re-fetches. // Default sort is `depended_on_by_count` descending: mara's own framing // ("i would also like to rank them by how many issues depend on them") // reads as the report's headline ordering, not just one more column. // // No `RefreshIntervalPicker`/auto-poll like the roster pages — this // reads as a "generate a report" action rather than a live status view // (mara: a "generating report" spinner sub-5s is fine, doesn't need to // be instant), so it fetches once per repo-selection change instead of // polling. // // Sort/filter state persists via the shared `useLocalSetting` hook (same // plumbing the theme/motion overrides use) — mara: "local storage" over // URL params. `labelFilter` is `string[]`, not `Set`: a `Set` // serializes to `"{}"` through `JSON.stringify` and silently loses its // contents, which is exactly what this hook round-trips through. import { useEffect, useMemo, useState } from "preact/hooks"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge } from "@hive/shared/badge.js"; import { useLocalSetting } from "@hive/shared/settings-storage.js"; import { Panel } from "../ui/panel/Panel.js"; import { SelectField } from "../ui/select-field/SelectField.js"; import { Table, type TableColumn } from "../ui/table/Table.js"; import "./IssueReportPage.css"; interface IssueReportRow { repo: string; number: number; title: string; labels: string[]; // `string[]`, not a single `assignee` — forge's own `assignee` field is // the legacy single-value one; `assignees` is the real multi-assignee // list, and this repo actually uses multiple (caught against an // earlier draft of this row shape that had it singular). assignees: string[]; html_url: string | null; blocked: boolean; depended_on_by_count: number; transitively_blocks_count: number; } type SortKey = | "repo" | "number" | "title" | "assignees" | "blocked" | "depended_on_by_count" | "transitively_blocks_count"; type SortDir = "asc" | "desc"; // Sentinel ` setHideBlocked((e.target as HTMLInputElement).checked) } /> hide blocked (open dependency) {allLabels.length ? (
{allLabels.map((l) => ( ))}
) : null} {error ? ( ) : null} {!error && loading ?

generating report…

: null} {!loading && rows ? ( `${r.repo}#${r.number}`} emptyMessage="no issues match the current filters" /> ) : null} ); }