Skip to content

feat(web): GitHub Issues next to PRs, linked to threads, with computed status (#25) - #37

Merged
lukemaj merged 5 commits into
mainfrom
feat/25-issues
Sep 26, 2026
Merged

lukemaj merged 5 commits into
mainfrom
feat/25-issues

Conversation

@lukemaj

@lukemaj lukemaj commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

All work was tracked in GitHub Issues and done by agents, but Chromeria only showed PRs. Seeing what was being worked on, reviewed, blocked or waiting meant leaving the app, and nothing showed which thread owned which Issue.

This adds GitHub Issues next to PRs (spec: #25):

Each component PR had an independent review of its exact head, review fixes, and green fork CI before merging into feat/25-issues. Live test suites run against real gh and real GitHub (no fake CLI); they are opt-in and not part of CI.

Not done: no pass in a real client and no screenshots; the user chose to skip them. Integration gaps found by the final review were fixed in #38. The fork CI upstream-depth failure was fixed in #39, and this branch was rebased onto that main (same five changes, verified by range-diff).

Closes #25
Closes #27
Closes #28
Closes #29
Closes #31

Model: Claude Opus 5.5 (medium) via T3 Code

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 25, 2026
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 13.5 KiB 13.5 KiB +19 B (+0.1%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +3 B (+0.0%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB +16 B (+0.2%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.3 KiB 56.3 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 10 10 0 (0.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB +9 B (+0.1%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB 0 B (0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.4 KiB 6.4 KiB +9 B (+0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.0 KiB 57.0 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 9 9 0 (0.0%) 21 ✅

Baseline: 4faaa44 · PR result: fab9092 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 113.9 KiB
  • Claude decoded thread snapshot: 114.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

lukemaj and others added 5 commits September 26, 2026 14:13
…) (#33)

* feat(web): show parent threads as working while child threads work (#31)

Child threads are hidden from the sidebar, so their work never reached the
parent row. A shared pure helper derives descendant child-thread activity
from thread shells; the sidebar row feeds it through the existing status
resolvers (Working pill and timer with "· N agents", child approvals as
the parent's approval), and the auto-settle sweep keeps such parents
active like threads with live background work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): record PR for sidebar-child-working (#31)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface child input, time from the earliest child (#31)

Review fixes: a child waiting on user input now shows the parent's
awaiting-input state, and children waiting on input or with any background
work (monitoring included) keep the parent from auto-settling. An idle
parent's Working timer starts at its earliest working child, and the
"· N agents" count sits outside the aria-hidden timer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(web): browse GitHub Issues next to PRs (#27)

The Pull Requests page gains a PRs/Issues toggle (`view=issues`) and the
sidebar an Issues icon. Issues from every GitHub repository of the user's
projects are read live through `gh` (one GraphQL search per host page,
within the existing GraphQL budget and rate-limit handling), merged across
connected environments, and shown as Open, Done or Not planned. Search,
sort, repository/label/milestone/parent filters, a parent tree that marks
sub-Issues outside the user's projects, and a side panel with body,
comments, comment, close and reopen. The command palette searches the
listed Issues while the page is open.

No storage: upstream tables, migrations and events are unchanged. Upstream
edits are allowlisted and mapped under the `issues-browse` feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): map the Issues browse PR (#27)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface Issues list failures and address review (#27)

- Show a failed server or "Load more" page with Retry above the rows that
  stay, and stop offering "Load more" while it failed.
- Toasts for comment and close/reopen carry GitHub's reason.
- Locked Issues keep the comment box (GitHub has no viewerCanComment); a
  note explains and GitHub's refusal is shown.
- Count non-GitHub repositories once by host and repository across
  worktrees, pages and servers.
- Palette Issues group follows Actions and appears only once typed.
- Remove unused scoreIssueMatch and knip-flagged exports; table tests for
  issueStateOf and issueSearchQuery; typecheck fix in the live suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(server): link GitHub Issues to threads (#28)

Store only non-derivable Issue links in the fork-owned
fork_thread_issue_links table (created by the fork, outside upstream
migrations and the event log). Derive branch links from conventional
<type>/<number>-<slug> branches and closing-reference links from the
thread's linked pull requests (real gh) or, for Issues, from the closing
pull requests the caller already read.

Expose issueLinks.* RPCs (for thread, batched threads for Issues, link,
unlink, change push) behind an issueLinks capability,
link_issue/unlink_issue/list_thread_issues MCP tools, an Issues section
in the "Linked PRs and Issues" panel, and a start-thread-from-Issue
action that opens a draft in the matching project, prefills it and links
it at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep retried threads' Issue links and refresh on batched changes

- Prune deleted threads' links only once deleted over a week ago: a failed
  first-send bootstrap deletes a thread and the retry re-creates its id.
- Deliver change pushes to the client one batch at a time and match a
  batch if any change names the thread or Issue.
- Render the Issues section only where the server advertises issueLinks.
- Enable and rename the command palette entry for the linked surface.
- Cover caller-supplied closing references in CI with real rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(web): computed Issue status with grouping and filters (#29)

Each Issue gets one status from the #25 table, first match wins: GitHub
state and close reason, open native blockers, closing pull requests
(open, draft) with the trusted `review/independent` mark on their head,
task-branch links, and whether a linked thread or any descendant child
thread works now.

The Issues page read gains blockers, closing pull requests with head SHA
and the review mark in the same per-page GraphQL query (cost 2 -> 3 per
100 rows); marks count only when the query's viewer posted them. The
view groups by status with Done and Not planned collapsed, adds Status
and Linked filters, shows linked threads on rows, wires Start thread on
rows and the side panel, and opens the side panel from a thread's linked
Issue through the `issue` search parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): count thread-linked PRs, trust marks across servers, drafts pause (#29)

Review fixes for PR #36:

- A PR into a non-default branch makes no closing reference, so the
  Issue's PRs are now its closing PRs plus the open PRs linked to its
  linked threads, deduped by repository and number. Each host's first
  search page reads those thread-linked PRs as `resource(url:)` aliases in
  the same GraphQL request (cost stays 3 per 100 rows with 50 aliases; a
  missing PR answers null instead of failing the page), and
  `threadsForIssues` returns each thread's PR keys.
- Threads are looked up on every server that keeps Issue links, and a
  review mark is trusted when any server that listed the Issue runs as
  its poster; the server now returns the mark's creator and its viewer.
- An open draft PR with nobody working reads as Paused; merged PRs keep
  not counting while the Issue is open.
- The status table moves to contracts so the live suite checks it too.
- The Issues view subscribes to a working-thread key atom instead of all
  thread shells, and a row's Start thread waits and ignores repeat clicks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): read only PRs of Issue-linked threads, also without a search (#29)

Re-review fixes for PR #36:

- Thread-linked pull request candidates now come from threads that link
  to an Issue (a stored, non-dismissed link or a task branch), through a
  new `IssueLinks.pullRequestsOfIssueThreads`. Last-synced snapshot state
  no longer filters them: the same GraphQL read returns GitHub's state.
  The 50 cap stays as a bound and logs when it truncates.
- A server whose repositories a filter excludes still reads its
  candidates and viewer in a small aliases-only request.
- While one row starts a thread, every row's Start button is disabled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(web): close Issues integration gaps (#25)

- Servers advertise an `issues` capability; the Issues list, the sidebar
  Issues icon, the palette's "Open issues" action and the PRs/Issues
  switch use only servers that have it, so upstream and older servers no
  longer answer `issues.list` with an unknown-tag error.
- The side panel shows the Issue's linked threads (open one) and its
  closing and thread-linked pull requests (open on GitHub), read for the
  one Issue, so it works when opened from a thread's link too. The Issue
  detail read now carries closing pull requests.
- The command palette finds threads by linked Issue: `#N`,
  `owner/repo#N` or an Issue URL asks `threadsForIssues` on servers with
  Issue links, debounced and bounded to 20 repositories per server.
- Start thread prefers a project on a server that keeps Issue links.
- User docs cover linking by hand, unlinking and the MCP tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): find closing-reference threads from the palette (#25)

Review fixes for PR #38:

- The palette's Issue lookup sends the Issue's closing pull requests:
  from the loaded Issues list entry, else, for `owner/repo#N` or a URL,
  from one debounced Issue read on a server that lists Issues. A bare
  `#N` the list does not hold finds stored and branch links only; the
  user docs say so.
- An `issue=` link without a usable `selectedEnvironmentId` resolves to
  a server that lists Issues, one keeping Issue links first, and never
  to a server without `issues.*`.
- The detail read's mapping moves to `issueDetailOf`, with a CI test
  decoding a fixture with one closing pull request and one null node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lukemaj
lukemaj merged commit 4b1bdec into main Sep 26, 2026
18 checks passed
@lukemaj
lukemaj deleted the feat/25-issues branch September 26, 2026 12:21
lukemaj added a commit that referenced this pull request Sep 27, 2026
…d status (#25) (#37)

* feat(web): show parent threads as working while child threads work (#31) (#33)

* feat(web): show parent threads as working while child threads work (#31)

Child threads are hidden from the sidebar, so their work never reached the
parent row. A shared pure helper derives descendant child-thread activity
from thread shells; the sidebar row feeds it through the existing status
resolvers (Working pill and timer with "· N agents", child approvals as
the parent's approval), and the auto-settle sweep keeps such parents
active like threads with live background work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): record PR for sidebar-child-working (#31)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface child input, time from the earliest child (#31)

Review fixes: a child waiting on user input now shows the parent's
awaiting-input state, and children waiting on input or with any background
work (monitoring included) keep the parent from auto-settling. An idle
parent's Working timer starts at its earliest working child, and the
"· N agents" count sits outside the aria-hidden timer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): browse GitHub Issues next to PRs (#27) (#32)

* feat(web): browse GitHub Issues next to PRs (#27)

The Pull Requests page gains a PRs/Issues toggle (`view=issues`) and the
sidebar an Issues icon. Issues from every GitHub repository of the user's
projects are read live through `gh` (one GraphQL search per host page,
within the existing GraphQL budget and rate-limit handling), merged across
connected environments, and shown as Open, Done or Not planned. Search,
sort, repository/label/milestone/parent filters, a parent tree that marks
sub-Issues outside the user's projects, and a side panel with body,
comments, comment, close and reopen. The command palette searches the
listed Issues while the page is open.

No storage: upstream tables, migrations and events are unchanged. Upstream
edits are allowlisted and mapped under the `issues-browse` feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(fork): map the Issues browse PR (#27)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): surface Issues list failures and address review (#27)

- Show a failed server or "Load more" page with Retry above the rows that
  stay, and stop offering "Load more" while it failed.
- Toasts for comment and close/reopen carry GitHub's reason.
- Locked Issues keep the comment box (GitHub has no viewerCanComment); a
  note explains and GitHub's refusal is shown.
- Count non-GitHub repositories once by host and repository across
  worktrees, pages and servers.
- Palette Issues group follows Actions and appears only once typed.
- Remove unused scoreIssueMatch and knip-flagged exports; table tests for
  issueStateOf and issueSearchQuery; typecheck fix in the live suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(server): link GitHub Issues to threads (#28) (#34)

* feat(server): link GitHub Issues to threads (#28)

Store only non-derivable Issue links in the fork-owned
fork_thread_issue_links table (created by the fork, outside upstream
migrations and the event log). Derive branch links from conventional
<type>/<number>-<slug> branches and closing-reference links from the
thread's linked pull requests (real gh) or, for Issues, from the closing
pull requests the caller already read.

Expose issueLinks.* RPCs (for thread, batched threads for Issues, link,
unlink, change push) behind an issueLinks capability,
link_issue/unlink_issue/list_thread_issues MCP tools, an Issues section
in the "Linked PRs and Issues" panel, and a start-thread-from-Issue
action that opens a draft in the matching project, prefills it and links
it at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): keep retried threads' Issue links and refresh on batched changes

- Prune deleted threads' links only once deleted over a week ago: a failed
  first-send bootstrap deletes a thread and the retry re-creates its id.
- Deliver change pushes to the client one batch at a time and match a
  batch if any change names the thread or Issue.
- Render the Issues section only where the server advertises issueLinks.
- Enable and rename the command palette entry for the linked surface.
- Cover caller-supplied closing references in CI with real rows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(web): computed Issue status with grouping and filters (#29) (#36)

* feat(web): computed Issue status with grouping and filters (#29)

Each Issue gets one status from the #25 table, first match wins: GitHub
state and close reason, open native blockers, closing pull requests
(open, draft) with the trusted `review/independent` mark on their head,
task-branch links, and whether a linked thread or any descendant child
thread works now.

The Issues page read gains blockers, closing pull requests with head SHA
and the review mark in the same per-page GraphQL query (cost 2 -> 3 per
100 rows); marks count only when the query's viewer posted them. The
view groups by status with Done and Not planned collapsed, adds Status
and Linked filters, shows linked threads on rows, wires Start thread on
rows and the side panel, and opens the side panel from a thread's linked
Issue through the `issue` search parameter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): count thread-linked PRs, trust marks across servers, drafts pause (#29)

Review fixes for PR #36:

- A PR into a non-default branch makes no closing reference, so the
  Issue's PRs are now its closing PRs plus the open PRs linked to its
  linked threads, deduped by repository and number. Each host's first
  search page reads those thread-linked PRs as `resource(url:)` aliases in
  the same GraphQL request (cost stays 3 per 100 rows with 50 aliases; a
  missing PR answers null instead of failing the page), and
  `threadsForIssues` returns each thread's PR keys.
- Threads are looked up on every server that keeps Issue links, and a
  review mark is trusted when any server that listed the Issue runs as
  its poster; the server now returns the mark's creator and its viewer.
- An open draft PR with nobody working reads as Paused; merged PRs keep
  not counting while the Issue is open.
- The status table moves to contracts so the live suite checks it too.
- The Issues view subscribes to a working-thread key atom instead of all
  thread shells, and a row's Start thread waits and ignores repeat clicks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(server): read only PRs of Issue-linked threads, also without a search (#29)

Re-review fixes for PR #36:

- Thread-linked pull request candidates now come from threads that link
  to an Issue (a stored, non-dismissed link or a task branch), through a
  new `IssueLinks.pullRequestsOfIssueThreads`. Last-synced snapshot state
  no longer filters them: the same GraphQL read returns GitHub's state.
  The 50 cap stays as a bound and logs when it truncates.
- A server whose repositories a filter excludes still reads its
  candidates and viewer in a small aliases-only request.
- While one row starts a thread, every row's Start button is disabled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): close Issues integration gaps (#25) (#38)

* fix(web): close Issues integration gaps (#25)

- Servers advertise an `issues` capability; the Issues list, the sidebar
  Issues icon, the palette's "Open issues" action and the PRs/Issues
  switch use only servers that have it, so upstream and older servers no
  longer answer `issues.list` with an unknown-tag error.
- The side panel shows the Issue's linked threads (open one) and its
  closing and thread-linked pull requests (open on GitHub), read for the
  one Issue, so it works when opened from a thread's link too. The Issue
  detail read now carries closing pull requests.
- The command palette finds threads by linked Issue: `#N`,
  `owner/repo#N` or an Issue URL asks `threadsForIssues` on servers with
  Issue links, debounced and bounded to 20 repositories per server.
- Start thread prefers a project on a server that keeps Issue links.
- User docs cover linking by hand, unlinking and the MCP tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(web): find closing-reference threads from the palette (#25)

Review fixes for PR #38:

- The palette's Issue lookup sends the Issue's closing pull requests:
  from the loaded Issues list entry, else, for `owner/repo#N` or a URL,
  from one debounced Issue read on a server that lists Issues. A bare
  `#N` the list does not hold finds stored and branch links only; the
  user docs say so.
- An `issue=` link without a usable `selectedEnvironmentId` resolves to
  a server that lists Issues, one keeping Issue links first, and never
  to a server without `issues.*`.
- The detail read's mapping moves to `issueDetailOf`, with a CI test
  decoding a fixture with one closing pull request and one null node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

1 participant