PrdDiffPanel.vue
2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<template>
<div class="prd-diff">
<div class="meta">
<span class="rev">PRD rev <b>{{ state?.prd_rev ?? '-' }}</b></span>
<span class="rev">SDD rev <b>{{ state?.sdd_rev ?? '-' }}</b></span>
<span class="grow" />
<button class="btn" @click="reload" :disabled="loading">{{ loading ? '...' : 'refresh' }}</button>
</div>
<div v-if="entries.length === 0" class="empty">no changelog yet</div>
<ul v-else class="entries">
<li v-for="e in entries.slice(0, 12)" :key="e.ts + ':' + e.kind" :class="['entry', e.kind]">
<span class="kind">{{ e.kind }}</span>
<span class="rev-num">rev {{ e.rev }}</span>
<span class="trigger">[{{ e.trigger || '?' }}]</span>
<span class="summary">{{ e.summary }}</span>
</li>
</ul>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
const props = defineProps({
pid: { type: String, required: true },
state: { type: Object, default: null },
});
const entries = ref([]);
const loading = ref(false);
async function reload() {
loading.value = true;
try {
const r = await fetch(`/api/devloop/projects/${props.pid}/changelog`);
const j = await r.json();
entries.value = j.entries || [];
} catch (e) {
/* swallow */
} finally {
loading.value = false;
}
}
onMounted(reload);
// Refresh changelog when prd_rev or sdd_rev changes
watch(() => props.state?.prd_rev, reload);
watch(() => props.state?.sdd_rev, reload);
</script>
<style scoped>
.prd-diff { display:flex; flex-direction:column; gap:8px; }
.meta { display:flex; align-items:center; gap:14px; font-size:12px; color:#666; }
.meta .grow { flex:1; }
.meta .rev b { color:#222; font-size:14px; }
.empty { padding:20px; color:#888; text-align:center; }
.entries { list-style:none; padding:0; margin:0; max-height:200px; overflow-y:auto; }
.entry {
display:flex; gap:10px; padding:6px 8px;
border-bottom:1px solid #f0f0f3; font-size:13px;
}
.entry:last-child { border-bottom:none; }
.entry .kind {
font-family: ui-monospace, monospace; font-size:11px; padding:2px 8px; border-radius:10px;
background:#eef; color:#446; flex:0 0 auto;
}
.entry.sdd_rev .kind { background:#ffeede; color:#a64a00; }
.entry.prd_rev .kind { background:#deecff; color:#1d4d99; }
.entry .rev-num { font-weight:600; color:#222; flex:0 0 50px; }
.entry .trigger { color:#888; font-size:11px; flex:0 0 auto; }
.entry .summary { color:#444; flex:1; overflow:hidden; text-overflow:ellipsis; }
.btn { background:#222; color:#fff; border:none; border-radius:4px; padding:3px 10px; cursor:pointer; font-size:11px; }
.btn:disabled { background:#bbb; }
</style>