Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

83 changes: 83 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Bug Report
description: Report a reproducible FluidVoice bug.
title: "[BUG] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
If you are unsure whether this is a FluidVoice bug, please start a Discussion first:
https://github.com/altic-dev/FluidVoice/discussions
- type: textarea
id: description
attributes:
label: Describe the bug
description: Briefly describe what is broken.
placeholder: FluidVoice crashes when...
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction steps
description: Provide exact steps a maintainer can follow.
placeholder: |
1. Open FluidVoice
2. Configure...
3. Press...
4. Observe...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: What should have happened?
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual behavior
description: What happened instead?
validations:
required: true
- type: input
id: app-version
attributes:
label: App Version
placeholder: "1.2.3"
validations:
required: true
- type: input
id: macos-version
attributes:
label: macOS Version
placeholder: "macOS 15.5"
validations:
required: true
- type: dropdown
id: architecture
attributes:
label: Architecture
options:
- Apple Silicon
- Intel
- Unknown
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs or crash report
description: Paste relevant logs or crash output when available.
render: text
validations:
required: false
- type: upload
id: media
attributes:
label: Screenshots or screen recording
description: Attach screenshots or a recording if they help explain the bug.
validations:
required: false
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Start a discussion or ask a question
url: https://github.com/altic-dev/FluidVoice/discussions
about: Use Discussions for support questions, unclear reports, ideas, and open-ended design conversations.
- name: Propose a feature idea
url: https://github.com/altic-dev/FluidVoice/discussions/new?category=ideas
about: Feature ideas should start as Discussions before they become prioritized issues.
19 changes: 0 additions & 19 deletions .github/ISSUE_TEMPLATE/feature_request.md

This file was deleted.

36 changes: 36 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea for FluidVoice.
title: "[FEATURE] "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Feature ideas should start in Discussions so maintainers and users can shape the request before it becomes tracked work.

Open a feature idea Discussion here:
https://github.com/altic-dev/FluidVoice/discussions/new?category=ideas

Use this issue form only for features that already have maintainer acceptance, roadmap priority, or a linked Discussion that has been accepted.
- type: textarea
id: problem
attributes:
label: Problem
description: What user problem does this solve?
placeholder: Users currently cannot...
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: Describe the requested behavior.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Mention workarounds or other approaches that were discussed.
validations:
required: false
123 changes: 123 additions & 0 deletions .github/policy/enforce-issue-intake.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { readFileSync } from "node:fs";

import { validateBugIssue } from "./github-policy.mjs";
import {
addLabels,
ensureLabel,
githubRequest,
paginate,
removeLabel,
repoContext,
upsertIssueComment,
} from "./github-api.mjs";

const BUG_MARKER = "<!-- fluidvoice-bug-intake -->";
const STALE_MARKER = "<!-- fluidvoice-stale-repro -->";
const STALE_REPRODUCTION_DAYS = 14;

const LABELS = {
needsReproduction: {
name: "needs reproduction",
color: "D93F0B",
description: "Bug report needs enough detail to reproduce.",
},
};

function eventPayload() {
return JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
}

function missingFieldsText(fields) {
return fields.map((field) => `- ${field}`).join("\n");
}

async function existingBugWarningComment(context, issueNumber) {
const comments = await paginate(
`/repos/${context.owner}/${context.repo}/issues/${issueNumber}/comments`,
);
return comments.find((comment) => comment.body?.includes(BUG_MARKER));
}

async function enforceBugIssue(context, issue) {
const result = validateBugIssue(issue.body ?? "");

if (result.ok) {
await removeLabel(context, issue.number, LABELS.needsReproduction.name);
return;
}

await addLabels(context, issue.number, [LABELS.needsReproduction.name]);
await upsertIssueComment(
context,
issue.number,
BUG_MARKER,
[
"This bug report is missing information required for maintainers to reproduce it.",
"",
"Please update the issue with:",
missingFieldsText(result.missing),
"",
`Issues still missing reproduction details after ${STALE_REPRODUCTION_DAYS} days may be closed.`,
].join("\n"),
);
}

async function closeStaleReproductionIssues(context) {
const staleBefore = new Date(
Date.now() - STALE_REPRODUCTION_DAYS * 24 * 60 * 60 * 1000,
)
.toISOString()
.slice(0, 10);
const query = encodeURIComponent(
`repo:${context.owner}/${context.repo} is:issue is:open label:"${LABELS.needsReproduction.name}" updated:<${staleBefore}`,
);
const results = await githubRequest("GET", `/search/issues?q=${query}&per_page=100`);

for (const issue of results.items) {
const warningComment = await existingBugWarningComment(context, issue.number);
if (!warningComment) continue;

await upsertIssueComment(
context,
issue.number,
STALE_MARKER,
[
`Closing this issue because it has been labeled \`needs reproduction\` for at least ${STALE_REPRODUCTION_DAYS} days without the missing reproduction details.`,
"",
"If you can still reproduce the problem, please open a new bug report with exact steps, expected behavior, actual behavior, app version, macOS version, and architecture.",
].join("\n"),
);
await githubRequest("PATCH", `/repos/${context.owner}/${context.repo}/issues/${issue.number}`, {
state: "closed",
state_reason: "not_planned",
});
}
}

async function main() {
const context = repoContext();
await Promise.all(Object.values(LABELS).map((label) => ensureLabel(context, label)));

const eventName = process.env.GITHUB_EVENT_NAME;
if (eventName === "schedule" || eventName === "workflow_dispatch") {
await closeStaleReproductionIssues(context);
return;
}

const event = eventPayload();
const issue = event.issue;
if (!issue || issue.pull_request) return;

const warningComment = await existingBugWarningComment(context, issue.number);
if (event.action !== "opened" && !warningComment) return;
Comment on lines +111 to +112

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Revalidate bug issues after clean opens

In .github/workflows/issue-intake.yml the job runs on edited and reopened; if a bug report was valid when opened, no warning comment exists, so a later edit removing reproduction fields returns before validation and never applies needs reproduction. This leaves incomplete bug reports outside the stale-close path; revalidate bug issues on every issue event and use the existing comment only for comment updates.

Useful? React with πŸ‘Β / πŸ‘Ž.


const labels = issue.labels.map((label) =>
typeof label === "string" ? label : label.name,
);

if (labels.includes("bug")) {
await enforceBugIssue(context, issue);
}
}

await main();
Loading
Loading