orhanobut/dialogplus
Advanced dialog solution for android
Healthy across all four use cases
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓11 active contributors
- ✓Apache-2.0 licensed
- ✓CI configured
Show all 6 evidence items →Show less
- ✓Tests present
- ⚠Stale — last commit 4y ago
- ⚠Concentrated ownership — top contributor handles 61% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/orhanobut/dialogplus)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/orhanobut/dialogplus on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: orhanobut/dialogplus
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/orhanobut/dialogplus shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- 11 active contributors
- Apache-2.0 licensed
- CI configured
- Tests present
- ⚠ Stale — last commit 4y ago
- ⚠ Concentrated ownership — top contributor handles 61% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live orhanobut/dialogplus
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/orhanobut/dialogplus.
What it runs against: a local clone of orhanobut/dialogplus — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in orhanobut/dialogplus | Confirms the artifact applies here, not a fork |
| 2 | License is still Apache-2.0 | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 1602 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of orhanobut/dialogplus. If you don't
# have one yet, run these first:
#
# git clone https://github.com/orhanobut/dialogplus.git
# cd dialogplus
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of orhanobut/dialogplus and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "orhanobut/dialogplus(\\.git)?\\b" \\
&& ok "origin remote is orhanobut/dialogplus" \\
|| miss "origin remote is not orhanobut/dialogplus (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
&& ok "license is Apache-2.0" \\
|| miss "license drift — was Apache-2.0 at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java" \\
&& ok "dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java" \\
|| miss "missing critical file: dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java"
test -f "dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java" \\
&& ok "dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java" \\
|| miss "missing critical file: dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java"
test -f "dialogplus/src/main/java/com/orhanobut/dialogplus/Holder.java" \\
&& ok "dialogplus/src/main/java/com/orhanobut/dialogplus/Holder.java" \\
|| miss "missing critical file: dialogplus/src/main/java/com/orhanobut/dialogplus/Holder.java"
test -f "dialogplus/src/main/java/com/orhanobut/dialogplus/HolderAdapter.java" \\
&& ok "dialogplus/src/main/java/com/orhanobut/dialogplus/HolderAdapter.java" \\
|| miss "missing critical file: dialogplus/src/main/java/com/orhanobut/dialogplus/HolderAdapter.java"
test -f "dialogplus/src/main/res/layout/base_container.xml" \\
&& ok "dialogplus/src/main/res/layout/base_container.xml" \\
|| miss "missing critical file: dialogplus/src/main/res/layout/base_container.xml"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 1602 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~1572d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/orhanobut/dialogplus"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
DialogPlus is an Android dialog library that replaces the standard Android dialog with flexible view-based alternatives supporting ListView, GridView, or custom layouts. It provides Material Design-inspired animations (Android L style) and three positioning modes (top, center, bottom) with expand/collapse animations similar to Android's native share dialog, making it ideal for building modern dialog UIs without layout constraints. The repository is a standard Android library monorepo: dialogplus/ (main library module) contains core builder and holder classes; app/src/main/ contains the sample application demonstrating all three ContentHolder types (ListHolder, GridHolder, ViewHolder) with example layouts in app/src/main/res/layout/ and sample adapters in app/src/main/java/com/orhanobut/android/dialogplussample/.
👥Who it's for
Android developers building apps that need advanced dialog customization—particularly those building list/grid pickers, custom content dialogs, or share-like expandable dialogs who want to avoid the limitations of AlertDialog and DialogFragment boilerplate.
🌱Maturity & risk
The project appears mature and production-ready: it's published on Android Arsenal (rated #1413) and supports API 10+, but last activity is unclear from the file structure alone. The presence of CI configuration (.travis.yml), Kotlin+Java support, and established Gradle build structure suggest active maintenance. However, the lack of visible test files in the file listing and reliance on a single maintainer (orhanobut) present some maintenance risk.
Risk is moderate: the project has minimal dependencies (only androidx.appcompat, flexbox, material, kotlin-stdlib visible in app/build.gradle), reducing supply chain risk. The primary risk is single-maintainer reliance on orhanobut—no visibility into open issues or PR backlog from provided data. The codebase mixes Java (46KB) and Kotlin (25KB), which could create maintenance friction if the transition direction isn't clear.
Active areas of work
Based on the file structure, the project is stable rather than actively evolving. The sample app (MainActivity.kt, SimpleAdapter.kt) and supporting layouts (content.xml, content2.xml, footer.xml, header.xml) suggest the library is feature-complete with working examples. No specific PRs or in-progress work are visible from the file listing.
🚀Get running
Clone the repo, then build and run the sample app:
git clone https://github.com/orhanobut/dialogplus.git
cd dialogplus
./gradlew app:assembleDebug
./gradlew app:installDebug
Or import the project into Android Studio and run the app module directly. The library is published to Maven Central, so you can also just add implementation 'com.orhanobut:dialogplus:1.11@aar' to your app's build.gradle.
Daily commands:
Open in Android Studio and run the app module (app/build.gradle), or use ./gradlew app:run from command line after connecting an emulator/device. The sample app MainActivity.kt will launch with example dialogs demonstrating all positioning and content types.
🗺️Map of the codebase
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java— Main public API class that manages dialog lifecycle, showing/dismissing, and orchestrates all other components; every contributor must understand this entry point.dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java— Builder pattern implementation for constructing DialogPlus instances with configuration; critical for understanding the fluent API design and initialization flow.dialogplus/src/main/java/com/orhanobut/dialogplus/Holder.java— Abstract base class defining the interface for dialog content layouts (list, grid, view); essential abstraction that all content adapters must extend.dialogplus/src/main/java/com/orhanobut/dialogplus/HolderAdapter.java— Bridge adapter connecting Holder implementations to RecyclerView/ListView; manages binding dialog content to views and handles item interactions.dialogplus/src/main/res/layout/base_container.xml— Root layout template defining the dialog container structure; all dialog positioning and animation relies on this base layout.app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt— Sample application demonstrating all DialogPlus features and usage patterns; required reading to understand intended API usage.dialogplus/build.gradle— Library module configuration defining minimum API level (15), dependencies, and build settings; affects compatibility and feature availability.
🛠️How to make changes
Add a new dialog position (e.g., center, left)
- Add new position constant to DialogPlusBuilder enum or configuration (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java) - Create new animation XML files in anim directory matching position (e.g., slide_in_left.xml, slide_out_left.xml) (
dialogplus/src/main/res/anim/) - Update DialogPlus.java to handle new position in layout positioning logic (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java) - Test with MainActivity.kt by adding button/demo for new position (
app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt)
Create a custom Holder for specialized content (e.g., horizontal scroll)
- Create new class extending Holder interface in dialogplus package (
dialogplus/src/main/java/com/orhanobut/dialogplus/) - Implement required methods: onCreateView(), getView(), getContentView() (
dialogplus/src/main/java/com/orhanobut/dialogplus/Holder.java) - Create corresponding layout XML in dialogplus/src/main/res/layout/ (e.g., dialog_horizontal.xml) (
dialogplus/src/main/res/layout/dialog_grid.xml) - Update DialogPlusBuilder to accept your custom Holder via setHolder() method (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java) - Demonstrate usage in sample app (
app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt)
Add a new callback listener for custom dialog events
- Create new listener interface in dialogplus package (e.g., OnExpandListener.java) following pattern of OnDismissListener (
dialogplus/src/main/java/com/orhanobut/dialogplus/OnDismissListener.java) - Add listener field and setter method in DialogPlusBuilder (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlusBuilder.java) - Invoke listener callback at appropriate point in DialogPlus.java lifecycle (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java) - Implement listener in sample MainActivity to demonstrate usage (
app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt)
Modify dialog animations or add new animation styles
- Edit or create animation XML files in dialogplus/src/main/res/anim/ (
dialogplus/src/main/res/anim/slide_in_top.xml) - Reference animations in base_container.xml or specific dialog layout (
dialogplus/src/main/res/layout/base_container.xml) - Update DialogPlus.java animation setup if using custom animation logic (
dialogplus/src/main/java/com/orhanobut/dialogplus/DialogPlus.java) - Test animations visually in sample app with all positions (
app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt)
🔧Why these technologies
- Android View Framework (not Jetpack Compose) — Broad compatibility with minSdkVersion 15, mature ecosystem, and simpler integration
🪤Traps & gotchas
ViewHolder inflation: When using ViewHolder(R.layout.id), ensure the layout file exists in app/src/main/res/layout/—missing layouts will crash at dialog creation time. Expand height default: setExpanded(true) without the height parameter uses an unspecified default; always pass explicit height (e.g., setExpanded(true, 300)) for predictable behavior. Material library version: The sample app locks Material library to 1.0.0 (app/build.gradle)—upgrading may break ripple/transition animations. Kotlin-Android-Extensions deprecation: The sample app uses kotlin-android-extensions plugin (app/build.gradle), which is deprecated in newer Kotlin versions; migration to view binding may be needed for future maintenance.
🏗️Architecture
💡Concepts to learn
- Strategy Pattern (ContentHolder) — DialogPlus uses multiple Holder implementations (ListHolder, GridHolder, ViewHolder) to swap dialog content rendering strategies at runtime; understanding this pattern is essential to extending the library with new content types
- Material Design Shared Element Transitions — The library implements Android L-style animations (mentioned in README) using shared element transitions and expand/collapse morphing; knowledge of ActivityOptions.makeSceneTransitionAnimation() and TransitionSet is required to customize animations
- Android Adapter Pattern (ListAdapter/BaseAdapter) — ListHolder and GridHolder bind data via Adapter instances (SimpleAdapter.kt shows usage); understanding ListView/GridView adapter lifecycle is critical for correct data binding and memory management in dialogs
- Builder Pattern with Fluent Interface — DialogPlus.newDialog(context) returns a builder with chained method calls (.setAdapter(), .setExpanded(), etc.); this immutable construction pattern is core to the API and affects how users interact with the library
- View Inflation and LayoutInflater — ViewHolder(R.layout.id) uses LayoutInflater internally to convert XML layout resources into View objects at runtime; bugs in custom layouts will manifest as inflation failures or layout measure/draw issues
- Window Manager and Dialog Positioning — The three positioning modes (TOP, CENTER, BOTTOM) require WindowManager.LayoutParams manipulation to anchor the dialog to specific screen regions; understanding gravity, flags, and coordinate systems is needed to add custom positions
- Kotlin-Android-Extensions (Deprecated) — The sample app uses kotlin-android-extensions for view binding (app/build.gradle), now deprecated in favor of View Binding; contributors should understand this migration path for future maintenance
🔗Related repos
afollestad/material-dialogs— Direct competitor: Material Design dialog builder with similar builder pattern and content customization, but more focused on material theming and stock dialog UI rather than raw view-as-dialog flexibilityAghajari/EmojiView— Uses similar expandable dialog pattern with custom views and animations, demonstrating the same Android L share-dialog-like expand behavior for emoji keyboard selectionAndroidX/androidx.appcompat— Dependency for AppCompatActivity and Material Design baseline; DialogPlus inherits animation and theming capabilities from AppCompat's AlertDialog basematerial-components/material-components-android— Ecosystem dependency providing Material Design 3 theming and components (used via com.google.android.material:material in app/build.gradle)google/flexbox-layout— Secondary dependency visible in app/build.gradle; used for layout flexibility in dialog content samples
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Migrate kotlin-android-extensions to View Binding in app module
kotlin-android-extensions (synthetics) were deprecated in Kotlin 1.4.20 and removed in 1.5.0. The app module (MainActivity.kt, SimpleAdapter.kt) likely uses synthetics for view access. Migrating to View Binding is the official replacement and improves type safety, null safety, and build performance. This is a concrete refactoring that affects app/src/main/java/com/orhanobut/android/dialogplussample/ and app/build.gradle.
- [ ] Enable viewBinding in app/build.gradle (add viewBinding { enabled = true } to android block)
- [ ] Replace synthetic imports in app/src/main/java/com/orhanobut/android/dialogplussample/MainActivity.kt with View Binding
- [ ] Replace synthetic imports in app/src/main/java/com/orhanobut/android/dialogplussample/SimpleAdapter.kt with View Binding
- [ ] Remove kotlin-android-extensions plugin from app/build.gradle
- [ ] Test that all layout files (activity_main.xml, content.xml, simple_list_item.xml, footer.xml, header.xml) are properly referenced
Add instrumented tests for DialogPlus core functionality
The repo has a sample app (app/src/main) but no visible test directory structure (no app/src/androidTest or dialogplus/src/androidTest folders). Adding instrumented tests for the dialogplus library module would verify dialog positioning, expand/collapse behavior, and view inflation work correctly across different Android versions (API 15+).
- [ ] Create dialogplus/src/androidTest/java/com/orhanobut/dialogplus/ directory structure
- [ ] Add DialogPositioningTest to verify top/bottom/center positioning works correctly
- [ ] Add DialogExpandableTest to verify expand/collapse animations and state management
- [ ] Add DialogViewInflationTest to verify custom view and adapter handling
- [ ] Add androidTestImplementation dependencies to dialogplus/build.gradle (androidx.test:runner, androidx.test.espresso:espresso-core)
- [ ] Update .travis.yml or create GitHub Actions workflow to run androidTest on emulator
Create missing unit tests for SimpleAdapter in sample app
The sample app has SimpleAdapter.kt (app/src/main/java/com/orhanobut/android/dialogplussample/SimpleAdapter.kt) but likely no corresponding unit tests. Adding tests for adapter behavior (item binding, count, click handling) provides a concrete testing example for contributors and improves sample code quality.
- [ ] Create app/src/test/java/com/orhanobut/android/dialogplussample/SimpleAdapterTest.kt
- [ ] Add testImplementation 'junit:junit:4.13.2' and 'org.mockito:mockito-core:3.x' to app/build.gradle if missing
- [ ] Write test cases for SimpleAdapter.getCount() with various data sizes
- [ ] Write test cases for SimpleAdapter.getItem(position) boundary conditions
- [ ] Write test cases for onBindViewHolder with mock View objects
- [ ] Verify tests run with ./gradlew test command
🌿Good first issues
- Add instrumented UI tests for the three ContentHolder types (ListHolder, GridHolder, ViewHolder) under a new app/src/androidTest/ directory, covering expand/collapse animations and item click callbacks—currently no test files visible in the file listing
- Document the exact API surface of the DialogPlus.Builder class and all Holder implementations by generating Javadoc/KDoc comments and publishing to a docs/ folder; currently the README only shows usage examples, not the full public API
- Add support for DialogPlus positioning constants (TOP, CENTER, BOTTOM) as enums instead of raw integers in the core library—grep for hardcoded position values in the main module and refactor them into an enumeration for type safety
⭐Top contributors
Click to expand
Top contributors
- @orhanobut — 61 commits
- @oobut — 17 commits
- @doyonghoon — 5 commits
- @michaelizer — 4 commits
- @MonikaJethani — 3 commits
📝Recent commits
Click to expand
Recent commits
40dcdad— Merge pull request #184 from MonikaJethani/master (orhanobut)440c48a— Updated .travis.yml (MonikaJethani)e912914— Updated Gradle wrapper and jar (MonikaJethani)bffb5f8— Migrated to AndroidX (MonikaJethani)291bf4d— Merge pull request #161 from TurboProgramming/patch-1 (orhanobut)7c775c4— Replacement of "compile" with "implementation" (ardacebi)f7d6998— Merge pull request #157 from michaelizer/master (orhanobut)1081af6— Merge pull request #158 from orhanobut/v2 (orhanobut)d02ecf2— Ignore star imports since kotlin might have it. (oobut)357faa7— Fix tests (oobut)
🔒Security observations
This Android project has significant security concerns primarily related to outdated dependencies and configurations. The compileSdkVersion 28 (2018), minSdkVersion 15 (2012), and obsolete AndroidX/Material libraries create a substantial attack surface and miss critical security patches from recent years. ProGuard obfuscation is disabled in release builds, and lint errors are suppressed, which collectively indicate a lack of security-focused development practices. Immediate action required: update all dependencies to current versions, increase API targets to
- High · Outdated Android Compilation Target —
app/build.gradle. The project uses compileSdkVersion 28 (Android 9.0, released in 2018). Modern Android projects should target API 34+ (Android 14). Using outdated compilation targets means missing critical security patches, modern security features, and best practices introduced in newer Android versions. Fix: Update compileSdkVersion to at least 34 (Android 14). Review and update targetSdkVersion to match or be close to compileSdkVersion. - High · Outdated AndroidX Dependencies —
app/build.gradle. The project uses androidx.appcompat:appcompat:1.0.0 (released in 2018) and com.google.android.material:material:1.0.0. These versions contain multiple known security vulnerabilities and lack modern security features and bug fixes. Fix: Update androidx.appcompat:appcompat to version 1.6.1+ and com.google.android.material:material to 1.11.0+. - High · Outdated Kotlin Version —
app/build.gradle. The project uses Kotlin via variable 'kotlin_version' which is not specified in the provided build.gradle. Based on the Android API level (28) and appcompat version (1.0.0), this is likely an old Kotlin version with potential security issues. Fix: Explicitly define and update kotlin_version to 1.9.0+ in the project's build.gradle properties. - Medium · ProGuard Disabled in Release Build —
app/build.gradle. The release build has minifyEnabled set to false, which means code obfuscation is disabled. This makes reverse engineering the application easier and increases the risk of intellectual property theft and security vulnerability discovery. Fix: Set minifyEnabled to true in release builds: 'minifyEnabled true' to enable ProGuard/R8 obfuscation. - Medium · Low Minimum SDK Version —
app/build.gradle. minSdkVersion is set to 15 (Android 4.0.3, released in 2012). Supporting such old API levels means the app must avoid modern security APIs and may be vulnerable to OS-level security issues patched in newer Android versions. Fix: Increase minSdkVersion to at least 21 (Android 5.0). This allows use of modern security APIs and reduces the attack surface significantly. - Medium · Lint Errors Ignored —
app/build.gradle. The build configuration has 'abortOnError false' in lintOptions, which suppresses Android lint security warnings. This could hide important security issues detected by Android's static analysis tools. Fix: Remove or set 'abortOnError true' to ensure lint errors are caught during build time. Address any lint warnings rather than suppressing them. - Low · Deprecated Gradle Plugin Declaration —
app/build.gradle. The project uses 'kotlin-android-extensions' which has been deprecated since Kotlin 1.4.20. While not a direct security issue, using deprecated tools can lead to missed updates and best practices. Fix: Replace 'kotlin-android-extensions' with the modern approach using View Binding or other contemporary solutions recommended in current Kotlin documentation. - Low · Outdated Flexbox Library —
app/build.gradle. com.google.android:flexbox:0.3.2 is an older version (released around 2017). While functionality may not have changed significantly, staying on outdated dependencies increases cumulative risk. Fix: Update com.google.android:flexbox to the latest stable version (currently 1.1.1+).
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.