Support issue identifiers (PAP-39) in URLs and prefer them throughout

Backend:
- Add router.param middleware in issues, activity, and agents routes to
  resolve identifiers (e.g. PAP-39) to UUIDs before handlers run
- Simplify GET /issues/:id now that param middleware handles resolution
- Include identifier in getAncestors response and issuesForRun query
- Add identifier field to IssueAncestor shared type

Frontend:
- Update all issue navigation links across 15+ files to use
  issue.identifier ?? issue.id instead of bare UUIDs
- Add URL redirect in IssueDetail: navigating via UUID automatically
  replaces the URL with the human-readable identifier
- Fix childIssues filter to use issue.id (UUID) instead of URL param
  so it works correctly with identifier-based URLs
- Add issueUrl() utility in lib/utils.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-20 16:04:05 -06:00
parent 0c0c308594
commit 9906a5ba06
21 changed files with 80 additions and 34 deletions

View File

@@ -1017,9 +1017,10 @@ export function agentRoutes(db: Db) {
});
router.get("/issues/:id/live-runs", async (req, res) => {
const id = req.params.id as string;
const rawId = req.params.id as string;
const issueSvc = issueService(db);
const issue = await issueSvc.getById(id);
const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
if (!issue) {
res.status(404).json({ error: "Issue not found" });
return;
@@ -1045,7 +1046,7 @@ export function agentRoutes(db: Db) {
and(
eq(heartbeatRuns.companyId, issue.companyId),
inArray(heartbeatRuns.status, ["queued", "running"]),
sql`${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${id}`,
sql`${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issue.id}`,
),
)
.orderBy(desc(heartbeatRuns.createdAt));
@@ -1054,9 +1055,10 @@ export function agentRoutes(db: Db) {
});
router.get("/issues/:id/active-run", async (req, res) => {
const id = req.params.id as string;
const rawId = req.params.id as string;
const issueSvc = issueService(db);
const issue = await issueSvc.getById(id);
const isIdentifier = /^[A-Z]+-\d+$/i.test(rawId);
const issue = isIdentifier ? await issueSvc.getByIdentifier(rawId) : await issueSvc.getById(rawId);
if (!issue) {
res.status(404).json({ error: "Issue not found" });
return;