Skip to content

Phase 1a–1c: Dioxus fullstack open-source health dashboard (PRs, Devin workbench, Releases, CI) - #1

Draft
jkelleyrtp wants to merge 30 commits into
masterfrom
devin/1789166688-health-dashboard
Draft

jkelleyrtp wants to merge 30 commits into
masterfrom
devin/1789166688-health-dashboard

Conversation

@jkelleyrtp

@jkelleyrtp jkelleyrtp commented Sep 11, 2026 •

Copy link
Copy Markdown
Member

Summary

Replaces the hello-world scaffold with a Dioxus 0.8.0-alpha.1 fullstack app (packages/status, crate dioxus-status) that collects GitHub + crates.io data into SQLite, serves the dashboard pages, and drives Devin sessions against PRs. Design lives in DESIGN.md, implementation notes in ARCHITECTURE.md.

Data collection (src/backend/, server feature only)

  • github.rs: GraphQL over reqwest for org repos, open + recently closed/merged PRs (review decision, mergeability, CI rollup, review threads, files, linked issues, timeline → last_activity_by ∈ {maintainer, contributor, bot}), open + recently closed issues, releases. Paginated, rate-limit aware. Items that vanish from the open set are marked closed with a targeted lifecycle update (rich rows are never replaced by partial ones).
  • Auth: GitHub App installation token via octocrab AppAuth (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY[_PATH], installation discovered by org unless GITHUB_APP_INSTALLATION_ID is set), falling back to GITHUB_TOKEN. The app gives membersWithRole, so maintainer classification works.
  • crates_io.rs: per-crate totals + 90-day daily downloads + versions for a configurable crate list.
  • scoring.rs: deterministic 0–100 mergeability score with a per-factor breakdown (ScorePart). Tested.
  • collector.rs: periodic syncs with per-kind mutex, manual sync_now(kind), plus a Devin poller (≤20 pending sessions / 60s).
  • db.rs + migrations/000{1,2,3}_*.sql: sqlx runtime queries, DB at $DATA_DIR/status.db.
  • auth.rs: v1 admin = ADMIN_TOKEN → HMAC cookie; require_admin() gates every mutating server fn.

Devin workbench (devin.rs, actions.rs) — v3 org-scoped API ({DEVIN_API_BASE}/v3/organizations/{DEVIN_ORG_ID}/sessions; the enterprise service-user key only works there, not on /v1).

  • Action kinds: assess rebase address_reviews deslop add_tests deep_review split changelog_entry custom. Every prompt carries the PR facts (title/body/labels/CI/mergeability/threads/files) and the rules: never force-push the contributor branch, push to devin/pr-{n}-{kind}, no Co-Authored-By.
  • assess is the lazy LLM path: structured_output_schema + max_acu_limit: 2, result cached in pr_assessments keyed by (repo, number, head_sha); assess_pr(force=false) returns the cache when the head hasn't moved. No background sweeps.
  • Daily budget (llm_budget, default 50, overridable in Settings): budget_check_now() → create_session → budget_consume(), so a failed API call costs nothing.
  • map_status(status, detail, has_result): structured-output sessions park at running/waiting_for_user when done, so a populated result maps to finished unless detail == working. Tested.
  • Server fns: dispatch_action, assess_pr, get_assessment, list_sessions (prompt stripped for non-admins), refresh_session, send_session_message, get_devin_status, set_llm_budget.

Server fns (src/api/mod.rs): get_overview, list_prs(PrFilter), get_pr, list_issues, list_repos, get_repo, get_health, get_settings, admin_login/logout, set_monitored_repos, set_crates, sync_now + the Devin set above. list_prs compiles a GitHub-style query (repo:x author:y label:z -label:z is:draft|green|red|approved|conflict|stale|first-timer + free text) into a bound SQL statement. Server fns that take args are #[post] — #[get] with args fails on the client (no body on GET).

