Skip to content

VPR-59 [3/4] CMS migration: management SPA (frontend)#253

Open
rlorenzo wants to merge 3 commits into
VPR-59-cms-r2-content-nav-backendfrom
VPR-59-cms-r3-frontend
Open

VPR-59 [3/4] CMS migration: management SPA (frontend)#253
rlorenzo wants to merge 3 commits into
VPR-59-cms-r2-content-nav-backendfrom
VPR-59-cms-r3-frontend

Conversation

@rlorenzo

@rlorenzo rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Slice 3 of 4 (stacks on #252; the diff shows only this slice). Built from the CI-verified branch tip — see #251 for why the earlier 6-PR stack (#245-#250) was replaced. Merging this completes the CMS migration as originally scoped; slice 4 (#255) adds delegated block editing on top.

Scope — the CMS management SPA (Vue 3 + Quasar)

  • Files at /CMS/ManageFiles: server-paged list with shareable URL-synced filters, upload/edit dialog with name-conflict and 409 edit-conflict handling, audit trail, import and bulk-encrypt tools.
  • Content blocks: list + QEditor editor with version history, diff viewer, attached files, 409 conflict dialog; cross-block Edit History page with per-version diffs.
  • Left-nav editor with drag/keyboard reorder (shared SortableList + live-region announcements) and independent settings/items save; link collections management.
  • Redesigned CMS hub with permission-scoped cards and a recent-activity rail.
  • Shared composables (use-server-table with stale-response guard, use-url-filtered-table, use-reorder, route-focus a11y), mobile card mode for the five dense list tables, WCAG-focused polish, and the full frontend test suite (1,026 tests).

Deploy notes (whole stack)

  • TEST/PROD config: CMS:LegacyWebrootPath (import tool), viperfiles.txt key file must exist, optional CMS:FileStorageRoot/CMS:ProfilePhotoPath/CMS:EncryptionKeyFile overrides, CMS:DownloadRateLimit limits, Cms:TrashPurgeEnabled stays false until the legacy VIPER 1 purge is retired.
  • Deployment flows through Development → TEST first, per repo convention.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Completes the CMS migration by landing the Vue 3 + Quasar “management SPA” for CMS tools (files, content blocks, left-nav menus), alongside shared SPA infrastructure (router scroll/focus behavior), reusable list/table composables, shared UI components, and an expanded frontend test suite.

Changes:

  • Added CMS management pages/components (hub, files audit, bulk encrypt, content history/diff, left-nav menu management) plus shared CMS composables/types and utilities.
  • Introduced shared frontend building blocks: createSpaRouter (hash + query-aware scroll), useRouteFocus refinements, useReorder + SortableList, and server-paged table composables with URL-synced filters.
  • Updated global styles/tokens/utilities for surfaces, focus rings, and responsive list/table/card layouts; minor Effort audit filter layout adjustments.

Reviewed changes

Copilot reviewed 85 out of 85 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
VueApp/src/styles/colors.css Adds surface/diff/focus tokens and bg utilities for secondary palette colors.
VueApp/src/styles/base.css Adds global a11y/layout helpers (focus, list headers, dialog sizing, card-mode table styling).
VueApp/src/Students/router/index.ts Switches router factory import to create-spa-router.
VueApp/src/shared/createSpaRouter.ts Removes old router factory module (renamed/replaced).
VueApp/src/shared/create-spa-router.ts New router factory with improved base-url normalization and scroll behavior (hash + query-aware).
VueApp/src/shared/tests/create-spa-router.test.ts Tests router scroll behavior and route-focus behavior.
VueApp/src/layouts/ViperLayout.vue Adds “Skip to section menu” skip link when left nav exists.
VueApp/src/layouts/LeftNav.vue Improves left-nav a11y (skip target focusability, roles, landmark labeling).
VueApp/src/Effort/router/index.ts Switches router factory import to create-spa-router.
VueApp/src/Effort/pages/AuditList.vue Reworks search + clear-filters placement to match CMS audit UX.
VueApp/src/Effort/components/EffortLeftNav.vue Makes left nav skip target focusable (tabindex=-1).
VueApp/src/CTS/router/index.ts Switches router factory import to create-spa-router.
VueApp/src/config/colors.ts Extends accessible text-color logic for the new arboretum background color.
VueApp/src/Computing/router/index.ts Switches router factory import to create-spa-router.
VueApp/src/composables/ViperFetch.ts Adds postForm/putForm helpers for multipart uploads; minor object shorthand cleanup.
VueApp/src/composables/use-route-focus.ts Avoids focus-steal on same-path navigations (query-only/hash-only).
VueApp/src/composables/use-reorder.ts New reorder composable (move math + “just moved” highlight state).
VueApp/src/composables/tests/use-reorder.test.ts Unit tests for useReorder.
VueApp/src/components/SortableList.vue New shared sortable list component (drag + buttons + a11y announcements + animations).
VueApp/src/components/BreadcrumbHeading.vue New breadcrumb-style <h1> component for subpages.
VueApp/src/components/tests/sortable-list.test.ts Unit tests for SortableList.
VueApp/src/CMS/types/index.ts Expands CMS TypeScript types for files, blocks, nav, audit/history, and activity rail.
VueApp/src/CMS/router/routes.ts Defines CMS SPA routes and shared home-permission set.
VueApp/src/CMS/router/index.ts Uses new router factory and adds CMS area-root canonicalization to Home.
VueApp/src/CMS/pages/LeftNavMenus.vue New CMS page: list/manage left-nav menus (filters + add dialog + delete).
VueApp/src/CMS/pages/FileAuditLog.vue New CMS page: server-paged file audit trail with URL-synced filters.
VueApp/src/CMS/pages/ContentBlockHistory.vue New CMS page: cross-block edit history with diff viewer.
VueApp/src/CMS/pages/CmsHome.vue Redesigns CMS hub with permission-scoped cards and purge-soon warning.
VueApp/src/CMS/pages/BulkEncrypt.vue New CMS tool page: server-paged list for bulk encryption.
VueApp/src/CMS/file-types.ts Centralizes accepted file extension allow-list for uploads.
VueApp/src/CMS/composables/use-url-filtered-table.ts New composable: URL-synced filters layered over server paging.
VueApp/src/CMS/composables/use-server-table.ts New composable: server-paged QTable core with stale-response guard.
VueApp/src/CMS/composables/use-content-diff-viewer.ts New shared diff-viewer state/helpers for ContentDiffDialog consumers.
VueApp/src/CMS/components/StatusIcon.vue New component: tooltip + sr-only label pattern for status icons.
VueApp/src/CMS/components/RecentActivity.vue New component: CMS hub activity rail + latest-diff helpers.
VueApp/src/CMS/components/PersonSelector.vue New component: server-searched people selector with stale-response guard.
VueApp/src/CMS/components/PermissionSelector.vue New component: lazy-loaded permission selector with client-side filtering.
VueApp/src/CMS/components/PermissionChips.vue New component: compact permission/person-count chip rendering.
VueApp/src/CMS/components/ModifiedStamp.vue New component: shared “MM/DD/YY by user” stamp for table + card modes.
VueApp/src/CMS/components/ListCardField.vue New component: label/value rows for QTable card/grid mode.
VueApp/src/CMS/components/ListCard.vue New component: shared QTable card/grid wrapper for CMS list pages.
VueApp/src/CMS/components/LinkCollections.vue Optimizes grouping/filtering behavior and adjusts filter UX.
VueApp/src/CMS/components/Link.vue Uses StatusBadge for tags and updates tag palette to non-semantic brand roles.
VueApp/src/CMS/components/LeftNavMenuSettingsFields.vue New shared settings form for left-nav menu create/edit.
VueApp/src/CMS/components/LeftNavMenuDialog.vue New create-menu dialog with validation + unsaved-changes guard.
VueApp/src/CMS/components/EditButton.vue New shared edit action button component for tables/cards.
VueApp/src/CMS/components/DeleteRestoreButtons.vue New shared delete/restore action button component.
VueApp/src/CMS/components/DateRangeFilter.vue New shared from/to date filter inputs.
VueApp/src/CMS/components/ContentDiffDialog.vue New diff dialog for content history comparisons.
VueApp/src/CMS/components/ContentBlock.vue Fixes content fetch behavior (avoid duplicate/deep watch) and handles fetch failure.
VueApp/src/CMS/components/ActivityRow.vue New component: recent-activity row layout with safe whole-row click behavior.
VueApp/src/CMS/tests/use-url-filtered-table.test.ts Tests URL-synced server table behavior and equality-guard.
VueApp/src/CMS/tests/use-server-table.test.ts Tests server table behavior and stale-response guard.
VueApp/src/CMS/tests/url.test.ts Tests CMS URL safety helpers (scheme allow/block, malformed handling).
VueApp/src/CMS/tests/test-utils.ts CMS test harness utilities (Quasar/Pinia/router + seeded permissions).
VueApp/src/CMS/tests/router-canonicalization.test.ts Tests CMS /CMS/ → Home canonicalization across permission shapes.
VueApp/src/CMS/tests/person-selector.test.ts Tests PersonSelector min-chars gate and stale-response guard.
VueApp/src/CMS/tests/permission-selector.test.ts Tests PermissionSelector lazy load + filtering + null-clear behavior.
VueApp/src/CMS/tests/modified-stamp.test.ts Tests ModifiedStamp formatting and dual render modes.
VueApp/src/CMS/tests/link.test.ts Tests Link URL safety rendering and tag color cycling behavior.
VueApp/src/CMS/tests/left-nav-menus.test.ts Tests left-nav menu list behavior (filters, deep-link add dialog, delete flow).
VueApp/src/CMS/tests/left-nav-menu-dialog.test.ts Tests create-menu dialog validation and POST success/failure behavior.
VueApp/src/CMS/tests/file-form-dialog-conflict.test.ts Tests file upload name-conflict flows and multipart upload helpers usage.
VueApp/src/CMS/tests/date-range-filter.test.ts Tests DateRangeFilter v-model and change emission.
VueApp/src/CMS/tests/content-diff-dialog.test.ts Tests ContentDiffDialog close/legend/empty behaviors.
VueApp/src/CMS/tests/content-block-history.test.ts Tests history list URL sync + diff viewer behaviors.
VueApp/src/CMS/tests/content-block-edit-diff.test.ts Tests ContentBlockEdit diff-against-current behaviors.
VueApp/src/CMS/tests/cms-home.test.ts Tests CMS hub permission-gated sections and purge warning behavior.
VueApp/src/ClinicalScheduler/router/index.ts Switches router factory import to create-spa-router.
VueApp/src/CAHFS/router/index.ts Switches router factory import to create-spa-router.

Comment thread VueApp/src/styles/base.css Outdated
Comment thread VueApp/src/styles/base.css Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 85 out of 85 changed files in this pull request and generated 2 comments.

Comment thread VueApp/src/composables/use-reorder.ts
Comment thread VueApp/src/styles/base.css

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 85 out of 85 changed files in this pull request and generated no new comments.

@rlorenzo

rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds CMS admin pages, dialogs, and composables for files, content blocks, left-nav menus, and link collections, plus shared sortable/reorder and router updates, accessibility changes, and an Effort audit filter layout tweak.

Changes

CMS Content Management Feature

Layer / File(s) Summary
Shared CMS types and composables
VueApp/src/CMS/types/index.ts, VueApp/src/CMS/composables/*, VueApp/src/composables/ViperFetch.ts, VueApp/src/CMS/file-types.ts, VueApp/src/CMS/__tests__/test-utils.ts, VueApp/src/CMS/__tests__/use-*.test.ts
Adds CMS domain type exports, server-table/url-filtered-table/content-diff-viewer composables, multipart fetch helpers, accepted extensions, and shared CMS test utilities.
CMS home and route entry
VueApp/src/CMS/pages/CmsHome.vue, VueApp/src/CMS/router/*, VueApp/src/components/BreadcrumbHeading.vue, VueApp/src/CMS/__tests__/cms-home.test.ts, VueApp/src/CMS/__tests__/router-canonicalization.test.ts
Adds the permission-gated CMS home dashboard, purge warning banner, CMS entry-point redirect, expanded routes, and breadcrumb heading support.
Recent activity feed
VueApp/src/CMS/components/RecentActivity.vue, .../ActivityRow.vue, .../StatusIcon.vue, VueApp/src/CMS/__tests__/recent-activity.test.ts
Adds aggregated recent activity rendering across blocks, files, and left-nav menus with diff viewing.
Content block editing and history
VueApp/src/CMS/pages/ContentBlock*.vue, .../ContentDiffDialog.vue, .../DateRangeFilter.vue, .../ModifiedStamp.vue, .../PermissionSelector.vue, .../PersonSelector.vue, .../ListCard*.vue, .../PermissionChips.vue, .../EditButton.vue, .../DeleteRestoreButtons.vue, related tests
Adds content block CRUD, history, diff viewing, file attachment flows, and shared selector/display components with tests.
File management, import, and audit
VueApp/src/CMS/pages/Files.vue, .../FileFormDialog.vue, .../InlineFileUpload.vue, .../BulkEncrypt.vue, .../ImportFiles.vue, .../FileAuditLog.vue, related tests
Adds file listing, upload/conflict handling, bulk encryption, import preview/import, and audit trail views with tests.
Left-nav menu management
VueApp/src/CMS/pages/LeftNav*.vue, .../LeftNavMenuDialog.vue, .../LeftNavMenuSettingsFields.vue, related tests
Adds left-nav menu CRUD, item reordering, conflict handling, and unsaved-changes guards with tests.
Link collections management
VueApp/src/CMS/components/Link.vue, .../LinkCollections.vue, VueApp/src/CMS/pages/ManageLinkCollections.vue, related tests
Updates link rendering, filtering/grouping, and reorderable collection/link management using SortableList, with URL safety tests.

Estimated code review effort: 5 (Critical) | ~150 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ContentBlockEdit
  participant InlineFileUpload
  participant ViperFetch
  participant CMSAPI
  User->>ContentBlockEdit: save block
  ContentBlockEdit->>InlineFileUpload: commit staged uploads
  InlineFileUpload->>ViperFetch: POST/PUT/GET/DEL file requests
  ViperFetch->>CMSAPI: multipart and delete requests
  CMSAPI-->>InlineFileUpload: created/attached files or error
  InlineFileUpload-->>ContentBlockEdit: attached files and createdGuids
  ContentBlockEdit->>ViperFetch: POST/PUT block save
  ViperFetch->>CMSAPI: save content block
  CMSAPI-->>ContentBlockEdit: saved block or conflict
Loading

Possibly related PRs

  • ucdavis/VIPER#177: Shares the CMS router factory/import migration and route-guard wiring changes.

Suggested reviewers: bsedwards

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change as the CMS management SPA migration slice.
Description check ✅ Passed The description is detailed and directly matches the CMS management SPA and supporting frontend changes in this PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch VPR-59-cms-r3-frontend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 31

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
VueApp/src/Effort/router/index.ts (1)

13-52: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Eval-permission load failure permanently locks the user out for the session.

loadEvalPermissions() has no catch around the get() call, and the entire "check/load missing Eval permissions" block (lines 44-51) only runs when !isInternalNavigation (line 36). Once userStore.isLoggedIn is true, every later internal navigation skips this block entirely — so if the first fetch fails or returns success: false, hasEvalPermissions never becomes true again for the rest of the session, and the user is silently redirected away from every SVMSecure.Eval-gated route with no retry path short of a full page reload.

Decouple the eval-permission check from the internal-navigation re-auth gate, and handle fetch failures explicitly so a transient error doesn't permanently disable future attempts.

🩹 Proposed direction
 router.beforeEach(async (to, from) => {
     const userStore = useUserStore()

-    // Skip re-authentication for in-app navigations (tab switches, course-to-course).
-    // The user is already logged in and permissions are loaded; re-calling requireLogin
-    // Would overwrite the permission array and cause a visible flash.
     const isInternalNavigation = from.matched.length > 0 && userStore.isLoggedIn
     if (!isInternalNavigation) {
         const { requireLogin } = useRequireLogin(to)
         const loginResult = await requireLogin(true, "SVMSecure.Effort")
         if (loginResult !== null && !loginResult) {
             return false
         }
+    }
 
-        // Eval permissions are in a separate area, so they aren't loaded by requireLogin
-        const existingPermissions = userStore.userInfo?.permissions ?? []
-        const hasEvalPermissions = existingPermissions.some((p: string) => p.startsWith("SVMSecure.Eval"))
-        if (!hasEvalPermissions) {
-            if (!evalPermissionsPromise) {
-                evalPermissionsPromise = loadEvalPermissions()
-            }
-            await evalPermissionsPromise
-        }
+    // Re-check on every navigation (not just external ones) so a transient failure can be retried.
+    const existingPermissions = userStore.userInfo?.permissions ?? []
+    const hasEvalPermissions = existingPermissions.some((p: string) => p.startsWith("SVMSecure.Eval"))
+    if (!hasEvalPermissions) {
+        if (!evalPermissionsPromise) {
+            evalPermissionsPromise = loadEvalPermissions()
+        }
+        await evalPermissionsPromise
     }

And add error handling in loadEvalPermissions (e.g. catch + log) rather than letting get() rejections propagate unhandled into the guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/Effort/router/index.ts` around lines 13 - 52, The eval-permission
refresh in router.beforeEach is currently tied to the non-internal navigation
path, so a failed load can permanently prevent future retries for the session.
Move the SVMSecure.Eval permission check/load logic out of the
isInternalNavigation gate so it still runs when needed on later navigations, and
update loadEvalPermissions() to catch errors from useFetch().get() and log them
instead of letting rejections escape. Keep the retry latch reset in
loadEvalPermissions() so future attempts can refetch after a failure.
VueApp/src/CMS/components/LinkCollections.vue (1)

16-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the lt-sm header visibility choice.

header-class="bg-grey-2 lt-sm" uses Quasar's visibility utility class, which hides the header entirely on sm+ screens. This is only correct because filtersExpandedComputed (line 138-139) forces the panel open for $q.screen.gt.xs, so the toggle header is unnecessary above xs. That relationship isn't obvious from this line alone and reads like a bug (hiding the "Filters" label/toggle on desktop) — a short comment here would prevent a future "fix" that reintroduces the header unnecessarily or removes the force-open logic without updating this class.

💬 Suggested comment
+            <!-- Panel is force-expanded above xs (see filtersExpandedComputed), so the
+                 toggle header is only shown/needed on xs screens. -->
             <q-expansion-item
                 v-model="filtersExpandedComputed"
                 icon="filter_list"
                 label="Filters"
                 header-class="bg-grey-2 lt-sm"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/CMS/components/LinkCollections.vue` around lines 16 - 22, Add a
short inline comment near the q-expansion-item header-class in
LinkCollections.vue explaining that lt-sm intentionally hides the header on sm+
screens because filtersExpandedComputed forces the panel open for
$q.screen.gt.xs. Reference filtersExpandedComputed and the q-expansion-item so
future edits don’t remove this coupling or “fix” the hidden desktop header.
VueApp/src/CMS/pages/ManageLinkCollections.vue (1)

512-563: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Re-baseline after a partial tag-save failure recovery.

handleTagFailure reloads tags and resets draftTags/deletedTagIds to the true server state, but never calls setCollectionBaseline(). If any tag mutation succeeded before the failing step (e.g. a delete went through before an add failed), the resynced draftTags now differs from the baseline captured when the dialog opened. Closing afterward will spuriously trigger "Unsaved Changes" even though the displayed state is already the current server truth.

🛠️ Proposed fix
         async function handleTagFailure(message: string) {
             $q.notify({ type: "negative", message })
             await loadTags()
             draftTags.value = collectionTags.value.map((t) => ({ ...t }))
             deletedTagIds.value = []
+            setCollectionBaseline()
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/CMS/pages/ManageLinkCollections.vue` around lines 512 - 563, The
partial tag-save recovery in handleTagFailure reloads server tags and resets
draftTags/deletedTagIds, but it leaves the original baseline stale. Update
handleTagFailure in ManageLinkCollections.vue to also re-baseline the dialog
state after loadTags() by calling setCollectionBaseline() so the refreshed draft
state matches the true server state. Keep this in the same recovery path used by
the tag mutation loop (delete/add/order failures) so closing the dialog does not
trigger a false Unsaved Changes warning.
♻️ Duplicate comments (3)
VueApp/src/CMS/components/LeftNavMenuDialog.vue (1)

53-60: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Same create-action color inconsistency as LeftNavEdit.vue.

"Create Menu" here also uses color="primary" rather than positive; see the corresponding comment on LeftNavEdit.vue (lines 35-50) for the guideline mapping and the sibling buttons that follow it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/CMS/components/LeftNavMenuDialog.vue` around lines 53 - 60, The
Create Menu action in LeftNavMenuDialog.vue uses the wrong button color and
should match the create-action styling used elsewhere. Update the q-btn inside
the menu creation form to use the positive color instead of primary, following
the same pattern as the create button in LeftNavEdit.vue and keeping the Create
Menu action consistent with the sibling buttons in this dialog.

Source: Coding guidelines

VueApp/src/CMS/__tests__/content-blocks.test.ts (1)

48-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate of routeGet in content-block-history.test.ts.

Same pattern (mockGet.mockReset + implementation branching on URL substring) is repeated here almost verbatim. Consider extracting a shared factory into test-utils.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/CMS/__tests__/content-blocks.test.ts` around lines 48 - 64, The
routeGet mock setup in content-blocks.test.ts duplicates the same mockReset plus
URL-branching implementation used in the content-block-history test. Extract
this shared behavior into a reusable factory/helper in test-utils.ts, then
update routeGet to delegate to it while preserving the /section-paths branch and
pagination handling via mockGet, mockDel, and mockPost.
VueApp/src/CMS/components/PersonSelector.vue (1)

58-58: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Same unguarded inject("apiURL") pattern as PermissionSelector.vue.

Duplicate of the concern raised in PermissionSelector.vue (Line 34): no default value, unknown-typed injection, silent malformed URL if apiURL isn't provided by an ancestor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@VueApp/src/CMS/components/PersonSelector.vue` at line 58, The apiURL
injection in PersonSelector.vue is unguarded and can produce a malformed CMS
options URL if no ancestor provides it. Update the PersonSelector setup logic
around the apiURL/inject usage to mirror the safer pattern from
PermissionSelector.vue: give inject("apiURL") a default or explicit guard,
ensure the injected value is properly typed, and avoid constructing the URL
unless a valid base URL is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@VueApp/src/CMS/__tests__/content-block-edit-diff.test.ts`:
- Around line 105-166: These tests trigger Oxlint style warnings: add
expect.hasAssertions() in the async test cases, capitalize the describe/it
titles to match the project’s convention, prefer toStrictEqual where deep
equality is intended, and avoid top-level beforeEach hooks by moving the
routeGet setup into the relevant nested describe blocks in
content-block-edit-diff.test.ts.

In `@VueApp/src/CMS/__tests__/content-block-edit-save.test.ts`:
- Around line 152-160: The test file still has several oxlint-style warnings to
clean up, but the shared top-level beforeEach that resets mockGet, mockPost,
mockPut, mockDel, mockCommit, and calls routeGet() should stay in place because
it is intentionally applied across all describe blocks. Fix the remaining
stylistic issues in the surrounding tests to match the diff test file’s pattern,
and if require-top-level-describe is the only blocker here, suppress or ignore
that specific hint for this file rather than moving the shared setup.

In `@VueApp/src/CMS/__tests__/content-block-history.test.ts`:
- Around line 50-67: The mock routing logic in routeGet is duplicated across
content-block-history.test.ts and content-blocks.test.ts, so factor the shared
mock setup into a reusable routeGetFactory helper in test-utils.ts. Update the
tests to use that helper with mockGet so both suites share the same routing
behavior and stay in sync if the API handling changes.

In `@VueApp/src/CMS/__tests__/file-form-dialog-conflict.test.ts`:
- Around line 99-108: The managed-overwrite test coverage is incomplete around
the `existingModifiedOn`/`lastModifiedOn` contract in
`file-form-dialog-conflict.test.ts`. Update `NAME_TAKEN` to include
`existingModifiedOn` and strengthen the “PUTs to the existing record's guid”
assertion in the relevant `FileFormDialog`/`submit` flow test so it explicitly
checks whether `lastModifiedOn` is sent or omitted based on the backend
contract. Use the existing `NAME_TAKEN` fixture and the PUT request assertion
block to keep the guard covered.

In `@VueApp/src/CMS/__tests__/files.test.ts`:
- Around line 105-316: This test file has several oxlint warnings that may block
CI: add assertion-count checks to the affected it blocks, rename the describe
titles in Files.vue test suites to lowercase, and replace the non-strict
equality expectation with strict equality in the row presentation test. Also
remove or refactor the shared beforeEach/afterEach hooks flagged by no-hooks by
inlining setup/teardown into the individual tests or using local helper setup in
the relevant suites, and reduce the assertion count in the query param
construction test or split it into smaller tests to satisfy max-expects; use the
existing Files.vue suite names and test cases like query param construction,
delete and restore actions, row presentation, and URL and query watcher to
locate the changes.

In `@VueApp/src/CMS/__tests__/inline-file-upload.test.ts`:
- Around line 318-334: Add a test in inline-file-upload.test.ts around the
existing commitOf(wrapper) rollback scenario to cover when mockDel rejects
during the rollback loop in InlineFileUpload.vue. Use the same staged upload
setup, but make the first rollback delete fail and assert that commitOf(wrapper)
still rejects with the original upload error from postForm while the failure in
del() does not silently stop the remaining rollback behavior. Reference the
commitOf, mockDel, and mockPostForm flows to keep the new case aligned with the
existing batch rollback test.

In `@VueApp/src/CMS/__tests__/left-nav-edit.test.ts`:
- Around line 49-55: The test setup in left-nav-edit.test.ts is using a hook
pattern that conflicts with the project’s no-hooks lint rule, and the mocked GET
can be simplified. Update the test initialization around the beforeEach block
and mockGet so the setup avoids a disallowed hook pattern, and switch the menu
fetch stub in mockGet to a resolved-value style helper instead of an explicit
Promise wrapper.

In `@VueApp/src/CMS/__tests__/left-nav-menu-dialog.test.ts`:
- Around line 54-74: The test in left-nav-menu-dialog.test.ts has too many
assertions in one case, so split the current “POSTs the menu and emits created +
close on success” test into two focused tests. Keep one test around the POST
request and payload normalization using mockPost, mountDialog, and
submitDialogForm, and move the emitted event checks for created and
update:modelValue into a separate success test. This will bring each test under
the max-expects limit while preserving the existing behavior coverage.

In `@VueApp/src/CMS/components/ContentBlock.vue`:
- Around line 23-33: The ContentBlock loading flow has a stale-response race: an
earlier get() call in loadContentBlock can finish after a later
props.contentBlockName change and overwrite contentBlock.value with outdated
data. Update loadContentBlock and the watch on props.contentBlockName to ignore
out-of-order results by tracking the latest requested block name or a request
token, and only assigning to contentBlock.value when the response still matches
the current props.contentBlockName.

In `@VueApp/src/CMS/components/FileFormDialog.vue`:
- Around line 36-49: The dialog header and copy-link logic in FileFormDialog are
still reading stale props.file data after reloadLatest() updates latestFile.
Update the template bindings for the displayed friendlyName/friendlyUrl and the
copyUrl() path to use the refreshed latestFile (or the form state populated from
it) instead of props.file, so the shown and copied link always match the latest
record after a 409 refresh.

In `@VueApp/src/CMS/components/InlineFileUpload.vue`:
- Around line 109-115: The local NameCheck type in InlineFileUpload.vue has
drifted from the same shape used by FileFormDialog.vue and is missing
existingModifiedOn. Update the shared NameCheck definition by moving it into
VueApp/src/CMS/types/index.ts and have both InlineFileUpload.vue and
FileFormDialog.vue import that single type so the check-name response shape
stays consistent.
- Around line 3-11: The drop-zone/click target in InlineFileUpload should use
Quasar’s q-btn instead of a raw button element. Update the template to replace
the native button with q-btn while preserving the existing classes, disabled
state, aria-label, and click behavior, and adjust the useDropZone target to use
dropZoneRef.value?.$el so the drop zone still binds to the rendered component
element.
- Around line 288-311: The inline upload styles use fixed px values for the
border and hover ring, which should follow the stylesheet convention of using
rem units instead. Update the border thickness in InlineFileUpload.vue’s upload
state styles and the box-shadow spread in the .inline-upload--over rule to
rem-based values, keeping the existing visual appearance consistent with the
.inline-upload:focus-visible styling.
- Around line 198-219: The rollback path in commit() is not resilient because a
failing del(apiURL + guid) can abort the cleanup loop and mask the original
error from commitOne(). Update the catch block in InlineFileUpload.vue’s
commit() so each createdGuids rollback is attempted independently, with per-guid
failure handling that continues cleanup for the remaining GUIDs and
preserves/rethrows the original caught error e rather than a rollback error.

In `@VueApp/src/CMS/components/ModifiedStamp.vue`:
- Around line 30-38: The date formatting in ModifiedStamp.vue is hardcoded to
en-US, so verify whether the app should use the browser/user locale or a shared
date-formatting helper instead of a fixed locale. Update the computed formatted
value in the ModifiedStamp component to match the app’s existing date-stamp
convention, ideally by reusing the same formatting utility or locale strategy
used elsewhere in the CMS rather than embedding the locale directly.

In `@VueApp/src/CMS/components/PersonSelector.vue`:
- Around line 11-13: QSelect in PersonSelector.vue is using object options
without a label field, so its internal label resolution is incomplete for
navigation and accessibility. Update the QSelect configuration in the
PersonSelector component to explicitly set option-label to a real field from
CmsPersonOption (such as name) alongside the existing option-value="iamId", and
make sure the same mapping is applied anywhere else in the component where the
options are rendered or reused (including the selected-item/option slot setup).

In `@VueApp/src/CMS/pages/BulkEncrypt.vue`:
- Around line 224-258: The confirmation dialog logic in encryptSelected
duplicates the same Promise-based wrapper used by confirmAction in Files.vue, so
factor it into a shared useConfirmDialog composable. Move the $q.dialog
onOk/onCancel/onDismiss resolution pattern into the reusable helper, then update
encryptSelected to call that helper with the bulk-encrypt title/message/button
labels while preserving the current confirmed flow and post/encrypting handling.

In `@VueApp/src/CMS/pages/ContentBlockEdit.vue`:
- Around line 471-485: The loadHistoryVersion flow in ContentBlockEdit.vue
currently only handles the success path, so failed history loads fail silently.
Update loadHistoryVersion (and the related restoreBlock path mentioned in the
same component) to handle the non-success case from get(...) by showing a
$q.notify error message, matching the failure handling used by
diffAgainstCurrent and the restoreBlock implementation in ContentBlocks.vue.
Keep the existing success behavior intact, but add explicit user feedback
whenever res.success is false or the request fails.

In `@VueApp/src/CMS/pages/FileAuditLog.vue`:
- Around line 177-187: The action-to-color mapping in getActionColor is
duplicated with AuditList.getActionColor, so extract it into a shared utility
and have both audit views call the same helper. Create a reusable shared
function/composable (for example, a shared audit-colors module) that contains
the single source of truth for
AccessFileDenied/Add/Upload/Edit/Delete/CancelDelete/Import mappings, then
update FileAuditLog.vue and the Effort audit trail to import and use it.

In `@VueApp/src/CMS/pages/Files.vue`:
- Around line 312-318: The route.query to filters mapping is duplicated between
the initial filters ref and the route watcher, so extract a shared helper that
parses folder, status, search, encryptedOnly, and publicOnly into a single
object. Reuse that helper from both the filters initialization and the
route.query watcher to keep behavior consistent and avoid drift when adding or
changing filter fields. Locate the existing logic in the filters ref setup and
the route watcher around the route query handling.
- Around line 437-477: The confirm dialog logic is duplicated in confirmAction
and should be shared instead of being reimplemented inline in encryptSelected.
Extract the Promise-wrapped $q.dialog pattern from confirmAction into a reusable
composable such as useConfirmDialog under src/composables, then update both
Files.vue and BulkEncrypt.vue to call that shared helper for consistent dialog
behavior and easier reuse.

In `@VueApp/src/CMS/pages/LeftNavEdit.vue`:
- Around line 559-591: The scoped styles in LeftNavEdit.vue mix px and rem
units, so update the `@media (width >= 1024px)` breakpoint and the `border: 1px
solid` / `border-bottom: 1px solid` declarations in the `.menu-items
:deep(.sortable-row)` rules to use rem-based values instead. Keep the existing
selectors (`.menu-items`, `.sortable-row`, `.sortable-row:last-child`,
`.sortable-row.menu-item--header`) unchanged and align the sizing with the
nearby rem-based container query.
- Around line 35-50: The submit button in LeftNavEdit.vue is using the generic
primary color even when it renders the create flow label, so align its styling
with the create-action convention used by the Add Header/Add Link controls or
make the intent explicit if it is meant to stay a generic form submit. Update
the q-btn in the menu form (and any shared create-action button logic around the
Add Header/Add Link controls) so the label/state mapping in the same component
consistently uses positive for creation actions and primary only for non-create
submits.
- Around line 249-274: The empty menu object shape is duplicated in
LeftNavEdit.vue and LeftNavMenuDialog.vue, which makes the defaults hard to keep
in sync. Extract the repeated default menu literal into a shared factory or
helper and reuse it both where menu is initialized in LeftNavEdit.vue and where
the watch(menuId, ...) reset recreates the menu, then import the same helper
into LeftNavMenuDialog.vue so all three spots use one source of truth.

In `@VueApp/src/CMS/pages/ManageLinkCollections.vue`:
- Around line 428-462: The collection dialog’s unsaved-changes snapshot in
collectionDialogState is missing the pending New tag category input, so typing
into addTag can be lost without confirmCollectionClose() noticing. Update the
computed state used by useUnsavedChanges to include the live addTag value
alongside name, draftTags, and deletedTagIds, and keep the existing
deep-tracking approach so the guard reflects in-progress tag entry as well as
staged tag edits. This change belongs in the ManageLinkCollections.vue dialog
state and should align with the existing link dialog baseline/guard pattern.

In `@VueApp/src/components/__tests__/sortable-list.test.ts`:
- Around line 74-109: The SortableList move-buttons tests are missing coverage
for the disabled state, so add a case in the existing sortable-list.test.ts
suite using mountList and threeItems with disabled: true. Assert that the Move
up/Move down controls in the SortableList.vue behavior are non-interactive when
disabled is passed, and that clicking them does not reorder rows or emit
reorder. Keep the new assertions alongside the current “move buttons” tests and
reference the existing aria-label selectors used there.

In `@VueApp/src/components/SortableList.vue`:
- Around line 18-26: The drag path in SortableList.vue still hardcodes
VueDraggable animation, so it ignores the user’s reduced-motion preference even
though the button move path already checks reducedMotion.value. Update the
VueDraggable binding in SortableList to use the same reduced-motion condition as
the existing move logic, and ensure the drag animation is disabled or reduced
when reducedMotion.value indicates "reduce". Use the existing reducedMotion and
onDragEnd-related logic in SortableList.vue as the reference points.
- Around line 50-76: The move controls in SortableList.vue are not respecting
the existing disabled state, so users can still reorder via the up/down buttons
while VueDraggable is locked. Update the q-btn handlers and disable bindings in
the SortableList component so the buttons are also gated by the disabled prop,
using the existing disabled value alongside the index checks in onMoveUp and
onMoveDown. Make sure the same disabled condition is applied to the button state
and interaction path so reordering cannot happen mid-save.

In `@VueApp/src/composables/ViperFetch.ts`:
- Around line 274-288: The postForm and putForm signatures in ViperFetch violate
the default-param-last rule because the optional url argument is defaulted
before the required body argument. Update these two functions to remove the
unused default from url (or otherwise make the required parameters come first)
and keep their call sites and fetchWrapper usage unchanged; use the postForm and
putForm identifiers to locate the affected methods.

In `@VueApp/src/shared/__tests__/create-spa-router.test.ts`:
- Around line 13-36: Add a test case in createSpaRouter scroll behavior coverage
for the savedPosition branch: when router.options.scrollBehavior is called with
a non-null savedPosition, it should return that saved position instead of
applying the normal route/hash logic. Use the existing route helper and
scrollBehavior variable in create-spa-router.test.ts so the new assertion covers
the popstate/back-forward navigation path.

In `@VueApp/src/styles/base.css`:
- Around line 85-91: The focus style on .q-card__actions .q-btn:focus-visible in
base.css relies only on box-shadow and hardcodes the ring color, so update that
rule to use the shared --focus-ring-color token from colors.css instead of
repeating the hex value and add a transparent outline fallback alongside the
existing shadow. Keep the same selector and preserve the current visual
appearance while making the focus indicator resilient in forced-colors mode.

---

Outside diff comments:
In `@VueApp/src/CMS/components/LinkCollections.vue`:
- Around line 16-22: Add a short inline comment near the q-expansion-item
header-class in LinkCollections.vue explaining that lt-sm intentionally hides
the header on sm+ screens because filtersExpandedComputed forces the panel open
for $q.screen.gt.xs. Reference filtersExpandedComputed and the q-expansion-item
so future edits don’t remove this coupling or “fix” the hidden desktop header.

In `@VueApp/src/CMS/pages/ManageLinkCollections.vue`:
- Around line 512-563: The partial tag-save recovery in handleTagFailure reloads
server tags and resets draftTags/deletedTagIds, but it leaves the original
baseline stale. Update handleTagFailure in ManageLinkCollections.vue to also
re-baseline the dialog state after loadTags() by calling setCollectionBaseline()
so the refreshed draft state matches the true server state. Keep this in the
same recovery path used by the tag mutation loop (delete/add/order failures) so
closing the dialog does not trigger a false Unsaved Changes warning.

In `@VueApp/src/Effort/router/index.ts`:
- Around line 13-52: The eval-permission refresh in router.beforeEach is
currently tied to the non-internal navigation path, so a failed load can
permanently prevent future retries for the session. Move the SVMSecure.Eval
permission check/load logic out of the isInternalNavigation gate so it still
runs when needed on later navigations, and update loadEvalPermissions() to catch
errors from useFetch().get() and log them instead of letting rejections escape.
Keep the retry latch reset in loadEvalPermissions() so future attempts can
refetch after a failure.

---

Duplicate comments:
In `@VueApp/src/CMS/__tests__/content-blocks.test.ts`:
- Around line 48-64: The routeGet mock setup in content-blocks.test.ts
duplicates the same mockReset plus URL-branching implementation used in the
content-block-history test. Extract this shared behavior into a reusable
factory/helper in test-utils.ts, then update routeGet to delegate to it while
preserving the /section-paths branch and pagination handling via mockGet,
mockDel, and mockPost.

In `@VueApp/src/CMS/components/LeftNavMenuDialog.vue`:
- Around line 53-60: The Create Menu action in LeftNavMenuDialog.vue uses the
wrong button color and should match the create-action styling used elsewhere.
Update the q-btn inside the menu creation form to use the positive color instead
of primary, following the same pattern as the create button in LeftNavEdit.vue
and keeping the Create Menu action consistent with the sibling buttons in this
dialog.

In `@VueApp/src/CMS/components/PersonSelector.vue`:
- Line 58: The apiURL injection in PersonSelector.vue is unguarded and can
produce a malformed CMS options URL if no ancestor provides it. Update the
PersonSelector setup logic around the apiURL/inject usage to mirror the safer
pattern from PermissionSelector.vue: give inject("apiURL") a default or explicit
guard, ensure the injected value is properly typed, and avoid constructing the
URL unless a valid base URL is present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e20f3196-1912-4855-926f-c0fc4950dfbf

📥 Commits

Reviewing files that changed from the base of the PR and between 5972a6d and 27edb0b.

📒 Files selected for processing (86)
  • VueApp/src/CAHFS/router/index.ts
  • VueApp/src/CMS/__tests__/cms-home.test.ts
  • VueApp/src/CMS/__tests__/content-block-edit-diff.test.ts
  • VueApp/src/CMS/__tests__/content-block-edit-save.test.ts
  • VueApp/src/CMS/__tests__/content-block-history.test.ts
  • VueApp/src/CMS/__tests__/content-blocks.test.ts
  • VueApp/src/CMS/__tests__/content-diff-dialog.test.ts
  • VueApp/src/CMS/__tests__/date-range-filter.test.ts
  • VueApp/src/CMS/__tests__/file-form-dialog-conflict.test.ts
  • VueApp/src/CMS/__tests__/file-form-dialog.test.ts
  • VueApp/src/CMS/__tests__/files.test.ts
  • VueApp/src/CMS/__tests__/inline-file-upload.test.ts
  • VueApp/src/CMS/__tests__/left-nav-edit.test.ts
  • VueApp/src/CMS/__tests__/left-nav-menu-dialog.test.ts
  • VueApp/src/CMS/__tests__/left-nav-menus.test.ts
  • VueApp/src/CMS/__tests__/link-collections.test.ts
  • VueApp/src/CMS/__tests__/link.test.ts
  • VueApp/src/CMS/__tests__/modified-stamp.test.ts
  • VueApp/src/CMS/__tests__/permission-selector.test.ts
  • VueApp/src/CMS/__tests__/person-selector.test.ts
  • VueApp/src/CMS/__tests__/recent-activity.test.ts
  • VueApp/src/CMS/__tests__/router-canonicalization.test.ts
  • VueApp/src/CMS/__tests__/test-utils.ts
  • VueApp/src/CMS/__tests__/url.test.ts
  • VueApp/src/CMS/__tests__/use-server-table.test.ts
  • VueApp/src/CMS/__tests__/use-url-filtered-table.test.ts
  • VueApp/src/CMS/components/ActivityRow.vue
  • VueApp/src/CMS/components/ContentBlock.vue
  • VueApp/src/CMS/components/ContentDiffDialog.vue
  • VueApp/src/CMS/components/DateRangeFilter.vue
  • VueApp/src/CMS/components/DeleteRestoreButtons.vue
  • VueApp/src/CMS/components/EditButton.vue
  • VueApp/src/CMS/components/FileFormDialog.vue
  • VueApp/src/CMS/components/InlineFileUpload.vue
  • VueApp/src/CMS/components/LeftNavMenuDialog.vue
  • VueApp/src/CMS/components/LeftNavMenuSettingsFields.vue
  • VueApp/src/CMS/components/Link.vue
  • VueApp/src/CMS/components/LinkCollections.vue
  • VueApp/src/CMS/components/ListCard.vue
  • VueApp/src/CMS/components/ListCardField.vue
  • VueApp/src/CMS/components/ModifiedStamp.vue
  • VueApp/src/CMS/components/PermissionChips.vue
  • VueApp/src/CMS/components/PermissionSelector.vue
  • VueApp/src/CMS/components/PersonSelector.vue
  • VueApp/src/CMS/components/RecentActivity.vue
  • VueApp/src/CMS/components/StatusIcon.vue
  • VueApp/src/CMS/composables/use-content-diff-viewer.ts
  • VueApp/src/CMS/composables/use-server-table.ts
  • VueApp/src/CMS/composables/use-url-filtered-table.ts
  • VueApp/src/CMS/file-types.ts
  • VueApp/src/CMS/pages/BulkEncrypt.vue
  • VueApp/src/CMS/pages/CmsHome.vue
  • VueApp/src/CMS/pages/ContentBlockEdit.vue
  • VueApp/src/CMS/pages/ContentBlockHistory.vue
  • VueApp/src/CMS/pages/ContentBlocks.vue
  • VueApp/src/CMS/pages/FileAuditLog.vue
  • VueApp/src/CMS/pages/Files.vue
  • VueApp/src/CMS/pages/ImportFiles.vue
  • VueApp/src/CMS/pages/LeftNavEdit.vue
  • VueApp/src/CMS/pages/LeftNavMenus.vue
  • VueApp/src/CMS/pages/ManageLinkCollections.vue
  • VueApp/src/CMS/router/index.ts
  • VueApp/src/CMS/router/routes.ts
  • VueApp/src/CMS/types/index.ts
  • VueApp/src/CTS/router/index.ts
  • VueApp/src/ClinicalScheduler/router/index.ts
  • VueApp/src/Computing/router/index.ts
  • VueApp/src/Effort/components/EffortLeftNav.vue
  • VueApp/src/Effort/pages/AuditList.vue
  • VueApp/src/Effort/router/index.ts
  • VueApp/src/Students/router/index.ts
  • VueApp/src/components/BreadcrumbHeading.vue
  • VueApp/src/components/SortableList.vue
  • VueApp/src/components/__tests__/sortable-list.test.ts
  • VueApp/src/composables/ViperFetch.ts
  • VueApp/src/composables/__tests__/use-reorder.test.ts
  • VueApp/src/composables/use-reorder.ts
  • VueApp/src/composables/use-route-focus.ts
  • VueApp/src/config/colors.ts
  • VueApp/src/layouts/LeftNav.vue
  • VueApp/src/layouts/ViperLayout.vue
  • VueApp/src/shared/__tests__/create-spa-router.test.ts
  • VueApp/src/shared/create-spa-router.ts
  • VueApp/src/shared/createSpaRouter.ts
  • VueApp/src/styles/base.css
  • VueApp/src/styles/colors.css
💤 Files with no reviewable changes (1)
  • VueApp/src/shared/createSpaRouter.ts

Comment thread VueApp/src/CMS/__tests__/content-block-edit-diff.test.ts
Comment thread VueApp/src/CMS/__tests__/content-block-edit-save.test.ts
Comment thread VueApp/src/CMS/__tests__/content-block-history.test.ts
Comment thread VueApp/src/CMS/__tests__/file-form-dialog-conflict.test.ts
Comment thread VueApp/src/CMS/__tests__/files.test.ts
Comment thread VueApp/src/components/SortableList.vue
Comment thread VueApp/src/components/SortableList.vue
Comment thread VueApp/src/composables/ViperFetch.ts
Comment thread VueApp/src/shared/__tests__/create-spa-router.test.ts
Comment thread VueApp/src/styles/base.css
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 86 out of 86 changed files in this pull request and generated 3 comments.

Comment thread VueApp/src/shared/create-spa-router.ts Outdated
Comment thread VueApp/src/CMS/components/ActivityRow.vue
Comment thread VueApp/src/CMS/__tests__/test-utils.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 86 out of 86 changed files in this pull request and generated 1 comment.

Comment thread VueApp/src/CMS/pages/CmsHome.vue
rlorenzo added a commit that referenced this pull request Jul 3, 2026
- extract getAuditActionColor to a shared composable so the CMS file
  audit and the Effort audit list (and any future audit surface) color
  the same verb classes identically, per PR #253 review feedback
- extract ChangeDetailDiff from AuditList's three layouts to remove the
  old/new diff markup duplication flagged by the JSCPD gate
@rlorenzo rlorenzo requested a review from Copilot July 3, 2026 15:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 89 out of 89 changed files in this pull request and generated 2 comments.

Comment thread VueApp/src/styles/base.css
Comment thread VueApp/src/styles/base.css

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 89 out of 89 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 89 out of 89 changed files in this pull request and generated 1 comment.

Comment thread VueApp/src/styles/base.css

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 89 out of 89 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

VueApp/src/CMS/components/ListCard.vue:24

  • This SFC has a top-level HTML comment after the block. Vue SFC parsers/tooling typically expect only /<script>/<style> (and named custom blocks) at the top level; leaving a stray comment outside those blocks can break compilation or linting in some setups. Move the comment inside the (or into a <script> block comment) to keep the file strictly valid.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 89 out of 89 changed files in this pull request and generated no new comments.

@rlorenzo

rlorenzo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@VueApp/src/Effort/components/ChangeDetailDiff.vue`:
- Around line 7-19: The ChangeDetailDiff.vue template currently distinguishes
old and new values only with text-negative/text-positive color classes and an
arrow, which lacks non-color meaning. Update the detail rendering in the
ChangeDetailDiff component to add accessible text labels for the old and new
values, preferably visually hidden so the UI stays unchanged, and make sure the
labels are tied to the existing oldValue/newValue spans. Check whether
AuditList.vue already provides announced “Old” and “New” headers before adding
redundant labels.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c045652-6a9b-4e40-9b5a-89dac02b1f31

📥 Commits

Reviewing files that changed from the base of the PR and between 27edb0b and 8f439ba.

📒 Files selected for processing (29)
  • VueApp/src/CMS/__tests__/file-form-dialog-conflict.test.ts
  • VueApp/src/CMS/__tests__/inline-file-upload.test.ts
  • VueApp/src/CMS/__tests__/left-nav-edit.test.ts
  • VueApp/src/CMS/__tests__/link-collections.test.ts
  • VueApp/src/CMS/__tests__/test-utils.ts
  • VueApp/src/CMS/components/ActivityRow.vue
  • VueApp/src/CMS/components/ContentBlock.vue
  • VueApp/src/CMS/components/FileFormDialog.vue
  • VueApp/src/CMS/components/InlineFileUpload.vue
  • VueApp/src/CMS/components/LeftNavMenuDialog.vue
  • VueApp/src/CMS/components/LeftNavMenuSettingsFields.vue
  • VueApp/src/CMS/components/LinkCollections.vue
  • VueApp/src/CMS/components/PersonSelector.vue
  • VueApp/src/CMS/pages/ContentBlockEdit.vue
  • VueApp/src/CMS/pages/FileAuditLog.vue
  • VueApp/src/CMS/pages/Files.vue
  • VueApp/src/CMS/pages/LeftNavEdit.vue
  • VueApp/src/CMS/pages/ManageLinkCollections.vue
  • VueApp/src/CMS/types/index.ts
  • VueApp/src/Effort/components/ChangeDetailDiff.vue
  • VueApp/src/Effort/pages/AuditList.vue
  • VueApp/src/components/SortableList.vue
  • VueApp/src/components/__tests__/sortable-list.test.ts
  • VueApp/src/composables/ViperFetch.ts
  • VueApp/src/composables/__tests__/use-audit-colors.test.ts
  • VueApp/src/composables/use-audit-colors.ts
  • VueApp/src/shared/__tests__/create-spa-router.test.ts
  • VueApp/src/shared/create-spa-router.ts
  • VueApp/src/styles/base.css

Comment thread VueApp/src/Effort/components/AuditChangeDetail.vue

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 90 out of 90 changed files in this pull request and generated no new comments.

@rlorenzo rlorenzo changed the title VPR-59 [3/3] CMS migration: management SPA (frontend) VPR-59 [3/4] CMS migration: management SPA (frontend) Jul 6, 2026
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from f1ca704 to c7bd327 Compare July 10, 2026 07:57
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from 1b1df26 to 1ab4737 Compare July 10, 2026 07:57
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from c7bd327 to bdacb1a Compare July 10, 2026 08:28
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from 1ab4737 to 0ff3092 Compare July 10, 2026 08:28
rlorenzo added 3 commits July 10, 2026 08:10
Slice 3/3 of the restacked CMS migration (tip file states). The Vue 3
management UI: files, content blocks with QEditor and version diffs,
left-nav editor, link collections, import and bulk-encrypt tools, the
redesigned hub with the recent-activity rail, shared sortable-list /
server-table / dialog components, mobile card mode, and the full
frontend test suite. Completes the stack; the tree now matches the
development branch tip exactly.
- extract getAuditActionColor to a shared composable so the CMS file
  audit and the Effort audit list (and any future audit surface) color
  the same verb classes identically, per PR #253 review feedback
- extract ChangeDetailDiff from AuditList's three layouts to remove the
  old/new diff markup duplication flagged by the JSCPD gate
feature/clinical-scheduler-audit-log merges to main first and reworked
the same regions of AuditList (filter card, search row, change-detail
markup). Adopt its version wholesale - including AuditChangeDetail,
byte-identical, extended to the desktop layout - and re-apply only the
shared audit palette, so the later main merge resolves cleanly.
ChangeDetailDiff is superseded and removed.
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r3-frontend branch from bdacb1a to b2f8e89 Compare July 10, 2026 15:11
@rlorenzo rlorenzo force-pushed the VPR-59-cms-r2-content-nav-backend branch from 0ff3092 to 1e7597e Compare July 10, 2026 15:11
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.

2 participants