diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..a4f7dbaa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,118 @@ +name: Bug Report +description: Report a reproducible problem in Netcatty +title: "[Bug] " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug. Incomplete reports may be closed automatically. + Please search [existing issues](https://github.com/binaricat/Netcatty/issues) first. + + - type: dropdown + id: platform + attributes: + label: Operating system + options: + - macOS + - Windows + - Linux + validations: + required: true + + - type: input + id: version + attributes: + label: Netcatty version + description: Find it in Settings > Application, or on the [latest release](https://github.com/binaricat/Netcatty/releases/latest) page. + placeholder: "e.g. 1.2.3" + validations: + required: true + + - type: dropdown + id: install_source + attributes: + label: How did you install Netcatty? + options: + - GitHub Release (.dmg / .exe / .AppImage / .deb) + - Homebrew + - Built from source (npm run dev / pack) + - Other + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Affected area + multiple: true + options: + - SSH connection / terminal + - SFTP / file browser + - Host vault / keychain + - Port forwarding + - Snippets + - AI assistant + - Settings / sync + - UI / layout + - Crash / app won't start + - Other + validations: + required: true + + - type: dropdown + id: reproducibility + attributes: + label: Can you reproduce it? + options: + - Always (100%) + - Often (>50%) + - Sometimes + - Once / not sure + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Numbered steps so we can follow exactly. + placeholder: | + 1. Open Netcatty and connect to host X + 2. Click SFTP tab + 3. ... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behavior + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs / screenshots + description: | + Optional but helpful. Crash logs: Settings > System > Crash Logs > Open folder. + For SSH errors, include redacted connection details (no passwords / private keys). + placeholder: Paste relevant log lines or attach screenshots. + + - type: checkboxes + id: checklist + attributes: + label: Before submitting + options: + - label: I searched existing issues and did not find a duplicate + required: true + - label: I removed passwords, private keys, and other secrets from this report + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..c5ae4733 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & general help + url: https://github.com/binaricat/Netcatty/discussions + about: Not sure if it is a bug? Ask in Discussions first. + - name: Latest release + url: https://github.com/binaricat/Netcatty/releases/latest + about: Check your Netcatty version before reporting. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..634c407e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,72 @@ +name: Feature Request +description: Suggest an improvement or new capability +title: "[Feature] " +labels: ["enhancement", "triage"] +body: + - type: markdown + attributes: + value: | + Describe the problem you are trying to solve and the change you want. + Vague requests like "make it better" may be closed. + + - type: textarea + id: problem + attributes: + label: Problem / pain point + description: What is hard, missing, or frustrating today? + placeholder: When I manage 50+ hosts, I cannot ... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed solution + description: What would you like Netcatty to do? + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other tools, workarounds, or designs you thought about. + validations: + required: true + + - type: dropdown + id: area + attributes: + label: Related area + multiple: true + options: + - SSH / terminal + - SFTP + - Host vault / keychain + - Port forwarding + - Snippets + - AI assistant + - Settings / sync + - UI / UX + - Other + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: How important is this to you? + options: + - Nice to have + - Would improve my daily workflow + - Blocking / critical for my use case + validations: + required: true + + - type: checkboxes + id: checklist + attributes: + label: Before submitting + options: + - label: I searched existing issues and discussions for similar requests + required: true diff --git a/.github/workflows/issue-format.yml b/.github/workflows/issue-format.yml new file mode 100644 index 00000000..87a911c0 --- /dev/null +++ b/.github/workflows/issue-format.yml @@ -0,0 +1,139 @@ +name: issue-format + +on: + issues: + types: [opened, edited] + +permissions: + issues: write + +jobs: + validate: + runs-on: ubuntu-latest + # Skip issues opened by bots (e.g. dependabot) and maintainers fixing format + if: >- + github.event.issue.user.type != 'Bot' && + !contains(github.event.issue.labels.*.name, 'format-exempt') + steps: + - name: Validate title and body + uses: actions/github-script@v7 + with: + script: | + const issue = context.payload.issue; + const title = issue.title.trim(); + const body = (issue.body || '').trim(); + const errors = []; + + const modernTitle = /^\[(Bug|Feature)\] .{8,}/.test(title); + const legacyAppTitle = /^Bug:\s*.{5,}/i.test(title); + if (!modernTitle && !legacyAppTitle) { + errors.push( + 'Title must start with `[Bug]` or `[Feature]` followed by a short summary (at least 8 characters after the prefix). Legacy app links using `Bug: ...` are also accepted. Example: `[Bug] SFTP upload fails on Windows`' + ); + } + + if (body.length < 120) { + errors.push( + 'Body is too short. Please use the Bug Report or Feature Request template and fill in all required fields.' + ); + } + + const templateMarkers = [ + 'Steps to reproduce', + 'Expected behavior', + 'Actual behavior', + 'Describe the problem', + 'Problem / pain point', + 'Proposed solution', + 'Operating system', + ]; + const hasTemplateStructure = templateMarkers.some((marker) => + body.includes(marker) + ); + if (!hasTemplateStructure) { + errors.push( + 'Body does not look like it came from an issue template. Choose **Bug Report** or **Feature Request** when opening an issue.' + ); + } + + const labels = new Set( + (issue.labels || []).map((label) => + typeof label === 'string' ? label : label.name + ) + ); + + if (errors.length === 0) { + if ( + issue.state === 'closed' && + labels.has('invalid-format') + ) { + labels.delete('invalid-format'); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'open', + labels: [...labels], + }); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: ' Format looks good now. Reopening this issue.', + }); + } + core.info('Issue format OK'); + return; + } + + const issueNumber = issue.number; + const marker = ''; + const bodyText = [ + marker, + '## Issue format check failed', + '', + 'This issue was closed automatically because it does not follow the required format.', + '', + ...errors.map((e) => `- ${e}`), + '', + '### How to resubmit', + '', + '1. Go to [New Issue](https://github.com/binaricat/Netcatty/issues/new/choose)', + '2. Pick **Bug Report** or **Feature Request**', + '3. Fill in every required field', + '4. Keep the `[Bug]` or `[Feature]` prefix in the title and add a clear summary after it (older app versions may use `Bug: ...`)', + '', + 'For questions and open-ended discussion, use [GitHub Discussions](https://github.com/binaricat/Netcatty/discussions) instead.', + '', + 'If you believe this was a mistake, reply here after fixing the title/body and a maintainer can reopen.', + ].join('\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + per_page: 100, + }); + const alreadyNotified = comments.some((c) => + (c.body || '').includes(marker) + ); + + if (!alreadyNotified) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: bodyText, + }); + } + + labels.add('invalid-format'); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + state: 'closed', + state_reason: 'not_planned', + labels: [...labels], + }); diff --git a/components/SettingsApplicationTab.tsx b/components/SettingsApplicationTab.tsx index ce578322..5f610349 100644 --- a/components/SettingsApplicationTab.tsx +++ b/components/SettingsApplicationTab.tsx @@ -16,28 +16,49 @@ type AppInfo = { }; const REPO_URL = "https://github.com/binaricat/Netcatty"; +const BUG_REPORT_TEMPLATE = "bug_report.yml"; -const buildIssueUrl = (appInfo: AppInfo) => { - const title = "Bug: "; - const bodyLines = [ - "## Describe the problem", - "", - "## Steps to reproduce", - "1.", - "", - "## Expected behavior", - "", - "## Actual behavior", - "", - "## Environment", - `- App: ${appInfo.name} ${appInfo.version}`, - `- Platform: ${appInfo.platform || "unknown"}`, - `- UA: ${typeof navigator !== "undefined" ? navigator.userAgent : "unknown"}`, - ]; +const mapIssuePlatform = (platform?: string) => { + switch (platform) { + case "darwin": + return "macOS"; + case "win32": + return "Windows"; + case "linux": + return "Linux"; + default: + return undefined; + } +}; + +/** Opens GitHub's Bug Report issue form with fields prefilled from the running app. */ +export const buildIssueUrl = (appInfo: AppInfo) => { const params = new URLSearchParams({ - title, - body: bodyLines.join("\n"), + template: BUG_REPORT_TEMPLATE, + title: "[Bug] ", }); + + if (appInfo.version) { + params.set("version", appInfo.version); + } + + const platform = mapIssuePlatform(appInfo.platform); + if (platform) { + params.set("platform", platform); + } + + const installSource = + appInfo.version === "0.0.0" + ? "Built from source (npm run dev / pack)" + : "GitHub Release (.dmg / .exe / .AppImage / .deb)"; + params.set("install_source", installSource); + + const ua = typeof navigator !== "undefined" ? navigator.userAgent : "unknown"; + params.set( + "logs", + `Reported from Netcatty Settings (${appInfo.name} ${appInfo.version || "unknown"}).\n\nUser-Agent: ${ua}`, + ); + return `${REPO_URL}/issues/new?${params.toString()}`; };