From 86c281bc38afa5522910c56d1505482def54ee36 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 17 Jul 2026 10:37:00 -0700 Subject: [PATCH] FN-8215: add GitHub issue comment posting Enable operators to post upstream GitHub issue comments from the Import Tasks preview. - Add authenticated GitHub CLI and REST fallback support for creating issue comments. - Expose a validated API route and inline issue-comment composer with optimistic preview updates. - Add localized UI copy, documentation, release metadata, and coverage for client, route, and modal behavior. Files changed: .changeset/fn-8215-github-issue-add-comment.md | 7 ++ docs/dashboard-guide.md | 9 ++- packages/dashboard/app/api/legacy.ts | 13 ++++ .../dashboard/app/components/GitHubImportModal.css | 34 +++++++++ .../dashboard/app/components/GitHubImportModal.tsx | 68 +++++++++++++++++- .../__tests__/GitHubImportModal.test.tsx | 62 +++++++++++++++++ packages/dashboard/src/__tests__/github.test.ts | 48 +++++++++++++ .../dashboard/src/__tests__/routes-github.test.ts | 80 ++++++++++++++++++++++ packages/dashboard/src/github.ts | 49 +++++++++++++ .../dashboard/src/routes/register-git-github.ts | 56 +++++++++++++++ packages/i18n/locales/en/app.json | 5 ++ packages/i18n/locales/es/app.json | 5 ++ packages/i18n/locales/fr/app.json | 5 ++ packages/i18n/locales/ko/app.json | 5 ++ packages/i18n/locales/zh-CN/app.json | 5 ++ packages/i18n/locales/zh-TW/app.json | 5 ++ packages/i18n/src/resources.d.ts | 5 ++ 17 files changed, 459 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-8215 Fusion-Task-Lineage: 0fe6acca-e86d-484f-a97f-5746d32717a1 Co-authored-by: Fusion (runfusion.ai) --- .../fn-8215-github-issue-add-comment.md | 7 ++ docs/dashboard-guide.md | 9 ++- packages/dashboard/app/api/legacy.ts | 13 +++ .../app/components/GitHubImportModal.css | 34 ++++++++ .../app/components/GitHubImportModal.tsx | 68 +++++++++++++++- .../__tests__/GitHubImportModal.test.tsx | 62 ++++++++++++++ .../dashboard/src/__tests__/github.test.ts | 48 +++++++++++ .../src/__tests__/routes-github.test.ts | 80 +++++++++++++++++++ packages/dashboard/src/github.ts | 49 ++++++++++++ .../src/routes/register-git-github.ts | 56 +++++++++++++ packages/i18n/locales/en/app.json | 5 ++ packages/i18n/locales/es/app.json | 5 ++ packages/i18n/locales/fr/app.json | 5 ++ packages/i18n/locales/ko/app.json | 5 ++ packages/i18n/locales/zh-CN/app.json | 5 ++ packages/i18n/locales/zh-TW/app.json | 5 ++ packages/i18n/src/resources.d.ts | 5 ++ 17 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 .changeset/fn-8215-github-issue-add-comment.md diff --git a/.changeset/fn-8215-github-issue-add-comment.md b/.changeset/fn-8215-github-issue-add-comment.md new file mode 100644 index 0000000000..bb113e04b2 --- /dev/null +++ b/.changeset/fn-8215-github-issue-add-comment.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Let operators post GitHub issue comments directly from Import Tasks. +category: feature +dev: Adds gh-first and token REST fallback comment posting with optimistic preview updates. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index a597ecdab6..de3f93e279 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -319,7 +319,14 @@ Use Import Tasks on desktop/tablet: Expected outcome: the list pane shows matching open issues or pull requests and marks entries that already exist on the board. Use **Hide imported** beside the imported count to remove those unavailable rows from the current Issues, Pull Requests, or GitLab list; turning it off restores the greyed **Imported** rows. After a successful GitHub or GitLab import, the source row is marked **Imported** and made unavailable immediately, without waiting for the board list to refresh. 4. Select an issue or pull request row. Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only. A pull request preview also shows its checks; use **Refresh checks** to fetch current GitHub check status and comments without reopening the detail. Each failed check has a **Create fix task** action that creates a new task prefilled with the repository, PR, branches, check status, and check-details link. -5. Select the import action in the detail window. Pull requests use **Resolve feedback**, which creates a task to resolve reviewer feedback and address failed CI checks; issues keep **Import**. Each GitHub issue and pull-request comment also has **Import as task**, which creates a separate resolve-feedback task quoting that comment and linking its source without closing the detail window. + +5. In an issue detail, use **Add comment** to write a new upstream GitHub comment. The composer remains available for open and closed issues; posting adds the comment to the preview thread immediately. This is separate from **Import as task**, which creates a Fusion resolve-feedback task from an existing GitHub comment. + Expected outcome: Fusion posts the new comment to GitHub, keeps its inline composer available for another response, and shows a success or retryable error message without leaving Import Tasks. +6. Select the import action in the detail window. Pull requests use **Resolve feedback**, which creates a task to resolve reviewer feedback and address failed CI checks; issues keep **Import**. Each GitHub issue and pull-request comment also has **Import as task**, which creates a separate resolve-feedback task quoting that comment and linking its source without closing the detail window. Expected outcome: Fusion creates the requested task, preserves GitHub provenance/tracking metadata, and returns the completed PR/issue import to the list while leaving comment imports available for further feedback. Leaving and returning to **Import Tasks** (for example switching to Board and back) restores the prior context for the current project — provider (GitHub/GitLab), active Issues/PRs tab, label filter, selected repository/remote, GitLab project/group inputs, the **Hide imported** preference, and the previously selected issue/PR — instead of resetting to defaults. When GitLab integration is disabled in Settings, the GitLab provider tab is hidden and any restored GitLab provider preference opens on GitHub instead; saved GitLab URLs and tokens remain configured. The restored selection re-validates against the freshly reloaded list; a selection that no longer exists (e.g. the issue was closed upstream) clears gracefully rather than showing a stuck or empty preview. First-time opens with no prior state keep the existing default-remote auto-detect behavior. State is scoped per project and does not leak across projects. diff --git a/packages/dashboard/app/api/legacy.ts b/packages/dashboard/app/api/legacy.ts index c3f91824f8..b6d9bed007 100644 --- a/packages/dashboard/app/api/legacy.ts +++ b/packages/dashboard/app/api/legacy.ts @@ -1751,6 +1751,19 @@ export async function apiCloseGitHubIssue(repo: string, number: number): Promise }); } + +/* +FNXC:GitHubImport 2026-07-17-12:00: +Posts a new comment to the upstream GitHub issue. This is deliberately separate from +apiImportGitHubComment, which creates a Fusion resolve-feedback task from an existing comment. +*/ +export async function apiAddGitHubIssueComment(repo: string, number: number, body: string): Promise { + await api<{ ok: boolean }>("/github/issues/comment", { + method: "POST", + body: JSON.stringify({ repo, number, body }), + }); +} + /** Import a specific GitHub pull request as a fn review task */ export function apiImportGitHubPull(owner: string, repo: string, prNumber: number, projectId?: string): Promise { return api(withProjectId("/github/pulls/import", projectId), { diff --git a/packages/dashboard/app/components/GitHubImportModal.css b/packages/dashboard/app/components/GitHubImportModal.css index 53f3aa8730..69cc4809b9 100644 --- a/packages/dashboard/app/components/GitHubImportModal.css +++ b/packages/dashboard/app/components/GitHubImportModal.css @@ -352,6 +352,29 @@ when the preview is short; the top border separates it from scrolling content. min-height: 36px; } + +/* +FNXC:GitHubImport 2026-07-17-12:00: +The upstream-comment composer shares the detail action bar but takes a full row, keeping its +textarea and submit control reachable before Import on desktop and the mobile detail sheet. +*/ +.github-import-issue-comment-composer { + display: flex; + flex: 1 0 100%; + gap: var(--space-sm); +} + +.github-import-issue-comment-composer__input { + flex: 1 1 auto; + min-width: 0; + min-height: 36px; + resize: vertical; +} + +.github-import-issue-comment-composer__submit { + align-self: flex-end; +} + /* FNXC:GitHubImport 2026-07-15-18:20 (superseded 2026-07-15-23:25): Supersedes #551a2a3c1's note here. That change dropped `justify-content: space-between` and gave the @@ -1740,6 +1763,17 @@ Import Tasks embedded header now adopts the canonical ViewHeader chrome — edge padding: 0 var(--space-md); font-size: 13px; } + + + .github-import-issue-comment-composer { + flex-direction: column; + } + + .github-import-issue-comment-composer__input, + .github-import-issue-comment-composer__submit { + width: 100%; + min-height: 40px; + } } /* diff --git a/packages/dashboard/app/components/GitHubImportModal.tsx b/packages/dashboard/app/components/GitHubImportModal.tsx index b51a0a0596..baa9e5d2fd 100644 --- a/packages/dashboard/app/components/GitHubImportModal.tsx +++ b/packages/dashboard/app/components/GitHubImportModal.tsx @@ -9,6 +9,7 @@ import { apiFetchGitHubPullDetail, apiFetchGitHubIssueDetail, apiCloseGitHubIssue, + apiAddGitHubIssueComment, apiImportGitHubPull, apiImportGitHubComment, apiFetchGitLabProjectIssues, @@ -529,6 +530,8 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId, const [closingIssue, setClosingIssue] = useState(false); const [closeToast, setCloseToast] = useState<{ type: "success" | "error"; message: string } | null>(null); const closeToastTimerRef = useRef | null>(null); + const [commentBody, setCommentBody] = useState(""); + const [addingComment, setAddingComment] = useState(false); // FNXC:GitHubImport 2026-07-16-17:00: Track check-task submission by name + row index so duplicate GitHub check names never disable each other's controls. const [creatingCheckFixTaskRows, setCreatingCheckFixTaskRows] = useState>(new Set()); const [checkFixTaskToast, setCheckFixTaskToast] = useState<{ type: "success" | "error"; message: string } | null>(null); @@ -1268,6 +1271,45 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId, } }, [selectedIssueNumber, owner, repo, t, confirm]); + /* + FNXC:GitHubImport 2026-07-17-12:00: + This composer posts a NEW upstream GitHub issue comment, rather than importing an existing + comment as a Fusion task. On success it updates both issue-detail surfaces so the shared + CommentsThread remains immediate and cache-first reselection does not lose the posted comment. + */ + const handleAddIssueComment = useCallback(async () => { + const body = commentBody.trim(); + if (selectedIssueNumber === null || !owner.trim() || !repo.trim() || !body) return; + + const issueNumber = selectedIssueNumber; + const repository = `${owner.trim()}/${repo.trim()}`; + setAddingComment(true); + if (closeToastTimerRef.current) clearTimeout(closeToastTimerRef.current); + setCloseToast(null); + try { + await apiAddGitHubIssueComment(repository, issueNumber, body); + const postedComment: GitHubCommentDetail = { + author: t("git.commentAuthorYou", "You"), + body, + createdAt: new Date().toISOString(), + authorIsBot: false, + }; + const cachedDetail = issueDetailCacheRef.current.get(issueNumber) ?? { comments: [] }; + issueDetailCacheRef.current.set(issueNumber, { + ...cachedDetail, + comments: [...cachedDetail.comments, postedComment], + }); + setIssueDetail((current) => current ? { ...current, comments: [...current.comments, postedComment] } : { comments: [postedComment] }); + setCommentBody(""); + setCloseToast({ type: "success", message: t("git.commentPosted", "Comment posted") }); + } catch (err: unknown) { + setCloseToast({ type: "error", message: getErrorMessage(err) }); + } finally { + setAddingComment(false); + closeToastTimerRef.current = setTimeout(() => setCloseToast(null), 4000); + } + }, [commentBody, selectedIssueNumber, owner, repo, t]); + const selectedIssue = issues.find((i) => i.number === selectedIssueNumber); const selectedPull = pulls.find((p) => p.number === selectedPullNumber); @@ -1891,7 +1933,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId, role="status" data-testid="github-import-issue-close-toast" > - {closeToast.message} + {closeToast.message} )} @@ -2125,6 +2167,30 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId, The detail action remains Import for issues, while pull requests are explicitly Resolve feedback so their imported task covers reviewer feedback and failed checks. Close issue remains to its left and is unaffected. */}
+ {activeTab === "issues" && selectedIssue && ( +
{ event.preventDefault(); void handleAddIssueComment(); }} + > +