UI (src/ui/): GitHub-style top navbar (header.topnav, html { background: var(--bg) } so overscroll doesn't flash), Overview (health strip + ready-to-merge / quick wins / going stale / waiting-on-maintainer / first-timers / Devin activity), PR table with search + chips + sort + expandable row (score breakdown, assessment card, Devin action buttons, session history), Issues, Repos + drill-down, Health (SVG charts), Bots (budget, session table, refresh/message), Settings (admin login, monitored repos, crates, budget, sync), Releases (below).

PR table details:

  • Rows never disappear on refetch: see the use_cached hook below; a small syncing… marker sits by the filter bar while a refetch runs.
  • Column picker (PR_COLUMNS, default hides # and last by, persisted in localStorage['prs.columns']); author association is a color class on the name (.author.maintainer|first-timer|bot) instead of a text badge.
  • Admins get an always-on devin column with a per-row Assess button (assess_pr(force=false) → expands the row to show the card).
  • Repos page sorts client-side (recently pushed default, stars, PRs, issues, name); db::list_repos now orders by pushed_at DESC.
  • document::eval("return x").recv() never resolves — return values come via .await; both the ?q= URL init and the localStorage read use .await.

Navigation cache (ui/cache.rs): use_cached(key, fetch) -> Cached<T> wraps use_loader with a web-only, in-memory (thread-local) stale-while-revalidate map keyed by a string that includes all query args. Only a first-ever uncached visit / SSR suspends; revisits render the last value instantly and refetch in the background. Chosen over localStorage because SSR already delivers data on hard reloads, so persisting payloads would only add hydration-mismatch risk. Preferences (columns, overview repo filter, debounced search) go through ui/storage.rs (use_persisted_signal, use_debounced).

Overview repo filter: get_overview(repos: Option<Vec<String>>) — None = all repos, Some([]) = none (AND 1=0), otherwise AND repo IN (?,…) applied to counts, stars, stars-delta, downloads (via crates.repo) and all six PR lists. Client model is enum RepoFilter { All, Only(Vec<String>) } persisted in localStorage['overview.repos']; unchecking from All expands to every monitored repo minus that one, and a full Only set collapses back to All. Menu rows use MenuCheckbox (a plain button) because the vendored Checkbox calls set_focus on click, which blurs the dropdown trigger and closes the menu.

Deslop pass (per deslop-rust-skill): typed ActionKind / SyncKind / TargetKind / SizeBucket enums (FromStr/Display produce the exact strings stored in the DB and sent on the wire — no format change), named constants for limits/windows/budgets, From<&DbPr> for PrSummary, days_since_rfc3339, str_list, db::upsert_release, urlencoding crate replacing hand-rolled percent-decoding (which mangled multi-byte UTF-8 in ?q=).

Releases (Phase 1c) (ui/releases.rs, migrations/0004_releases.sql)

  • /releases: card per monitored repo — latest stable tag, days since (severity-colored), latest prerelease, unreleased merged-PR count, median cadence of recent stable gaps, next open milestone, must-ship open/total.
  • /releases/:repo: last 10 releases, unreleased changelog since the latest stable tag (one fixed-column table, grouped Breaking / Features / Fixes / Docs / Other / Internal by label + feat:/fix: prefix + bot author), open milestones with progress, must-ship checklist, copy-as-markdown.
  • Collector: sync_releases → sync_milestones (REST; milestones closed upstream are deleted) → sync_merged_since(latest stable date, 180d fallback) via GraphQL search is:merged merged:>=…, capped at 300/repo, written with the lifecycle-aware update so rich rows survive.
  • release_targets (pr|issue|note, optional number, manual done); linked PR merged / issue closed counts as complete automatically. Add/remove/toggle are require_admin(); reads are public.

CI: .github/workflows/ci.yml — fmt check, both clippy invocations, server tests (cargo only, no dx).

Hydration note: list data uses use_server_future (not use_resource().suspend()), which is what serializes results into the SSR payload; without it the client crashed in hydrate_node and no row listeners were bound.

Build: dx builds with cargo profiles server-dev (always with --target x86_64-unknown-linux-gnu) and wasm-dev; both are declared in the workspace Cargo.toml so the README's cargo check/clippy/test --profile … --target … commands share dx's artifacts (verified: dx build after them recompiles 0 crates). cargo test builds its own dep tree once because the dioxus-ssr dev-dep changes feature resolution.

Vendored (vendor/, src/components/): dioxus-primitives and dioxus-attributes from dioxus-components' 0.8 branch bumped to alpha.1, a 4-icon inline-SVG replacement for dioxus-icons, and a minimal dioxus-sdk-time shim.

Known gaps / notes

  • dx serve needs --hot-patch false --hot-reload false on rustc 1.94.
  • Voting, backlog/goals, auto-repro and deploy are later phases.

Screenshots

prs
columns
releases
release detail
bots
pr assessed
overview repo filter
overview

Running locally

cp .env.example .env   # GITHUB_TOKEN or GitHub App vars, ADMIN_TOKEN, DEVIN_API_KEY/DEVIN_ORG_ID/DEVIN_API_BASE
dx serve --package dioxus-status --web --port 8080 --hot-patch false --hot-reload false

Link to Devin session: https://dioxus.staging.devinenterprise.com/sessions/2203bdc5b6e5485fab5c3f02ddd15e9b
Open in Devin Desktop: https://dioxus.staging.devinenterprise.com/desktop/session/2203bdc5b6e5485fab5c3f02ddd15e9b?variant=devin-insiders
Requested by: @jkelleyrtp

@staging-devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

…ration

- stars_7d_delta is null unless a snapshot >=7 days old exists; UI renders
  it as a signed count
- sync closed/merged PRs (updatedAt > 30d, paginated pullRequests
  states:[MERGED,CLOSED]) and closed issues per monitored repo; chosen over
  GraphQL search because it avoids the search rate limit
- fix use_effect debounce loop in prs/issues that read-and-wrote the same
  signal, spawning unbounded tasks and exhausting the wasm externref table
  ('table grow failure' broke hydration on /prs)
- wrap PR table and repo detail in SuspenseBoundary + suspend() so SSR
  renders real data
- pin wasm-bindgen =0.2.121 to match dx 0.8.0-alpha.1's bundled CLI
Sync carry-overs: closed PR/issue queries fetch labels and update only
lifecycle columns; open-issue sync marks DB rows absent from the fetch as
closed. GitHub App auth via octocrab AppAuth (JWT mint + installation token
refresh handled internally); PAT remains the fallback.
backend/devin.rs talks to the org-scoped v3 sessions API; backend/actions.rs
renders per-kind prompts and dispatches sessions behind a daily llm_budget;
collector polls pending sessions every 60s; new server fns for dispatch,
assess (cached per head_sha), session list/refresh/message, devin status.
- consume daily budget only after devin create_session succeeds
- map_status treats populated structured_output/pull_requests as finished
  regardless of devin status (structured_output_required sessions park at
  running/waiting_for_user); poll_session upserts pr_assessments on it
- use_server_future instead of use_resource for list_prs/get_repo so
  results serialize for hydration; fixes client-side hydrate_node crash
  that left all /prs row clicks unbound
- get_assessment/list_sessions switched to #[post] (GET server fns
  cannot send args in a request body)
…ck_now

- declare [profile.server-dev]/[profile.wasm-dev] in workspace Cargo.toml
  (inherit dev) so plain cargo uses the same profiles dx builds with;
  canonical commands documented in README/ARCHITECTURE. Dep artifacts
  share with dx when --target matches dx's layout; cargo test builds a
  parallel tree once because the dioxus-ssr dev-dep changes features
- map_status: (running, working, has_result) stays working so sessions
  that produce a result mid-run keep being polled
- commit missing db::budget_check_now used by actions::dispatch
@staging-devin-ai-integration staging-devin-ai-integration Bot changed the title Phase 1a: Dioxus fullstack open-source health dashboard Phase 1a+1b: Dioxus fullstack health dashboard with Devin workbench Sep 12, 2026
- replace sidebar with sticky header.topnav; active link via route prefix,
  admin badge when logged in
- html { background, color-scheme: dark } + data-theme=dark on mount so
  overscroll and dxc components stay dark
- AdminState context provided by Layout, populated via api::is_admin();
  settings login updates it
- author association text badge replaced by colored .author.<kind> name
  with the association word in title (prs, issues, repo detail)
- filter-bar controls share one rule (34px, 6px radius); Columns button
  no longer a pill
- PrsTable keeps last_rows and renders them from the suspense fallback
  during refetch (no loading flash); 'syncing...' indicator while pending;
  committed only set when the query actually changes
- Columns popover: pick visible columns, persisted to localStorage
  'prs.columns'; number/last_by hidden by default; title locked on
- admin-only 'devin' column with per-row Assess button (expands row on
  success, spinner + inline error)
- document::eval: use .await (join) instead of .recv() — recv waits for
  dioxus.send() and never resolves on 'return', so URL ?q= init and the
  localStorage read were dead; both work now
- repos page: sort select (pushed/stars/prs/issues/name), list_repos
  orders by pushed_at desc
- ci.yml: fmt, clippy server+web (dx profiles), test; no dx install
@staging-devin-ai-integration staging-devin-ai-integration Bot changed the title Phase 1a+1b: Dioxus fullstack health dashboard with Devin workbench Phase 1a+1b: Dioxus fullstack open-source health dashboard with Devin workbench Sep 12, 2026
@staging-devin-ai-integration staging-devin-ai-integration Bot changed the title Phase 1a+1b: Dioxus fullstack open-source health dashboard with Devin workbench Phase 1a–1c: Dioxus fullstack open-source health dashboard (PRs, Devin workbench, Releases, CI) Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant