diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2cd252ae..00a16a52 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ name: Bug report description: Report a reproducible bug (one per issue). -title: "[Bug]: " +title: "[Bug]: short summary here" labels: - bug body: @@ -10,6 +10,9 @@ body: Thanks for reporting a bug. If we can reproduce it, we can usually fix it. This form is just to get the basics in one place. + Please replace the default title with a short summary of the actual problem. + + If the app crashes, logs are required. Crash reports without logs may be labeled `needs-info`. - type: markdown attributes: @@ -197,10 +200,11 @@ body: - type: textarea id: logs attributes: - label: Logs (optional but helpful) + label: Logs (required for crash reports) description: | - Not required, but super helpful for playback/crash issues. - If you can, include a short snippet from around the time the bug happens. + Required if the app crashes or force closes. + For other bug reports, logs are optional but still helpful. + Include a short snippet from around the time the bug happens. render: shell placeholder: | adb logcat -d | tail -n 300 diff --git a/.github/workflows/triage-needs-info.yml b/.github/workflows/triage-needs-info.yml index 7b36afc6..9143c366 100644 --- a/.github/workflows/triage-needs-info.yml +++ b/.github/workflows/triage-needs-info.yml @@ -20,6 +20,7 @@ jobs: const issue_number = context.payload.issue.number; const issue = context.payload.issue; + const title = (issue.title || "").trim(); const body = issue.body || ""; const labels = (issue.labels || []).map(l => (typeof l === "string" ? l : l.name).toLowerCase()); @@ -43,9 +44,31 @@ jobs: return section.trim(); } + function extractFirstSection(titles) { + for (const sectionTitle of titles) { + const value = extractSection(sectionTitle); + if (value) return value; + } + return ""; + } + + function normalizeText(value) { + return (value || "").replace(/\s+/g, " ").trim(); + } + + function stripIssuePrefix(value) { + return normalizeText(value).replace(/^\[[^\]]+\]:\s*/i, "").trim(); + } + const steps = extractSection("Steps to reproduce"); const expected = extractSection("Expected behavior"); const actual = extractSection("Actual behavior"); + const logs = extractFirstSection([ + "Logs (required for crash reports)", + "Logs (optional but helpful)", + ]); + const extra = extractSection("Anything else? (optional)"); + const summaryTitle = stripIssuePrefix(title); // Only triage bug-style issues. // Skip feature/enhancement issues so they don't get "missing repro" comments. @@ -62,9 +85,23 @@ jobs: // Keep checks simple and forgiving: required fields can still be low-effort (e.g. "doesn't work"). const problems = []; + const genericTitle = /^(bug|issue|problem|help|question|crash|broken|error|bug report|short summary here|title here)$/i; + const numericOnlyTitle = /^#?\d+$/; + const crashPattern = /\b(crash|crashes|crashed|crashing|force close|force closes|force closed|fatal exception|app closes|app closed unexpectedly)\b/i; + const crashContext = [summaryTitle, steps, actual, extra].map(normalizeText).join("\n"); + const isCrashIssue = crashPattern.test(crashContext); + const normalizedLogs = normalizeText(logs); + const hasLogs = normalizedLogs.length >= 20 && !/^(n\/a|na|none|no|not available)$/i.test(normalizedLogs); + + if (!summaryTitle || summaryTitle.length < 8 || genericTitle.test(summaryTitle) || numericOnlyTitle.test(summaryTitle)) { + problems.push("Issue title (replace the default `[Bug]:` prefix with a short summary of the actual problem)"); + } if (!steps || steps.length < 30) problems.push("Steps to reproduce (please list exact steps)"); if (!expected || expected.length < 10) problems.push("Expected behavior"); if (!actual || actual.length < 10) problems.push("Actual behavior (include any on-screen error text)"); + if (isCrashIssue && !hasLogs) { + problems.push("Logs (required for crash reports; include a short logcat snippet or stack trace)"); + } async function ensureLabel(name, color, description) { try { @@ -99,7 +136,8 @@ jobs: `Missing / too short:\n` + problems.map(p => `- ${p}`).join("\n") + `\n\n` + - `Logcat is optional, but if this is playback/crash related it helps a lot.\n` + + `Use a specific title, for example: \`[Bug]: Continue Watching shows percent instead of time left\`.\n` + + `${isCrashIssue ? `Crash reports must include logs.\n` : `Logcat is optional for most issues, but it helps a lot.\n`}` + `Example: \`adb logcat -d | tail -n 300\``; // Avoid spamming duplicate bot comments on every edit. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17080515..e41e3bde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,6 +32,7 @@ Translation PRs are allowed, as long as they stay focused on translation/localiz To keep issues fixable, bug reports should include: +- A short, specific issue title that describes the bug - App version (release version or commit hash) - Platform + device model + Android version - Install method (release APK / CI / built from source) @@ -39,7 +40,9 @@ To keep issues fixable, bug reports should include: - Expected vs actual behavior - Frequency (always/sometimes/once) -Logcat is **optional**, but it helps a lot for playback/crash issues. +Do not leave the title as just `[Bug]:` or another generic placeholder. + +Logcat is optional for most issues, but it is **required** for crash / force-close reports. ### How to capture logs (optional) @@ -49,7 +52,7 @@ If you can, reproduce the issue once, then attach a short log snippet from aroun adb logcat -d | tail -n 300 ``` -If the issue is a crash, also include any stack trace shown by Android Studio or `adb logcat`. +If the issue is a crash, include a stack trace or log snippet from Android Studio or `adb logcat`. ## Feature requests (rules)