Skip to content

feat(ui): refresh button on all list pages#165

Merged
unidoc-ahall merged 2 commits into
masterfrom
feat/refresh-button
Jul 14, 2026
Merged

feat(ui): refresh button on all list pages#165
unidoc-ahall merged 2 commits into
masterfrom
feat/refresh-button

Conversation

@unidoc-ahall

Copy link
Copy Markdown
Contributor

Closes #164 — there's no way to refresh a list without F5 (which reloads the whole SPA and loses scroll/selection/filters).

Adds a shared components/RefreshButton.vue and wires it into every list page:

  • Registers: Tasks, Risks, Suppliers, Assets, Legal, Systems, Incidents, Corrective Actions, Changes, Objectives, Programs, Audit.
  • Also: Reviews, Inbox.

Each view gets a reload() that re-fetches its list (the register loaders already cascade to stats) using a dedicated refreshing ref — the icon spins, the button disables, and the list updates in place. No full reload, no auth round-trip, scroll/selection/filters preserved.

Dashboard is intentionally deferred — it's a summary that loads on navigation, not a stale-list surface. Easy follow-up if wanted.

just build-web green.

No way to refresh a list without F5. Adds a shared
components/RefreshButton.vue and wires it into every list page: the 12 registers
plus Reviews and Inbox. Each view gets a reload() that re-fetches its list (and
stats) with a dedicated 'refreshing' state, so only the button spins and scroll/
selection/filters are preserved — no full SPA reload. Dashboard deferred (summary
page, loads on nav).

Closes #164.
@unidoc-ahall unidoc-ahall added this to the 0.7.1 milestone Jul 14, 2026
@unidoc-ahall
unidoc-ahall requested a review from unidoc-alip July 14, 2026 10:04

@unidoc-alip unidoc-alip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, mechanical rollout of a shared RefreshButton.vue wired into 14 list views, matching issue #164's scope exactly. Verification turned up one real must-fix: in four views, reload() routes through a loading-toggling helper, which brings back the exact full-list-disappears flicker the PR is meant to eliminate. Also flagging an unaddressed gap (button unreachable after a failed initial load, on 12 of 14 views) and a minor duplicate-fetch nit. No security concerns — this is pure UI re-fetch wiring, no new endpoints or unescaped content.


1. [must-fix] web/src/views/CorrectiveActions.vue line 571

reload() calls await loadAll(), but loadAll() itself does loading.value = true / loading.value = false around the fetch. The template is v-if="loading"ListSkeleton / v-else-if="error" / v-else → main content (which is where RefreshButton and the list live). So every refresh click flips loading back to true, unmounts the entire list + the RefreshButton itself, and shows the skeleton in its place — the same "list disappears, scroll resets" behavior the PR is meant to eliminate. The other views affected by the identical pattern: Incidents.vue (loadAll at line 644), Objectives.vue (loadAll at line 623), Programs.vue (loadAll at line 342).

Suggested fix — extract the fetch body out of loadAll() so reload() can call it without touching loading:

async function loadAll() {
  loading.value = true
  error.value = null
  try {
    await fetchAll()
  } catch (e) {
    error.value = e.message
  } finally {
    loading.value = false
  }
}

async function fetchAll() {
  await Promise.all([loadActions(), loadStats()])
}

async function reload() {
  refreshing.value = true
  try {
    await fetchAll()
  } finally {
    refreshing.value = false
  }
}

loadActions()/loadStats() already have their own try/catch that sets error.value, so error handling on refresh is unaffected. Apply the same split in Incidents.vue (fetchAll = Promise.all([loadIncidents(), loadStats()])), Objectives.vue (fetchAll = the api.getPrograms() + loadObjectives() body), and Programs.vue (extract the api.getPrograms() call into a fetchPrograms() that reload() can call directly).


2. [should-fix] web/src/views/Assets.vue line 10

The template gates on v-if="loading" / v-else-if="error" / v-else (main content), and RefreshButton only lives in the v-else (success) branch. So if the initial list load fails, the user lands on the error screen with no RefreshButton in sight — the only way to retry is still a full F5, which is exactly the problem issue #164 was filed to fix. This affects every view using this same three-way template split: Assets, Audit, CorrectiveActions, Incidents, Legal, Objectives, Programs, Reviews, Risks, Suppliers, Systems, and Inbox (Changes.vue and Tasks.vue are fine — they use toast-based errors instead of a blocking error screen, so the button stays visible there).

Suggested fix — add a retry control to the error branch:

<div v-else-if="error" class="max-w-5xl mx-auto px-8 py-12">
  <div class="bg-red-950/40 border border-red-900/50 rounded-lg p-6 text-red-300 text-sm flex items-center justify-between gap-4">
    <span>{{ error }}</span>
    <RefreshButton :loading="refreshing" @refresh="reload" />
  </div>
</div>

This alone isn't sufficient: none of Assets, Legal, Risks, Suppliers, Systems, Audit, or Inbox's primary loaders ever reset error.value = null on entry, so a successful retry would leave the stale error banner showing forever since v-else-if="error" stays true. Those loaders need error.value = null added at the top of the fetch too (CorrectiveActions/Incidents/Objectives/Programs already do this inside loadAll(), so they're covered by the fix above once fetchAll() is wired to reload()).


3. [nit] web/src/views/Risks.vue line 432

reload() does await loadRisks(); loadAllAdvisories() — but loadRisks() already fires loadAllAdvisories() itself (fire-and-forget) as part of its own body (line 982), so every refresh click triggers two advisory fetches instead of one.

Suggested fix — drop the redundant call since loadRisks() already covers it:

async function reload() {
  refreshing.value = true
  try {
    await loadRisks()
  } finally {
    refreshing.value = false
  }
}

…pe advisory

- reload() no longer calls the loading-toggling loadAll(); extracted fetchAll() in
  CorrectiveActions/Incidents/Objectives/Programs so refresh updates the list in
  place instead of flipping the whole view (and the button) to the skeleton.
- RefreshButton added to the error branch of the 12 error-screen views, and
  reload() now resets error.value + catches, so a failed initial load is
  recoverable without F5 and a successful retry clears the banner.
- Risks reload() drops the redundant loadAllAdvisories() (loadRisks already fires
  it) — no more double advisory fetch per refresh.
@unidoc-ahall

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review @unidoc-alip — all three addressed:

1. [must-fix] loadAll() skeleton flicker on refresh. Fixed exactly as suggested. Extracted a fetchAll() (the fetch body, no loading toggle) in CorrectiveActions, Incidents, Objectives and Programs; loadAll() keeps the loading + error handling and calls fetchAll(), and reload() calls fetchAll() directly. Refresh now updates in place — the list and the button stay mounted.

2. [should-fix] No retry after a failed initial load. Added <RefreshButton :loading="refreshing" @refresh="reload" /> to the error branch of all 12 error-screen views. To make the retry actually recover (not leave a stale banner), reload() now resets error.value = null on entry and catches, so a successful retry clears the error and a failed one re-shows it. (Changes/Tasks unchanged — toast-based, button already stays visible.)

3. [nit] Risks double advisory fetch. reload() now just await loadRisks() — dropped the redundant loadAllAdvisories() since loadRisks() already fires it.

just build-web green. Pushed on feat/refresh-button.

@unidoc-alip unidoc-alip left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me

@unidoc-ahall
unidoc-ahall merged commit f9ef084 into master Jul 14, 2026
2 checks passed
@unidoc-ahall
unidoc-ahall deleted the feat/refresh-button branch July 14, 2026 14:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refresh button on list pages (no more F5)

2 participants