Skip to content

feat(api,web): server-resolved PR/issue titles for the connected-work rail#282

Merged
Zach Dunn (zachdunn) merged 11 commits into
mainfrom
claude/github-title-loading-e3b02e
Jul 20, 2026
Merged

feat(api,web): server-resolved PR/issue titles for the connected-work rail#282
Zach Dunn (zachdunn) merged 11 commits into
mainfrom
claude/github-title-loading-e3b02e

Conversation

@zachdunn

@zachdunn Zach Dunn (zachdunn) commented Jul 20, 2026

Copy link
Copy Markdown
Member

Implements phase 1 of the GitHub App integration (design + plan in .context/267-*, session-local).

  • api: GitHub App auth client — WebCrypto RS256 JWT → KV-cached installation tokens (ghinst:/ghtok: in a new GITHUB_CACHE namespace); ref→title resolution over a token ladder (home installation for public repos → the repo's own installation for private), with TTL'd positive/negative caching and rate-limit-aware backoff; member-gated batch endpoint GET /me/workspaces/:name/github-titles (≤20 refs, per-ref nulls never fail the batch).
  • web: the connected-work rail still paints instantly from metadata (gh.title/ref), then swaps in server-resolved titles via a generation-guarded repaint that preserves show-more expansion. Every failure path degrades to today's labels.
  • CLI untouched; attach-time gh.title stamping remains the offline seed. No changeset (api/web are changeset-ignored).

Verified in the local portless stack: with no App secrets the rail is byte-identical to today (endpoint returns nulls); with a cached title the rail swaps the label in place.

Operator steps before this is live in prod (all code degrades safely until then): register the GitHub App (issues:read, pull_requests:read, webhooks off), install on buildinternet, wrangler kv namespace create GITHUB_CACHE + fill the id in apps/api/wrangler.jsonc (currently TBD_BY_OPERATOR), set GITHUB_APP_PRIVATE_KEY (PKCS#8) / GITHUB_APP_ID / GITHUB_APP_HOME_INSTALLATION_ID.

Closes #267

Before / after (local stack, dev-demo files tab)

Before (no title available) After (server-resolved title)
Rail before: ref fallback label Rail after: real PR title

Summary by CodeRabbit

  • New Features

    • Added automatic GitHub issue and pull request title resolution for workspace connected-work items.
    • Added a workspace API endpoint to retrieve titles for up to 20 GitHub references.
    • Added caching and fallback handling to improve title loading reliability.
    • Updated connected-work displays with fetched titles without disrupting expanded views.
  • Bug Fixes

    • Prevented stale title requests from overwriting newer results.
    • Added graceful handling for unavailable, invalid, or inaccessible GitHub references.
  • Tests

    • Added comprehensive coverage for GitHub authentication, caching, title resolution, API validation, and UI relabeling.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6008e39-f6c3-4a5d-9c43-c81e189c3963

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds GitHub App authentication and KV caching for issue/PR titles, exposes a member-scoped batch API, and asynchronously repaints connected-work rail labels with resolved titles while preserving expansion state and ignoring stale responses.

Changes

GitHub title resolution

Layer / File(s) Summary
GitHub App authentication
apps/api/src/env.d.ts, apps/api/src/github-app.ts, apps/api/src/github-app.test.ts, apps/api/test/fake-kv.ts
Adds GitHub App configuration, JWT signing, installation lookup, token exchange, KV caching, and tests.
Cached GitHub title resolution
apps/api/src/github-titles.ts, apps/api/src/github-titles.test.ts, apps/api/wrangler.jsonc
Resolves issue and pull-request refs, classifies states, applies token fallback, and caches positive and negative results in GITHUB_CACHE.
Workspace titles API route
apps/api/src/routes/me.ts, apps/api/src/routes/github-titles-route.test.ts
Adds an authenticated endpoint that validates, normalizes, limits, de-duplicates, and resolves workspace refs.
Connected-work title repainting
apps/web/src/lib/api-client.ts, apps/web/src/lib/gh-context.ts, apps/web/src/lib/workspace-rail.ts, apps/web/src/lib/*test.ts
Fetches title maps, overlays non-empty titles, repaints changed labels, preserves expansion for repaint operations, and rejects stale results.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A bunny hops through refs in flight,
Fetching titles crisp and bright.
Cached in burrows, tokens spin,
Rail labels bloom with names within.
Old replies fade; fresh ones stay—
“Hop-hop!” says Bun, “repaint today!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed It clearly summarizes the main change: adding server-resolved PR/issue titles for the connected-work rail.
Linked Issues check ✅ Passed The PR delivers the requested connected-work title display via server-side GitHub lookup, caching, and rail relabeling.
Out of Scope Changes check ✅ Passed The changes stay focused on GitHub title resolution, API wiring, caching, and rail rendering with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/github-title-loading-e3b02e

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-api e5566ad Commit Preview URL

Branch Preview URL
Jul 20 2026, 02:25 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-web f483dea Commit Preview URL

Branch Preview URL
Jul 20 2026, 01:44 PM

@zachdunn Zach Dunn (zachdunn) added the coderabbit:review Trigger CodeRabbit review for the PR. label Jul 20, 2026
…re (#267)

- share one lazily-minted home-installation token across a resolveTitles
  batch (kills the cold-cache N-way JWT/token-mint stampede)
- reuse secrets.ts b64url codec in github-app.ts instead of a second copy
- export GITHUB_TITLES_MAX_REFS from api-client; rail imports it instead of
  redeclaring the cap
- hoist the shared GitHub App test env fixture

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
apps/api/src/routes/me.ts (1)

539-568: 🚀 Performance & Scalability | 🔵 Trivial

Consider throttling the batch title endpoint against the shared home-installation quota.

Up to 20 refs per request, each capable of issuing 2 outbound GitHub calls on a cold cache, all draw from the single shared home-installation token/rate-limit bucket used across every workspace. There's no per-user/workspace throttle here beyond membership — worth considering if this becomes a hot path, though it's consistent with other read routes in this file that also skip rate limiting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/routes/me.ts` around lines 539 - 568, Consider adding throttling
for the github-titles route around resolveTitles, using the existing shared
home-installation quota mechanism so requests are limited per user or workspace
before issuing outbound GitHub calls. Preserve the current membership
validation, batch-size validation, and per-reference null behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/github-app.ts`:
- Around line 78-132: The GitHub API calls in installationForRepo and
installationToken lack request timeouts, allowing resolveTitles to hang on slow
responses. Add the same AbortController/timeout mechanism used by fetchIssue to
both fetchImpl calls, pass the abort signal, and preserve the existing
null-return behavior when requests time out or fail.
- Line 1: Add a shared AbortController-based timeout helper and use it for the
fetchImpl calls in installationForRepo, installationToken, and fetchIssue,
applying the same fail-fast duration to each outbound GitHub request. Ensure the
timeout signal is passed to fetchImpl and abort resources are cleaned up after
completion, while preserving existing response and error handling.

In `@apps/api/src/github-titles.ts`:
- Around line 33-46: Add timeout-based cancellation to the fetch request in
fetchIssue, using an AbortController and passing its signal to fetchImpl. Ensure
the controller aborts after the configured timeout and is cleaned up when the
request completes, while preserving the existing error outcome behavior.
- Around line 86-97: Update the fallback logic in the surrounding fetch flow so
a failed home installationToken attempt still proceeds to installationForRepo
and retries fetchIssue with the repo-specific token. Track the home-token
failure distinctly from the initial error outcome, while preserving the existing
no-access fallback and negative-cache behavior for genuine terminal errors.

In `@apps/web/src/lib/api-client.ts`:
- Around line 550-552: Strengthen the response validation before returning from
the refs-parsing flow: require body.refs to be a non-array record, and validate
every entry as either null or an object with string title/state fields and a
valid kind value. Return null for any malformed entry, including arrays or
non-string titles, and only cast to GithubTitleMap after all entries pass
validation.

In `@apps/web/src/lib/workspace-rail.ts`:
- Around line 147-154: Remove the HTMLButtonElement generic from the
querySelector call in the rail expansion listener. Use the project’s Worker-safe
selector abstraction or structural typing for the selected control, while
preserving the existing click handler and once-only listener behavior.

---

Nitpick comments:
In `@apps/api/src/routes/me.ts`:
- Around line 539-568: Consider adding throttling for the github-titles route
around resolveTitles, using the existing shared home-installation quota
mechanism so requests are limited per user or workspace before issuing outbound
GitHub calls. Preserve the current membership validation, batch-size validation,
and per-reference null behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6edbcece-f81e-42f6-8e8e-d3181755d511

📥 Commits

Reviewing files that changed from the base of the PR and between d055cb9 and 396f7af.

📒 Files selected for processing (14)
  • apps/api/src/env.d.ts
  • apps/api/src/github-app.test.ts
  • apps/api/src/github-app.ts
  • apps/api/src/github-titles.test.ts
  • apps/api/src/github-titles.ts
  • apps/api/src/routes/github-titles-route.test.ts
  • apps/api/src/routes/me.ts
  • apps/api/test/fake-kv.ts
  • apps/api/wrangler.jsonc
  • apps/web/src/lib/api-client.ts
  • apps/web/src/lib/gh-context.test.ts
  • apps/web/src/lib/gh-context.ts
  • apps/web/src/lib/workspace-rail.test.ts
  • apps/web/src/lib/workspace-rail.ts

@@ -0,0 +1,132 @@
/**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No timeout on outbound GitHub API calls in the request path. installationForRepo, installationToken (github-app.ts), and fetchIssue (github-titles.ts) all call fetchImpl with no AbortController/timeout. All three run synchronously inside resolveTitles, which the new /workspaces/:name/github-titles route awaits before responding — a slow or hanging GitHub response ties up the request with no fail-fast guard, and there's no shared coalescing to bound worst-case latency for a 20-ref batch.

  • apps/api/src/github-app.ts#L78-132: wrap the fetchImpl calls in installationForRepo and installationToken with an AbortController-based timeout (e.g., 5-10s).
  • apps/api/src/github-titles.ts#L33-46: apply the same timeout to fetchIssue's fetchImpl call, ideally via a shared helper so all three sites stay consistent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/github-app.ts` at line 1, Add a shared AbortController-based
timeout helper and use it for the fetchImpl calls in installationForRepo,
installationToken, and fetchIssue, applying the same fail-fast duration to each
outbound GitHub request. Ensure the timeout signal is passed to fetchImpl and
abort resources are cleaned up after completion, while preserving existing
response and error handling.

Comment on lines +78 to +132
export async function installationForRepo(
env: Env,
cfg: GithubAppConfig,
repo: string,
fetchImpl: typeof fetch = fetch,
): Promise<number | null> {
const key = `ghinst:${repo}`;
const cached = (await env.GITHUB_CACHE.get(key)) as string | null;
if (cached !== null) return cached === "none" ? null : Number(cached);
try {
const res = await fetchImpl(`https://api.github.com/repos/${repo}/installation`, {
headers: githubHeaders(await appJwt(cfg)),
});
if (res.status === 404) {
await env.GITHUB_CACHE.put(key, "none", { expirationTtl: INSTALL_TTL });
return null;
}
if (!res.ok) return null;
const body = (await res.json()) as { id?: number };
if (typeof body.id !== "number") return null;
await env.GITHUB_CACHE.put(key, String(body.id), { expirationTtl: INSTALL_TTL });
return body.id;
} catch {
return null;
}
}

/**
* Short-lived installation token, KV-cached below its 60-minute life. Tokens
* are read-only (App permissions) and expire server-side, so KV storage is an
* accepted trade-off (spec §Components).
*/
export async function installationToken(
env: Env,
cfg: GithubAppConfig,
installationId: number,
fetchImpl: typeof fetch = fetch,
): Promise<string | null> {
const key = `ghtok:${installationId}`;
const cached = (await env.GITHUB_CACHE.get(key)) as string | null;
if (cached !== null) return cached;
try {
const res = await fetchImpl(
`https://api.github.com/app/installations/${installationId}/access_tokens`,
{ method: "POST", headers: githubHeaders(await appJwt(cfg)) },
);
if (res.status !== 201) return null;
const body = (await res.json()) as { token?: string };
if (typeof body.token !== "string") return null;
await env.GITHUB_CACHE.put(key, body.token, { expirationTtl: TOKEN_TTL });
return body.token;
} catch {
return null;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

No timeout on GitHub API calls used in the request path.

installationForRepo and installationToken call fetchImpl with no AbortController/timeout. Both run synchronously inside resolveTitles, which the new /workspaces/:name/github-titles route awaits before responding — a slow/hanging GitHub response ties up the request with no fail-fast guard. Same root cause as fetchIssue in apps/api/src/github-titles.ts; see the consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/github-app.ts` around lines 78 - 132, The GitHub API calls in
installationForRepo and installationToken lack request timeouts, allowing
resolveTitles to hang on slow responses. Add the same AbortController/timeout
mechanism used by fetchIssue to both fetchImpl calls, pass the abort signal, and
preserve the existing null-return behavior when requests time out or fail.

Comment on lines +33 to +46
async function fetchIssue(
repo: string,
num: string,
token: string,
fetchImpl: typeof fetch,
): Promise<FetchOutcome> {
let res: Response;
try {
res = await fetchImpl(`https://api.github.com/repos/${repo}/issues/${num}`, {
headers: githubHeaders(token),
});
} catch {
return { kind: "error" };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

No timeout on the GitHub issue fetch.

fetchIssue calls fetchImpl with no AbortController/timeout, same gap as installationForRepo/installationToken in apps/api/src/github-app.ts. All three run synchronously in the /workspaces/:name/github-titles request path. See consolidated comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/github-titles.ts` around lines 33 - 46, Add timeout-based
cancellation to the fetch request in fetchIssue, using an AbortController and
passing its signal to fetchImpl. Ensure the controller aborts after the
configured timeout and is cleaned up when the request completes, while
preserving the existing error outcome behavior.

Comment on lines +86 to +97
let negativeTtl = NEGATIVE_TTL;
let outcome: FetchOutcome = { kind: "error" };
const homeToken = await installationToken(env, cfg, Number(cfg.homeInstallationId), fetchImpl);
if (homeToken) outcome = await fetchIssue(repo, num, homeToken, fetchImpl);

if (outcome.kind === "no-access") {
const installId = await installationForRepo(env, cfg, repo, fetchImpl);
if (installId !== null) {
const instToken = await installationToken(env, cfg, installId, fetchImpl);
if (instToken) outcome = await fetchIssue(repo, num, instToken, fetchImpl);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fallback ladder skips repo-installation retry when the home token mint itself fails.

outcome only reaches "no-access" when fetchIssue actually ran with the home token. If installationToken(...) for the home installation returns null (transient mint failure — network error, non-201), outcome stays { kind: "error" } from its initial value, and the outcome.kind === "no-access" check on line 91 never triggers — so the repo-specific installation is never tried, even for refs with a legitimate, cached install. The ref then gets negative-cached for up to an hour on a purely transient blip, for both public and private repos.

🐛 Proposed fix
-  if (outcome.kind === "no-access") {
+  if (outcome.kind === "no-access" || !homeToken) {
     const installId = await installationForRepo(env, cfg, repo, fetchImpl);

Want me to add a test case for "home token mint fails, repo install succeeds" alongside the fix?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let negativeTtl = NEGATIVE_TTL;
let outcome: FetchOutcome = { kind: "error" };
const homeToken = await installationToken(env, cfg, Number(cfg.homeInstallationId), fetchImpl);
if (homeToken) outcome = await fetchIssue(repo, num, homeToken, fetchImpl);
if (outcome.kind === "no-access") {
const installId = await installationForRepo(env, cfg, repo, fetchImpl);
if (installId !== null) {
const instToken = await installationToken(env, cfg, installId, fetchImpl);
if (instToken) outcome = await fetchIssue(repo, num, instToken, fetchImpl);
}
}
let negativeTtl = NEGATIVE_TTL;
let outcome: FetchOutcome = { kind: "error" };
const homeToken = await installationToken(env, cfg, Number(cfg.homeInstallationId), fetchImpl);
if (homeToken) outcome = await fetchIssue(repo, num, homeToken, fetchImpl);
if (outcome.kind === "no-access" || !homeToken) {
const installId = await installationForRepo(env, cfg, repo, fetchImpl);
if (installId !== null) {
const instToken = await installationToken(env, cfg, installId, fetchImpl);
if (instToken) outcome = await fetchIssue(repo, num, instToken, fetchImpl);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/github-titles.ts` around lines 86 - 97, Update the fallback
logic in the surrounding fetch flow so a failed home installationToken attempt
still proceeds to installationForRepo and retries fetchIssue with the
repo-specific token. Track the home-token failure distinctly from the initial
error outcome, while preserving the existing no-access fallback and
negative-cache behavior for genuine terminal errors.

Comment thread apps/web/src/lib/api-client.ts Outdated
Comment on lines +550 to +552
const body = (await result.response.json().catch(() => null)) as { refs?: unknown } | null;
if (!body || typeof body !== "object" || !body.refs || typeof body.refs !== "object") return null;
return body.refs as GithubTitleMap;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate each refs entry before casting.

Arrays and entries such as { title: 1 } pass this check, despite the documented malformed-response fallback. That can propagate a non-string label into rail rendering. Require a non-array record whose entries are null or have string title/state and a valid kind; otherwise return null.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/api-client.ts` around lines 550 - 552, Strengthen the
response validation before returning from the refs-parsing flow: require
body.refs to be a non-array record, and validate every entry as either null or
an object with string title/state fields and a valid kind value. Return null for
any malformed entry, including arrays or non-string titles, and only cast to
GithubTitleMap after all entries pass validation.

Comment on lines +147 to +154
list.querySelector<HTMLButtonElement>("[data-rail-more]")?.addEventListener(
"click",
() => {
expanded = true;
paint(items.length);
},
{ once: true },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid the newly introduced DOM-library generic.

Line 147 adds HTMLButtonElement, which conflicts with the Workers TypeScript rule. Use the project’s Worker-safe abstraction or structural typing for the selected control instead.

As per coding guidelines, “do not rely on DOM library types in this Workers codebase.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/workspace-rail.ts` around lines 147 - 154, Remove the
HTMLButtonElement generic from the querySelector call in the rail expansion
listener. Use the project’s Worker-safe selector abstraction or structural
typing for the selected control, while preserving the existing click handler and
once-only listener behavior.

Source: Coding guidelines

- 8s AbortSignal deadline on every outbound GitHub call (githubFetch helper)
- retry via the repo installation when the home token fails to mint, so a
  transient mint blip can't negative-cache a resolvable ref for an hour
- per-entry shape validation of the github-titles response in the web client
@zachdunn

Copy link
Copy Markdown
Member Author

Addressed the review in f483dea:

  • Timeouts (3 comments): added a shared githubFetch helper with an 8s AbortSignal.timeout deadline; all three outbound call sites (installationForRepo, installationToken, fetchIssue) go through it, and an aborted fetch falls into the existing degrade-to-null handling.
  • Ladder skips installation retry on home-mint failure: fixed as proposed (no-access || homeToken === null) with a regression test (cached repo installation resolves a ref while the home mint fails).
  • refs entry validation in the web client: added per-entry shape checks (null or {title: string, state: string, kind: "pull"|"issue"}, non-array) before the cast.

Skipped — HTMLButtonElement generic in workspace-rail.ts: that generic predates this PR (moved, not added, by the show-more fix), and apps/web/src/lib is browser-side app code that uses DOM lib generics (querySelector<HTMLElement>, HTMLButtonElement) throughout — the Workers no-DOM-types rule applies to the worker packages, not this client bundle.

The batch-throttling nitpick is covered by the home-token memoization added in c965f42 (one lazily-minted token per resolveTitles batch).

@zachdunn
Zach Dunn (zachdunn) merged commit e1a2dc3 into main Jul 20, 2026
4 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the claude/github-title-loading-e3b02e branch July 20, 2026 14:30
Zach Dunn (zachdunn) added a commit that referenced this pull request Jul 20, 2026
The exact-PR-match banner rendered the bare gh.ref for files without an
attach-time gh.title, never calling the title resolver that PR #282 wired
into the right rail. Resolve titles once per listing in WorkspaceFileTable
and share the ref->title map across both the banner and the rail, dropping
the rail's duplicate fetch. Falls back to the bare ref on resolver miss.

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

Labels

coderabbit:review Trigger CodeRabbit review for the PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connected-work rail: show real PR/issue titles

1 participant