mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-11 21:39:01 +00:00
feat(web): show PR state badge on item cards (TASK-1230) (#436)
Render a state-colored pill badge in the top-right corner of ItemCard
whenever an item has a linked GitHub PR. The badge overlaps the card
border ("sticker on card" feel), shows the PR number, and opens the PR
URL in a new tab on click without navigating to the item.
PR data is read from `item.code_context?.pull_request` which the server
already derives from `fields.github_pr` via ExtractItemCodeContext —
no new types, helpers, or API calls. State colors:
- OPEN -> var(--accent-green)
- MERGED -> var(--accent-purple, #8b5cf6)
- CLOSED -> var(--accent-red, #ef4444)
- DRAFT -> var(--text-muted)
- default -> var(--text-muted)
Implementation matches the existing star-btn pattern: a <button> (not
nested <a>) with preventDefault + stopPropagation, then window.open
with noopener,noreferrer. Visible in both default and compact card
variants; tooltip on hover surfaces the PR title + state.
Implements [[IDEA-1214]]. Stale-state refresh (badge may show OPEN
after PR is merged) is intentionally out of scope and tracked
separately under IDEA-1214 (sibling task).
Verification:
- golangci-lint run --timeout=5m ./... -> 0 issues
- go test ./... -> all pass
- cd web && npm run build -> clean
- svelte-check -> 0 errors
Note: `make check` also runs govulncheck which flags 4 pre-existing
Go stdlib vulnerabilities (GO-2026-4982 / 4980 / 4971 / 4918) on the
1.26.2 baseline. Tracked under TASK-1232 (Bump Go toolchain to 1.26.3).
Unrelated to this PR.
This commit is contained in:
@@ -33,8 +33,28 @@
|
||||
!!onStatusClick && !!statusOptions && statusOptions.length > 1 && !!fields.status
|
||||
);
|
||||
|
||||
let pullRequest = $derived(item.code_context?.pull_request);
|
||||
|
||||
let pulsing = $state(false);
|
||||
|
||||
function prStateColor(state: string): string {
|
||||
switch (state?.toUpperCase()) {
|
||||
case 'OPEN': return 'var(--accent-green)';
|
||||
case 'MERGED': return 'var(--accent-purple, #8b5cf6)';
|
||||
case 'CLOSED': return 'var(--accent-red, #ef4444)';
|
||||
case 'DRAFT': return 'var(--text-muted)';
|
||||
default: return 'var(--text-muted)';
|
||||
}
|
||||
}
|
||||
|
||||
function openPullRequest(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (pullRequest?.url) {
|
||||
window.open(pullRequest.url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
}
|
||||
|
||||
function cycleStatus(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -81,6 +101,17 @@
|
||||
</script>
|
||||
|
||||
<a href={itemUrl} class="item-card" class:compact class:focused>
|
||||
{#if pullRequest}
|
||||
<button
|
||||
type="button"
|
||||
class="pr-badge"
|
||||
style:background={prStateColor(pullRequest.state)}
|
||||
onclick={openPullRequest}
|
||||
title="#{pullRequest.number} {pullRequest.title} ({(pullRequest.state ?? '').toLowerCase()})"
|
||||
>
|
||||
#{pullRequest.number}
|
||||
</button>
|
||||
{/if}
|
||||
<div class="card-top-row">
|
||||
<button
|
||||
class="star-btn"
|
||||
@@ -154,6 +185,7 @@
|
||||
|
||||
<style>
|
||||
.item-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
@@ -166,6 +198,34 @@
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.pr-badge {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--bg-primary);
|
||||
border-radius: 10px;
|
||||
padding: 1px 7px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.7em;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
|
||||
transition: transform 0.1s, filter 0.1s;
|
||||
}
|
||||
|
||||
.pr-badge:hover {
|
||||
filter: brightness(1.1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.pr-badge:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.item-card:hover,
|
||||
.item-card.focused {
|
||||
background: var(--bg-hover);
|
||||
|
||||
Reference in New Issue
Block a user