fix: aria-label binding, dead import, guardAndLoad network error handling

- Fix 1: Add missing `:` binding prefix to aria-label on score badge
  (was emitting literal backtick template string to DOM)
- Fix 2: Remove unused `watch` import from InterviewPrepView.vue
- Fix 3: guardAndLoad now checks interviewsStore.error after fetchAll;
  shows pageError banner instead of silently redirecting to /interviews
  on network failure; job is now a ref set explicitly in the guard
- Fix 4: Remove unconditional research-badge from InterviewCard.vue
  (added in this branch; card has no access to prep store so badge
  always showed regardless of whether research exists)
This commit is contained in:
pyr0ball 2026-03-20 18:57:41 -07:00
parent 1cee73e233
commit 8479f79701
2 changed files with 18 additions and 24 deletions

View file

@ -176,7 +176,6 @@ const columnColor = computed(() => {
<div v-if="interviewDateLabel" class="date-chip">
{{ dateChipIcon }} {{ interviewDateLabel }}
</div>
<div class="research-badge research-badge--done">🔬 Research ready</div>
</div>
<footer class="card-footer">
<button class="card-action" @click.stop="emit('move', job.id)">Move to </button>
@ -331,23 +330,6 @@ const columnColor = computed(() => {
align-self: flex-start;
}
.research-badge {
display: inline-flex;
align-items: center;
gap: 3px;
border-radius: 99px;
padding: 2px 8px;
font-size: 0.7rem;
font-weight: 700;
align-self: flex-start;
margin-top: 2px;
}
.research-badge--done {
background: color-mix(in srgb, var(--status-phone) 12%, var(--color-surface-raised));
color: var(--status-phone);
border: 1px solid color-mix(in srgb, var(--status-phone) 30%, var(--color-surface-raised));
}
.card-footer {
border-top: 1px solid var(--color-border-light);

View file

@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useStorage } from '@vueuse/core'
import { usePrepStore } from '../stores/prep'
import { useInterviewsStore } from '../stores/interviews'
import type { PipelineJob } from '../stores/interviews'
const route = useRoute()
const router = useRouter()
@ -22,10 +23,7 @@ const jobId = computed<number | null>(() => {
// Current job (from interviews store)
const PREP_VALID_STATUSES = ['phone_screen', 'interviewing', 'offer'] as const
const job = computed(() => {
if (jobId.value === null) return null
return interviewsStore.jobs.find(j => j.id === jobId.value) ?? null
})
const job = ref<PipelineJob | null>(null)
// Tabs
type TabId = 'jd' | 'email' | 'letter'
@ -35,6 +33,9 @@ const activeTab = ref<TabId>('jd')
const notesKey = computed(() => `cf-prep-notes-${jobId.value ?? 'none'}`)
const callNotes = useStorage(notesKey, '')
// Page-level error (e.g. network failure during guard)
const pageError = ref<string | null>(null)
// Routing / guard
async function guardAndLoad() {
if (jobId.value === null) {
@ -45,6 +46,11 @@ async function guardAndLoad() {
// Ensure the interviews store is populated
if (interviewsStore.jobs.length === 0) {
await interviewsStore.fetchAll()
if (interviewsStore.error) {
// Store fetch failed don't redirect, show error
pageError.value = 'Failed to load job data. Please try again.'
return
}
}
const found = interviewsStore.jobs.find(j => j.id === jobId.value)
@ -53,6 +59,7 @@ async function guardAndLoad() {
return
}
job.value = found
await prepStore.fetchFor(jobId.value)
}
@ -361,7 +368,7 @@ async function onGenerate() {
<span
class="score-badge"
:class="matchScoreBadge(matchScore).cls"
aria-label="`Match score: ${matchScore ?? 'unknown'}%`"
:aria-label="`Match score: ${matchScore ?? 'unknown'}%`"
>
{{ matchScoreBadge(matchScore).icon }}
</span>
@ -446,6 +453,11 @@ async function onGenerate() {
</div>
</template>
<!-- Network/load error don't redirect, show message -->
<div v-else-if="pageError" class="error-banner" role="alert">
{{ pageError }}
</div>
<!-- Fallback while redirecting -->
<div v-else class="prep-loading" aria-live="polite">
Redirecting