Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a full issues dashboard: Vue 3 components (repo selector, toolbar, issue row, page), a Pinia issue store with filtering/sorting and critical scoring, a server API endpoint that paginates GitHub GraphQL results with caching, shared types/utils for issue mapping, i18n entries/schema, and tests. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as Issues Page (Vue)
participant Store as Issue Store (Pinia)
participant API as Server API (/api/issues)
participant GH as GitHub GraphQL
User->>UI: open issues page / interact
UI->>Store: selectRepo(repoName)
alt repo not loaded/cached
Store->>API: GET /api/issues?state=open&repo=repoName
API->>GH: GraphQL ISSUES_QUERY (paginated)
GH-->>API: issue pages (nodes)
API->>API: toIssue(node) -> Issue[]
API-->>Store: Issue[] (cached)
end
Store->>Store: compute filteredIssues (search, filters, sort)
Store-->>UI: filteredIssues
UI->>Store: update filters/sort/search
Store->>Store: recompute filteredIssues
Store-->>UI: updated list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@app/components/issue/IssueRow.vue`:
- Around line 8-129: The row is missing repository name, author (avatar + name)
and relative created time; add a new computed createdAgo (e.g.
useTimeAgo(computed(() => props.issue.createdAt))) and render the repo name,
author avatar/name and createdAgo in the Row 2 meta next to the existing
number/updated items. Specifically, update IssueRow.vue to: 1) add createdAgo
computed like updatedAgo, 2) render a repository label (e.g. issue.repository or
issue.repoName) before the issue number to disambiguate cross‑repo rows, and 3)
render the author use (issue.author.login and issue.author.avatarUrl) as a small
UAvatar + name and show createdAgo (relative created time) in the meta section;
use the existing UAvatar, UTooltip and UIcon components and the props.issue
fields (createdAt, repository/repoName, author.login, author.avatarUrl) so the
new elements integrate with the current template layout.
In `@app/pages/issues/index.vue`:
- Around line 35-40: The template uses dynamic i18n keys with
t(`issues.error.${store.errorKey}.title`) and
t(`issues.error.${store.errorKey}.description`) which static analyzers can't
validate; replace this with an explicit mapping: create a computed mapping
(e.g., a computed property or helper function like getIssueI18nKeys or
issueI18nMap) that maps each known store.errorKey value ('rateLimited',
'sessionExpired', etc.) to concrete translation keys for title and description,
then pass those concrete keys to UAlert (and keep using store.errorKey for
color/icon logic). This ensures only predefined keys are used and is easily
discovered by static tools while preserving existing behavior.
In `@i18n/locales/de.json`:
- Around line 151-178: Remove or reintroduce the unused German i18n keys to
match the English cleanup: either delete the keys "issues.title",
"issues.comments", and "issues.linkedPrs" from the DE locale or wire them into
the UI the same way the EN locale uses them (ensure the component rendering
issues list looks up issues.title, issues.comments and issues.linkedPrs). Update
any code that references these keys (search for usages of issues.title,
issues.comments, issues.linkedPrs in the issues list / issue components) to
ensure consistency between locales and tests.
In `@i18n/locales/en.json`:
- Around line 151-179: CI reports that the i18n keys issues.title,
issues.comments, and issues.linkedPrs are unused; either wire them into the UI
where the issues list is rendered (use issues.title for the issues header,
issues.comments for comment counts display, and issues.linkedPrs for linked PR
counts in the issue item components) or remove these keys from the locale and
the corresponding schema/translation references (ensure you also remove or
update any references in the issues-related schema/translation lookup code such
as count/label rendering logic to avoid missing-key errors).
In `@shared/utils/issue.ts`:
- Around line 18-20: The maintainerCommented boolean is unreliable because the
GraphQL field comments(first: 100) only returns the first 100 comment nodes;
update the logic that sets maintainerCommented to page through comments (use
comments.pageInfo.hasNextPage and pageInfo.endCursor) or request a larger limit
until you either find a comment by maintainerLogin or exhaust pages. Locate the
object building maintainerCommented in shared/utils/issue.ts and replace the
single-page check (comments.nodes.some(...)) with a loop/function that queries
comments(first, after) repeatedly (or a helper like
fetchCommentsUntilFoundMaintainer) and short-circuits when a maintainer comment
is found, then set maintainerCommented accordingly.
🧹 Nitpick comments (2)
server/api/issues/index.get.ts (1)
27-28: Optimize comments query to reduce payload size.The query fetches
comments(first: 100) { totalCount nodes { author { login } } }but this retrieves up to 100 comment nodes per issue just to check if the maintainer commented. For issues with many comments, this significantly inflates the response payload.Consider fetching only the first few comments or restructuring the maintainer-check logic server-side if possible.
♻️ Proposed optimization
- comments(first: 100) { totalCount nodes { author { login } } } + comments(first: 20) { totalCount nodes { author { login } } }Note: This trades completeness (may miss maintainer comments beyond first 20) for performance. Consider if that tradeoff is acceptable for your use case.
app/components/issue/IssueRepoSelect.vue (1)
30-37: Consider adding error handling for initial fetch.If
repoStore.fetchAll()fails, the component continues without handling the error. The user may see an empty state without understanding why.🛡️ Proposed error handling
onMounted(async () => { - await repoStore.fetchAll() - const saved = settings.value?.selectedRepo - if (saved && repoStore.repos.some(r => r.fullName === saved)) { - await issueStore.selectRepo(saved) + try { + await repoStore.fetchAll() + const saved = settings.value?.selectedRepo + if (saved && repoStore.repos.some(r => r.fullName === saved)) { + await issueStore.selectRepo(saved) + } + } catch (err) { + // Error should be handled by the parent page or store's error state + console.error('Failed to load repositories', err) } })
| const { t } = useI18n() | ||
| const updatedAgo = useTimeAgo(computed(() => props.issue.updatedAt)) | ||
|
|
||
| const stateIcon = computed(() => { | ||
| if (props.issue.state === 'OPEN') return 'i-lucide-circle-dot' | ||
| if (props.issue.stateReason === 'NOT_PLANNED') return 'i-lucide-circle-slash' | ||
| return 'i-lucide-check-circle' | ||
| }) | ||
|
|
||
| const stateColor = computed(() => { | ||
| if (props.issue.state === 'OPEN') return 'text-emerald-500' | ||
| if (props.issue.stateReason === 'NOT_PLANNED') return 'text-neutral-400' | ||
| return 'text-violet-500' | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <a | ||
| :href="issue.url" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| class="flex items-start gap-3 px-4 py-3 hover:bg-elevated transition-colors" | ||
| > | ||
| <!-- State icon --> | ||
| <UIcon | ||
| :name="stateIcon" | ||
| class="size-5 mt-0.5 shrink-0" | ||
| :class="stateColor" | ||
| /> | ||
|
|
||
| <!-- Content --> | ||
| <div class="min-w-0 flex-1"> | ||
| <!-- Row 1: Title + labels --> | ||
| <div class="flex items-center gap-2 flex-wrap"> | ||
| <span class="font-medium text-highlighted hover:underline"> | ||
| {{ issue.title }} | ||
| </span> | ||
| <UBadge | ||
| v-for="label in issue.labels" | ||
| :key="label.name" | ||
| variant="subtle" | ||
| size="xs" | ||
| :style="{ backgroundColor: `#${label.color}20`, color: `#${label.color}` }" | ||
| > | ||
| {{ label.name }} | ||
| </UBadge> | ||
| </div> | ||
|
|
||
| <!-- Row 2: Meta --> | ||
| <div class="flex items-center gap-3 mt-1 text-xs text-muted"> | ||
| <UTooltip | ||
| v-if="!issue.maintainerCommented && issue.commentCount > 0" | ||
| :text="t('issues.needsResponse')" | ||
| > | ||
| <span class="inline-flex items-center gap-0.5 text-amber-500"> | ||
| <UIcon | ||
| name="i-lucide-message-circle-warning" | ||
| class="size-3.5" | ||
| /> | ||
| </span> | ||
| </UTooltip> | ||
| <span>#{{ issue.number }}</span> | ||
| <span>{{ updatedAgo }}</span> | ||
|
|
||
| <span | ||
| v-if="issue.commentCount" | ||
| class="inline-flex items-center gap-0.5" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-message-square" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.commentCount }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.linkedPrCount" | ||
| class="inline-flex items-center gap-0.5 text-blue-500" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-git-pull-request" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.linkedPrCount }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.state === 'CLOSED'" | ||
| class="inline-flex items-center gap-0.5" | ||
| :class="issue.stateReason === 'NOT_PLANNED' ? 'text-neutral-400' : 'text-violet-500'" | ||
| > | ||
| {{ issue.stateReason === 'NOT_PLANNED' ? t('issues.closedAsNotPlanned') : t('issues.closedAs') }} | ||
| </span> | ||
|
|
||
| <span | ||
| v-if="issue.milestone" | ||
| class="inline-flex items-center gap-0.5" | ||
| > | ||
| <UIcon | ||
| name="i-lucide-milestone" | ||
| class="size-3.5" | ||
| /> | ||
| {{ issue.milestone }} | ||
| </span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Right side: Assignees + author --> | ||
| <div class="flex items-center gap-1 shrink-0"> | ||
| <UTooltip | ||
| v-for="assignee in issue.assignees" | ||
| :key="assignee.login" | ||
| :text="assignee.login" | ||
| > | ||
| <UAvatar | ||
| :src="assignee.avatarUrl" | ||
| :alt="assignee.login" | ||
| size="xs" | ||
| /> | ||
| </UTooltip> | ||
| </div> | ||
| </a> |
There was a problem hiding this comment.
Missing required row metadata (repo, author, created time).
PR objectives call for repository name, author avatar/name, and relative created time in each row. Currently only updated time and assignees are rendered, so the overview can’t disambiguate cross‑repo issues or show author/created info.
💡 Minimal sketch of the missing metadata wiring
<script setup lang="ts">
@@
const { t } = useI18n()
const updatedAgo = useTimeAgo(computed(() => props.issue.updatedAt))
+const createdAgo = useTimeAgo(computed(() => props.issue.createdAt))
@@
</script><!-- Row 2: Meta -->
<div class="flex items-center gap-3 mt-1 text-xs text-muted">
+ <span class="inline-flex items-center gap-1">
+ <UIcon name="i-lucide-book" class="size-3.5" />
+ {{ issue.repository.nameWithOwner }}
+ </span>
+ <span class="inline-flex items-center gap-1">
+ <UAvatar :src="issue.author.avatarUrl" :alt="issue.author.login" size="xs" />
+ {{ issue.author.login }}
+ </span>
<span>#{{ issue.number }}</span>
+ <span>{{ createdAgo }}</span>
<span>{{ updatedAgo }}</span>
...
</div>🤖 Prompt for AI Agents
In `@app/components/issue/IssueRow.vue` around lines 8 - 129, The row is missing
repository name, author (avatar + name) and relative created time; add a new
computed createdAgo (e.g. useTimeAgo(computed(() => props.issue.createdAt))) and
render the repo name, author avatar/name and createdAgo in the Row 2 meta next
to the existing number/updated items. Specifically, update IssueRow.vue to: 1)
add createdAgo computed like updatedAgo, 2) render a repository label (e.g.
issue.repository or issue.repoName) before the issue number to disambiguate
cross‑repo rows, and 3) render the author use (issue.author.login and
issue.author.avatarUrl) as a small UAvatar + name and show createdAgo (relative
created time) in the meta section; use the existing UAvatar, UTooltip and UIcon
components and the props.issue fields (createdAt, repository/repoName,
author.login, author.avatarUrl) so the new elements integrate with the current
template layout.
| <UAlert | ||
| :title="t(`issues.error.${store.errorKey}.title`)" | ||
| :description="t(`issues.error.${store.errorKey}.description`)" | ||
| :color="store.errorKey === 'rateLimited' ? 'warning' : 'error'" | ||
| :icon="store.errorKey === 'sessionExpired' ? 'i-lucide-log-out' : store.errorKey === 'rateLimited' ? 'i-lucide-clock' : 'i-lucide-alert-triangle'" | ||
| /> |
There was a problem hiding this comment.
Address pipeline warning: dynamic i18n keys may not be statically analyzable.
The pipeline flagged dynamic translation keys. While this works at runtime, static analysis tools can't verify these keys exist. Consider using a type-safe approach or explicitly listing the possible keys.
🔧 Proposed fix using explicit key mapping
+const errorConfig = computed(() => {
+ const configs: Record<string, { title: string; description: string; color: string; icon: string }> = {
+ sessionExpired: {
+ title: t('issues.error.sessionExpired.title'),
+ description: t('issues.error.sessionExpired.description'),
+ color: 'error',
+ icon: 'i-lucide-log-out',
+ },
+ rateLimited: {
+ title: t('issues.error.rateLimited.title'),
+ description: t('issues.error.rateLimited.description'),
+ color: 'warning',
+ icon: 'i-lucide-clock',
+ },
+ fetchError: {
+ title: t('issues.error.fetchError.title'),
+ description: t('issues.error.fetchError.description'),
+ color: 'error',
+ icon: 'i-lucide-alert-triangle',
+ },
+ }
+ return store.errorKey ? configs[store.errorKey] : null
+})Then update template:
- <UAlert
- :title="t(`issues.error.${store.errorKey}.title`)"
- :description="t(`issues.error.${store.errorKey}.description`)"
- :color="store.errorKey === 'rateLimited' ? 'warning' : 'error'"
- :icon="store.errorKey === 'sessionExpired' ? 'i-lucide-log-out' : store.errorKey === 'rateLimited' ? 'i-lucide-clock' : 'i-lucide-alert-triangle'"
- />
+ <UAlert
+ v-if="errorConfig"
+ :title="errorConfig.title"
+ :description="errorConfig.description"
+ :color="errorConfig.color"
+ :icon="errorConfig.icon"
+ />🤖 Prompt for AI Agents
In `@app/pages/issues/index.vue` around lines 35 - 40, The template uses dynamic
i18n keys with t(`issues.error.${store.errorKey}.title`) and
t(`issues.error.${store.errorKey}.description`) which static analyzers can't
validate; replace this with an explicit mapping: create a computed mapping
(e.g., a computed property or helper function like getIssueI18nKeys or
issueI18nMap) that maps each known store.errorKey value ('rateLimited',
'sessionExpired', etc.) to concrete translation keys for title and description,
then pass those concrete keys to UAlert (and keep using store.errorKey for
color/icon logic). This ensures only predefined keys are used and is easily
discovered by static tools while preserving existing behavior.
| "issues": { | ||
| "title": "Issues", | ||
| "open": "Open", | ||
| "closed": "Closed", | ||
| "selectRepo": "Select a repository...", | ||
| "openCount": "{count} open", | ||
| "noResults": "No issues found.", | ||
| "comments": "{count} comment | {count} comments", | ||
| "linkedPrs": "{count} linked PR | {count} linked PRs", | ||
| "closedAs": "Closed", | ||
| "closedAsNotPlanned": "Not planned", | ||
| "needsResponse": "Waiting for your response", | ||
| "search": "Search issues...", | ||
| "count": "No issues | {count} issue | {count} issues", | ||
| "filter": { | ||
| "unassigned": "Unassigned", | ||
| "hasLinkedPr": "Has PR", | ||
| "noLinkedPr": "No PR", | ||
| "hasMilestone": "Milestone" | ||
| }, | ||
| "sort": { | ||
| "critical": "Needs attention", | ||
| "newest": "Newest", | ||
| "oldest": "Oldest", | ||
| "mostCommented": "Most commented", | ||
| "leastCommented": "Least commented", | ||
| "recentlyUpdated": "Recently updated" | ||
| }, | ||
| "error": { |
There was a problem hiding this comment.
Resolve unused i18n keys flagged by CI.
CI reports issues.title, issues.comments, and issues.linkedPrs as unused. Either wire them into the UI or remove them (and corresponding schema entries) to keep locales clean.
🤖 Prompt for AI Agents
In `@i18n/locales/en.json` around lines 151 - 179, CI reports that the i18n keys
issues.title, issues.comments, and issues.linkedPrs are unused; either wire them
into the UI where the issues list is rendered (use issues.title for the issues
header, issues.comments for comment counts display, and issues.linkedPrs for
linked PR counts in the issue item components) or remove these keys from the
locale and the corresponding schema/translation references (ensure you also
remove or update any references in the issues-related schema/translation lookup
code such as count/label rendering logic to avoid missing-key errors).
There was a problem hiding this comment.
used dynamically. is fine
There was a problem hiding this comment.
@Flo0806, understood—thanks for clarifying that these keys are used dynamically. I've noted this for future reference.
✏️ Learnings added
Learnt from: Flo0806
Repo: flumen-dev/flumen.dev PR: 58
File: i18n/locales/en.json:151-176
Timestamp: 2026-02-13T14:17:04.414Z
Learning: In i18n/locales/en.json, the keys `issues.title`, `issues.comments`, and `issues.linkedPrs` are used dynamically in the issues feature code and should not be flagged as unused even if static analysis cannot detect them.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| commentCount: node.comments.totalCount, | ||
| linkedPrCount: node.timelineItems.totalCount, | ||
| maintainerCommented: node.comments.nodes.some(c => c.author?.login === maintainerLogin), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "index.get.ts" -path "*/api/issues/*" | head -5Repository: flumen-dev/flumen.dev
Length of output: 97
🏁 Script executed:
cat server/api/issues/index.get.ts | head -250Repository: flumen-dev/flumen.dev
Length of output: 2417
🏁 Script executed:
cat shared/utils/issue.tsRepository: flumen-dev/flumen.dev
Length of output: 975
maintainerCommented may be inaccurate for issues with more than 100 comments.
The query fetches only the first 100 comment nodes via comments(first: 100). If an issue has more than 100 comments and the maintainer commented beyond that limit, maintainerCommented will incorrectly report false. Consider fetching a higher limit, paginating through all comments, or using a different approach to check maintainer participation.
linkedPrCount is correctly derived from timelineItems.totalCount and properly filtered to CROSS_REFERENCED_EVENT items, so no issue there.
🤖 Prompt for AI Agents
In `@shared/utils/issue.ts` around lines 18 - 20, The maintainerCommented boolean
is unreliable because the GraphQL field comments(first: 100) only returns the
first 100 comment nodes; update the logic that sets maintainerCommented to page
through comments (use comments.pageInfo.hasNextPage and pageInfo.endCursor) or
request a larger limit until you either find a comment by maintainerLogin or
exhaust pages. Locate the object building maintainerCommented in
shared/utils/issue.ts and replace the single-page check
(comments.nodes.some(...)) with a loop/function that queries comments(first,
after) repeatedly (or a helper like fetchCommentsUntilFoundMaintainer) and
short-circuits when a maintainer comment is found, then set maintainerCommented
accordingly.
There was a problem hiding this comment.
not this time. 100 comments are a lot... it's enough for now
There was a problem hiding this comment.
@Flo0806, understood! That's a reasonable pragmatic decision for the current scope. 👍
🧠 Learnings used
Learnt from: Flo0806
Repo: flumen-dev/flumen.dev PR: 58
File: i18n/locales/en.json:151-176
Timestamp: 2026-02-13T14:17:04.414Z
Learning: In i18n/locales/en.json, the keys `issues.title`, `issues.comments`, and `issues.linkedPrs` are used dynamically in the issues feature code and should not be flagged as unused even if static analysis cannot detect them.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@app/components/issue/IssueRow.vue`:
- Around line 57-80: The meta row is missing the repository identifier; update
IssueRow.vue's template (near the span showing {{ issue.number }}) to render the
repository name (e.g., add a span like {{ issue.repository.name }} or {{
issue.repoFullName }} depending on the issue object shape) so the UI shows
"title + number + repository name"; if the issue prop doesn't currently include
repository info, ensure the parent supplies issue.repository (or repoFullName)
from the API/prop source used by IssueRow.vue (keep placement alongside {{
issue.number }} and maintain styling consistent with createdAgo/updatedAgo).
🧹 Nitpick comments (1)
app/components/issue/IssueRow.vue (1)
125-125: Stale comment: author is now in the meta row.The comment says "Assignees + author" but author avatar/login was moved to the meta section (line 70-77). Update the comment to reflect the actual content.
✏️ Proposed fix
- <!-- Right side: Assignees + author --> + <!-- Right side: Assignees -->
| <!-- Row 2: Meta --> | ||
| <div class="flex items-center gap-3 mt-1 text-xs text-muted"> | ||
| <UTooltip | ||
| v-if="!issue.maintainerCommented && issue.commentCount > 0" | ||
| :text="t('issues.needsResponse')" | ||
| > | ||
| <span class="inline-flex items-center gap-0.5 text-amber-500"> | ||
| <UIcon | ||
| name="i-lucide-message-circle-warning" | ||
| class="size-3.5" | ||
| /> | ||
| </span> | ||
| </UTooltip> | ||
| <span class="inline-flex items-center gap-1"> | ||
| <UAvatar | ||
| :src="issue.author.avatarUrl" | ||
| :alt="issue.author.login" | ||
| size="3xs" | ||
| /> | ||
| {{ issue.author.login }} | ||
| </span> | ||
| <span>#{{ issue.number }}</span> | ||
| <span>{{ createdAgo }}</span> | ||
| <span>{{ updatedAgo }}</span> |
There was a problem hiding this comment.
Repository name still missing from meta row.
PR objectives require displaying "title + number + repository name" to disambiguate issues across repositories. The author and created time from the prior feedback have been added, but the repository identifier is still absent.
🔧 Proposed fix to add repository name
<!-- Row 2: Meta -->
<div class="flex items-center gap-3 mt-1 text-xs text-muted">
+ <span class="inline-flex items-center gap-1">
+ <UIcon name="i-lucide-book-marked" class="size-3.5" />
+ {{ issue.repository }}
+ </span>
<UTooltip
v-if="!issue.maintainerCommented && issue.commentCount > 0"
:text="t('issues.needsResponse')"🤖 Prompt for AI Agents
In `@app/components/issue/IssueRow.vue` around lines 57 - 80, The meta row is
missing the repository identifier; update IssueRow.vue's template (near the span
showing {{ issue.number }}) to render the repository name (e.g., add a span like
{{ issue.repository.name }} or {{ issue.repoFullName }} depending on the issue
object shape) so the UI shows "title + number + repository name"; if the issue
prop doesn't currently include repository info, ensure the parent supplies
issue.repository (or repoFullName) from the API/prop source used by IssueRow.vue
(keep placement alongside {{ issue.number }} and maintain styling consistent
with createdAgo/updatedAgo).
Summary
Related issue(s)
Closes #53
Type of change
Checklist
Summary by CodeRabbit
New Features
Localization
Tests