Skip to content

Issue Detail Page

Overview

The Issue Detail page is the hub for viewing and managing issue lifecycle in Symphony. It displays a complete timeline of activity (comments, findings, learnings, agent runs), enables inline editing of issue metadata, manages PR review workflows (approve/reject/merge), and visualizes dependencies. The page polls for real-time updates and integrates findings/learnings collection with the orchestrator's phase execution pipeline.

How It Works

Data Fetching & Polling

The page loads four independent data streams when a project slug and issue identifier are provided:

Each fetch is conditional — comments only load after the issue loads, PR data only loads after the issue loads. The polling composable refreshes all four endpoints simultaneously.

Timeline Construction

Findings, learnings, comments, and agent runs are unified into a chronologically sorted timeline via buildTimeline():

ts
const timeline = computed(() => buildTimeline(
  comments.value ?? [],
  issue.value?.findings ?? [],
  issue.value?.learnings ?? [],
  issue.value?.agentRuns ?? [],
))

Each type is mapped to a TimelineItem:

  • Comment{ type: 'comment', content, author, createdAt }
  • Finding{ type: 'finding', content: summary + details, meta: category, createdAt }
  • Learning{ type: 'learning', content: pattern + context, createdAt }
  • Agent Run{ type: 'run', content: "Attempt N: status", meta: duration, createdAt }

Items are sorted lexicographically by timestamp.

Inline Editing Pipeline

The useIssueEditor composable manages state for six edit modes:

FieldInitial StateSave Behavior
Titletext input (focused)PUT /issues/:issueId with trimmed title
Descriptiontextarea (auto-resizing)PUT /issues/:issueId with trimmed content or null
Priorityselect dropdown (1-5)PUT /issues/:issueId with numeric priority
Sizeselect dropdown (small/medium/large)PUT /issues/:issueId with size or null
Labelstag list + inputPUT /issues/:issueId with updated array
Commentsadd comment formPOST /issues/:issueId/comments with author='user'

All writes trigger an updateIssue() call that:

  1. Makes the API request
  2. Calls refreshIssue() to sync state
  3. Sets a success flash (2s) or error flash (4s)

Saves are atomic — no partial updates.

PR Workflow

When a PR exists for an issue, three user actions are available:

Approve & Merge:

  1. User clicks "Approve & Merge" → POST /issues/:issueId/merge
  2. Handler calls mergeAndComplete() service
  3. Service executes git merge in the worktree
  4. Issue status is updated, PR status → 'merged', mergedAt timestamp set
  5. Page refreshes all data streams (issue, PR, diff, comments)

Reject:

  1. User enters rejection comment → POST /issues/:issueId/pr/reject
  2. Handler finds latest open/approved PR
  3. Sets PR status → 'rejected'
  4. Moves issue status to 'todo' (allows rework)
  5. Adds system comment with rejection reason
  6. Page refreshes

Status Changes:

  • User changes status in detail panel
  • PUT /issues/:issueId/status with new status
  • Page syncs via refresh

Findings & Learnings

Agents and users add findings/learnings to document discovery:

Adding a Finding (via MCP tool or manual):

  • POST /issues/:issueId/findings with category, summary, details, files
  • Handler appends to issue.findings JSON array (immutable append)
  • Issue updatedAt is refreshed
  • Timeline updates automatically on next poll

Adding a Learning (via MCP tool or manual):

  • POST /issues/:issueId/learnings with pattern, context, applies_to
  • Handler appends to issue.learnings JSON array
  • Issue updatedAt refreshed
  • Timeline updates automatically on next poll

Both endpoints validate input via schemas and generate UUIDs with prefixes (f-* for findings, l-* for learnings).

Dependencies & Subtasks

The page displays two issue relationship types:

  1. Dependencies (prerequisites):

    • Fetched from issueDependencies table
    • Enriched with project info for cross-project badges
    • Display as read-only links (user clicks to navigate)
  2. Children (subtasks):

    • Issues with parentIssueId = issue.id
    • Enriched with project info
    • Display as expandable list

Both are fetched in the issue detail endpoint and displayed as dependency graph.

Phase Awareness

The page determines current phase label and displays phase-specific UI:

ts
const currentPhase = computed(() => {
  const label = (issue.value?.labels ?? []).find(l => isPhaseLabel(l))
  return label ? extractPhase(label) : null
})

If a phase is active AND artifacts exist:

  • Display phase metrics (entered_at, rejection_count, last_verdict)
  • Show artifact revision history
  • Display required sections from phase contract

Key Components

FileResponsibility
app/pages/p/[slug]/issues/[identifier].vueMain page component, data fetching, polling setup, tab switching (activity/diff), event handlers
app/utils/issueTypes.tsTypeScript definitions for IssueDetail, PrData, IssueComment, PrFileStats, PrGateResult, PrRiskScore, TimelineItem
app/utils/buildTimeline.tsUnifies comments, findings, learnings, agent runs into chronological timeline
app/composables/useIssueEditor.tsState management for 6 edit modes (title, description, priority, size, labels, comments), save/error/success flashes
app/utils/phaseLabels.tsPhase label utilities (isPhaseLabel, extractPhase)
app/utils/formatTime.tsTimestamp formatting utilities
app/components/DiffViewer.vuePR diff rendering with syntax highlighting
app/components/IssueDetailSidebar.vueSidebar metadata panel (priority, size, status, labels)
app/components/IssueCommentForm.vueComment submission form
app/components/CommentThread.vueThreaded comment display
app/components/PhaseArtifactTrail.vuePhase artifact revision history display
app/components/IssueMergeActions.vueApprove/Reject/Merge action buttons
server/api/projects/[projectId]/issues/by-identifier/[identifier].get.tsIssue detail endpoint, enriches with dependencies, children, agent runs
server/api/projects/[projectId]/issues/[issueId]/comments.get.tsFetch all comments for issue
server/api/projects/[projectId]/issues/[issueId]/comments.post.tsCreate new comment, validates author and content
server/api/projects/[projectId]/issues/[issueId]/pr/index.get.tsFetch latest PR for issue, sorted by createdAt desc
server/api/projects/[projectId]/issues/[issueId]/findings.post.tsCreate new finding, appends to issue.findings array
server/api/projects/[projectId]/issues/[issueId]/learnings.post.tsCreate new learning, appends to issue.learnings array
server/api/projects/[projectId]/issues/[issueId]/merge.post.tsMerge PR, delegates to mergeAndComplete service
server/api/projects/[projectId]/issues/[issueId]/pr/reject.post.tsReject PR, moves issue to 'todo', adds comment

Design Decisions

1. Polling for Real-Time Updates

Decision: Use 5-second polling instead of WebSockets.

Rationale: SQLite with WAL mode provides eventual consistency across processes. 5s is fast enough for human collaboration without server complexity. Simpler to debug and deploy than maintaining WebSocket connections.

2. Immutable Findings/Learnings Append

Decision: Append to JSON arrays rather than maintaining separate tables.

Rationale: Findings and learnings are append-only audit logs tied to issue lifetime. Storing as JSON arrays avoids join complexity and simplifies phase artifact validation (which scans findings for completion signals). Each append is atomic with issue.updatedAt refresh.

3. Composite Timeline from Multiple Sources

Decision: Unify comments, findings, learnings, runs into a single sorted timeline.

Rationale: Agents and users need to see complete context of work in chronological order. Separate feeds would fragment the narrative. The buildTimeline function is lightweight and sorts on the client.

4. Inline Editing with Optimistic UI

Decision: Update state locally, show flash messages on success/error rather than blocking UI.

Rationale: Reduces friction in workflow. Errors are displayed for 4s, enough to correct a title or re-submit. Full data refresh ensures consistency with server state.

5. PR Status Progression: open → approved → merged/rejected

Decision: Use explicit status field rather than relying on merge commit presence.

Rationale: Status is queryable and sortable. Merge handler explicitly sets status and mergedAt timestamp. Rejection is reversible (user can push again, creating a new PR record).

6. Dependencies Fetched in Issue Detail Endpoint

Decision: Enrich dependencies with project info in the GET endpoint.

Rationale: Avoids N+1 queries on the client. Dependencies are displayed read-only, so denormalization is safe. Users can click to navigate to a dependency issue.

Known Gaps

  • Comment threading: Currently flat list; no parent/child comment relationships are stored in the schema.
  • Duplicate findings/learnings: No deduplication logic exists. Agents may submit identical findings on retry. Consider adding a content hash check.
  • Async validation of phase artifacts: Artifact existence is checked at phase transition time, but not validated on the detail page. A "Ready to Complete?" indicator would help agents avoid failed submissions.