Skip to content

Link Issues to threads and start a thread from an Issue #28

Description

@lukemaj

Parent

#25

Outcome

Threads and Issues are linked, the user sees a thread's Issues next to its PRs, and can start a linked thread from an Issue.

Acceptance criteria

  • Links come from: manual link and unlink in the thread's right panel, a link_issue / unlink_issue MCP tool mirroring link_pull_request, task branch name <type>/<number>-<slug> in the project's repository, and closing references (Closes #N) of the thread's linked PRs.
  • Only non-derivable links are stored, in fork-owned table fork_thread_issue_links created by the fork in the existing database, outside upstream migrations and the event log. Derived links are computed when read. Stored links survive a server restart.
  • The thread's right panel shows linked Issues the way it shows PRs, each removable; clients update when links change.
  • An API returns the threads linked to an Issue (used by Issue rows and the status in C).
  • "Start thread" on an Issue creates a thread in the project matching the Issue's repository, with the project's usual worktree mode, linked immediately, composer prefilled with title, URL and body, not sent. Disabled with a reason when no project matches.
  • Upstream tables, migrations and events unchanged; every upstream-file edit is in scripts/fork-upstream-edits.txt and docs/fork-features.md.

Non-goals

Blocked by

None

Required proof

  • Server tests for manual, MCP, branch-derived and closing-reference links, unlink, and restart persistence; real gh where GitHub is read (no fake CLI).
  • Targeted lint and typecheck; scripts/fork-check.sh; independent review of the exact head.

Activity

  1. lukemaj commented on Sep 25, 2026

    @lukemaj
    ContributorAuthor

    Handoff: PR #34 (not merged)

    Commands run and results

    • apps/server: vp test run src/issueLinks src/mcp/McpHttpServer.test.ts src/auth/RpcAuthorization.test.ts src/server.test.ts: 233 passed, 2 skipped (the live suite, opt-in).
    • apps/server: T3_LIVE_GITHUB=1 vp test run src/issueLinks/IssueLinks.live.test.ts with real gh against real GitHub (PR feat(server): add project-scoped thread supervision #16 closes Threads toolkit: read and message any thread in the project (scope: children | project) #15): 2 passed.
    • packages/contracts: vp test run src/issueLinks.test.ts: 5 passed. apps/web: vp test run src/components/issues: 9 passed.
    • tsc --noEmit in apps/server, apps/web, packages/contracts, packages/client-runtime: 0 errors.
    • vp lint on changed files: no new findings (ChatView's 74 warnings exist before and after). vp fmt applied.
    • knip --include files,exports on server, web, contracts, client-runtime: clean.
    • scripts/fork-check.sh: OK. Stack is 14 commits, 10 features.

    Deviations and notes

    • Closing references are not stored in projection_thread_pull_requests: snapshots carry no PR body. They are read from GitHub (gh api graphql, closingIssuesReferences / closedByPullRequestsReferences) at read time and cached. A failed read drops only those derived links.
    • Server push covers stored-link changes only. For derived changes, clients reread when the thread shell's branch or PR links change. A server-side domain-event forwarder would have broken upstream tests whose mocked engines lack subscribeDomainEvents.
    • Unlink writes a dismissed tombstone, so a removed branch or closing-reference link stays removed until it is linked again.
    • The links tab (the PR panel) is now also available for server threads when the environment answers Issue-link reads. This makes manual linking reachable when a thread has no PRs.
    • Start thread: Computed Issue status, status grouping and filters #29 uses useStartThreadFromIssue(), which prefills title, URL and body. The trigger here is "Start another thread" on each linked-Issue row, which knows only the URL.
    • Issue rows show number and repository but no title (no GitHub read for titles here).
    • Merge overlap with feat(web): browse GitHub Issues next to PRs (#27) #32 (Browse Issues: list, filters, parent tree and side panel #27): both add insertions at the same spots in rpc.ts, ws.ts, server.ts, RpcAuthorization.ts, contracts/index.ts, the allowlist and the feature map. Whichever lands second must rebase, and move shared paths to sharedFiles so each keeps exactly one primary owner.

    Unverified

    • No real-client or browser pass, and no screenshots (no dev server or browser started, per the brief).
    • No independent review of the head yet.
    • No user docs added.
  2. lukemaj commented on Sep 25, 2026

    @lukemaj
    ContributorAuthor

    Handoff update: review fixes on PR #34 (not merged)

    Review items

    1. The links tab opens for threads without PRs: addPullRequestsSurface and both tab props gate on linksSurfaceAvailable. The label is now "Linked PRs and Issues".
    2. threadsForIssue is replaced by batched threadsForIssues({ issues: [{host, repository, number, closingPullRequests}] }), capped at 100 and returned in input order. It makes no GitHub call. Change pushes carry {threadId, issues}, and client refresh signals fire only for the matching thread or Issue keys.
    3. A capabilities.issueLinks server flag decides tab availability. Only the links panel calls useThreadIssueLinks.
    4. The branch rule requires a conventional-commit type and a slug starting with a letter.
    5. link returns alreadyLinked=true for any existing source (branch and closing reference included) and writes nothing.
    6. Lazy prune on read, at most every 10 minutes: links of deleted threads, and drafts unsent after 7 days. A reused draft drops its previous started row.
    7. An explicit unlink always writes the tombstone.
    8. The stubbed closing-references service is removed. CI tests use stored and branch links only; closing references are covered only by the live real-gh suite.

    Commands and results

    • apps/server: vp test run src/issueLinks src/mcp/McpHttpServer.test.ts src/auth/RpcAuthorization.test.ts src/server.test.ts src/environment: 277 passed, 2 skipped.
    • T3_LIVE_GITHUB=1 vp test run src/issueLinks/IssueLinks.live.test.ts: 2 passed (real gh, real GitHub).
    • Contracts issueLinks.test.ts: 5 passed. Web components/issues and RightPanelTabs: 29 passed.
    • tsc --noEmit in apps/server, apps/web, packages/contracts, packages/client-runtime: 0 errors.
    • vp lint on changed files: no errors and no new warnings. vp fmt --check on changed files: clean. vp run knip:check: passes. scripts/fork-check.sh: OK (stack 15/20).

    New upstream edits (allowlisted, owner issues-links)

    packages/contracts/src/environment.ts and apps/server/src/environment/ServerEnvironment.ts (capability), and apps/web/src/components/RightPanelTabs.tsx (label).

    Pending and unverified

    • Rebase onto origin/feat/25-issues after feat(web): browse GitHub Issues next to PRs (#27) #32 merges, resolving shared insert points and moving shared paths to sharedFiles. Waiting for the go-ahead.
    • No browser or real-client pass. No re-review of this head yet.
    • Derived-link changes on other threads (a branch rename) reach Issue rows only through the 30 s staleness, not a push.
  3. lukemaj commented on Sep 25, 2026

    @lukemaj
    ContributorAuthor

    Handoff update: rebase onto #27 and re-review fixes (PR #34, not merged)

    • PR: feat(server): link GitHub Issues to threads (#28) #34 into feat/25-issues, mergeable
    • Head: 43988f2615a37695be2816dcbca3d323b3a420b7 (supersedes 7fd4e6d). Two commits on 335312e4:
      • e7221b06d: the feature squashed to one commit before rebasing.
      • 43988f261: the re-review fixes.

    Rebase

    • Conflicts resolved in rpc.ts, ws.ts, server.ts, RpcAuthorization.ts, contracts/index.ts, the allowlist and the feature map. Both features are intact (...IssueRpcs plus .merge(IssueLinksRpcGroup), and both handler spreads, scopes and layers).
    • Ownership: issues-browse stays primary for RpcAuthorization.ts, server.ts, ws.ts, CommandPalette.tsx and rpc.ts. issues-links lists those, plus McpHttpServer.ts and contracts/index.ts, in sharedFiles. issues-links is primary owner of ServerEnvironment.ts, ChatView.tsx, RightPanelTabs.tsx, client-runtime/rpc/client.ts and contracts/environment.ts.

    Re-review fixes

    1. Pruning removes a deleted thread's links only when deleted_at is more than 7 days old. Tests cover delete then re-create, a recent deletion, and an existing thread's link older than 7 days.
    2. The client subscription applies Stream.chunks, so each delivered batch is one value, and a batch matches when any of its changes names the target (issueLinkChangesMatch, tested with two changes delivered together).
    3. ThreadLinksPanel renders the Issues section only when capabilities.issueLinks is true.
    4. A CI test drives threadsForIssues closing references from real DB rows and caller data. It covers stack-dismissed exclusion, a tombstone suppressing a closing reference, mixed-case repositories, and a cross-repo closing PR.
    5. The command palette entry reads "Show linked PRs and Issues" and is enabled when the thread has PRs or the server advertises issueLinks. The file is allowlisted by Browse Issues: list, filters, parent tree and side panel #27 and shared in the map.
    6. // Fork: … (#28) marker added on the RightPanelTabs label.

    Commands and results

    • apps/server: vp test run src/issueLinks src/issues src/mcp/McpHttpServer.test.ts src/auth/RpcAuthorization.test.ts src/server.test.ts src/environment: 291 passed, 8 skipped (live suites).
    • T3_LIVE_GITHUB=1 vp test run src/issueLinks/IssueLinks.live.test.ts: 2 passed.
    • Web components/issues, RightPanelTabs, CommandPalette, state/pullRequests.test.ts: 79 passed. Contracts issueLinks.test.ts: 5 passed.
    • tsc --noEmit in server, web, contracts, client-runtime: 0 errors.
    • vp lint on changed files: no errors and no new warnings. vp fmt --check on changed files: clean. vp run knip:check: passes. scripts/fork-check.sh: OK (stack 16/20, 12 features).

    Process note

    An intermediate push (76d1c15) went up before fork-check passed: the rebase had cut the end of the issues-browse JSON block. It was fixed and replaced with a lease-guarded push; the current head passes fork-check.

    Unverified

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions