Skip to content

fix: treat missing capture provenance as empty state for manual cards (#680)#754

Merged
Chris0Jeky merged 8 commits intomainfrom
fix/680-manual-card-empty-provenance
Apr 4, 2026
Merged

fix: treat missing capture provenance as empty state for manual cards (#680)#754
Chris0Jeky merged 8 commits intomainfrom
fix/680-manual-card-empty-provenance

Conversation

@Chris0Jeky
Copy link
Copy Markdown
Owner

Summary

  • Manual cards (not created via capture/review pipeline) return a clean 404 from the provenance endpoint — this was already correct backend behavior
  • Frontend http.ts interceptor now logs 404 responses at warn level instead of error, so expected absences don't pollute the console
  • cardsApi.getCardProvenance now catches 404 and returns null instead of throwing, keeping the error boundary at the API layer
  • cardStore.fetchCardProvenance simplified: removes the redundant isHttpNotFound guard since the API layer now handles it
  • CardModal.vue shows "Created manually — no capture provenance." with data-testid="provenance-empty-state" for targetable assertions
  • Also fixes a pre-existing compilation error in AuthControllerEdgeCaseTests.cs where three AuthController instantiations were missing the IUserContext argument added in Fix ChangePassword identity bypass (SEC-20) #732

Closes #680.

Test plan

  • Create a card manually via "Add Card" on any board
  • Open the card editor — provenance section shows "Created manually — no capture provenance." with no console errors (only warn-level if anything)
  • Create a card via the capture → triage → proposal → apply pipeline — provenance section still shows "Capture Origin" with links
  • Frontend vitest: 1546 tests pass (includes 2 new cardsApi tests and 2 updated CardModal tests)
  • Backend dotnet test: CardServiceTests (30 tests), CardsApiTests (28 tests), AuthControllerEdgeCaseTests — all pass

Manual cards have no capture provenance; a 404 from the provenance
endpoint is expected, not exceptional. Logging it at error level creates
noisy diagnostics that hide real failures. Demote 404s to console.warn
so callers that suppress the absence silently don't pollute the console.

Fixes #680.
Manual cards have no capture provenance so a 404 from the
/provenance endpoint is expected, not exceptional. Catch 404 errors
and return null; rethrow all other errors so real failures are not
swallowed.

Fixes #680.
cardsApi.getCardProvenance now handles 404 internally and returns null,
so the duplicate isHttpNotFound check in cardStore is redundant and can
be removed. Any non-404 errors will still propagate correctly.

Fixes #680.
Replace the plain "No capture provenance available." text with a
semantic empty state that clearly communicates the card was created
manually. Add data-testid="provenance-empty-state" to enable targeted
test assertions.

Fixes #680.
- cardsApi: assert 404 returns null; assert non-404 errors rethrow
- CardModal: assert data-testid="provenance-empty-state" is shown and
  no error alert is rendered when provenance is null (manual card)
- CardModal: tighten error path assertion to check testid absence

Closes #680.
…tructor change

AuthController was refactored to inherit AuthenticatedControllerBase in
#732, which added a required IUserContext parameter. Three direct
instantiations in AuthControllerEdgeCaseTests were not updated at the
time. Add a CreateAnonymousUserContext() helper and thread the mock
IUserContext into all three construction sites.
Copilot AI review requested due to automatic review settings April 4, 2026 01:18
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Adversarial self-review

Findings

1. Global 404 downgrade in http.ts (acceptable)
The change demotes ALL 404 responses to warn level, not just provenance. This is intentional — any 404 from a well-structured API represents "not found", not an error. Callers that want to surface 404s as errors handle them in their own catch blocks (and cardsApi.getCardProvenance re-throws non-404s). No other call site is broken by this change.

2. cardsApi null return vs isHttpNotFound in cardStore (equivalent, confirmed)
The removed isHttpNotFound guard in cardStore.ts checked response?.status === 404 — identical to the check now in cardsApi.getCardProvenance. The behavior is preserved; the check just moved to the correct layer.

3. CardModal conditional rendering (correct)
The v-else empty state fires when both captureProvenance === null AND captureProvenanceError === null. When the store throws a real error, the modal's catch block sets captureProvenanceError, so the error branch (role="alert") renders instead. The test asserts exactly this: no alert shown for null, alert shown for thrown error.

4. AuthController mock IUserContext (appropriate)
The CreateAnonymousUserContext() mock is only needed to satisfy the AuthenticatedControllerBase constructor. The Login endpoint doesn't call TryGetCurrentUserId, so the unauthenticated context is correct for these pre-auth tests.

No real issues found

The diff is minimal, correct, and all tests pass (1546 frontend vitest + 30 CardService + 28 CardsApiTests + 28 AuthControllerEdgeCaseTests).

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the handling of card provenance to treat 404 "Not Found" responses as an expected empty state for manually created cards rather than an error. Key changes include updating the cardsApi to return null on 404, adjusting the global HTTP interceptor to log 404s as warnings, and updating the UI and tests to reflect this behavior. Feedback was provided regarding the global downgrade of 404 logs to warnings, as this might reduce visibility for critical missing resources; a per-request configuration is recommended instead.

Comment thread frontend/taskdeck-web/src/api/http.ts Outdated
Comment on lines +51 to +55
if (error.response.status === 404) {
console.warn('API Not Found:', error.response.data)
} else {
console.error('API Error:', error.response.data)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Downgrading all 404 responses to console.warn in the global interceptor may reduce visibility for legitimate errors. While this prevents 'noise' for optional resources like card provenance, it also affects 404s for critical resources (e.g., a missing board or user profile) which should likely remain as errors for better observability and monitoring in production environments. Consider allowing individual requests to opt-out of error logging via a custom property in the Axios config instead of applying this change globally.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the “manual card” UX by treating missing capture provenance as an expected empty state instead of an error, reducing noisy logging and keeping error handling contained at the API layer. It also fixes backend test compilation after the AuthController constructor change from #732.

Changes:

  • Frontend: cardsApi.getCardProvenance returns null on 404; store logic simplified accordingly; CardModal displays a dedicated manual-card empty state.
  • Frontend: global Axios response interceptor logs 404s at warn instead of error.
  • Backend tests: update AuthController instantiations to include IUserContext via a new helper.

Reviewed changes

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

Show a summary per file
File Description
frontend/taskdeck-web/src/tests/components/CardModal.spec.ts Updates modal tests to assert the new provenance empty state and no alert rendering.
frontend/taskdeck-web/src/tests/api/cardsApi.spec.ts Adds tests for getCardProvenance returning null on 404 and rethrowing non-404 errors.
frontend/taskdeck-web/src/store/board/cardStore.ts Simplifies provenance fetch by relying on API-layer 404 handling.
frontend/taskdeck-web/src/components/board/CardModal.vue Updates the provenance fallback UI text and adds a data-testid for the empty state.
frontend/taskdeck-web/src/api/http.ts Downgrades 404 logging from console.error to console.warn in the response interceptor.
frontend/taskdeck-web/src/api/cardsApi.ts Catches 404 for provenance endpoint and returns null instead of throwing.
backend/tests/Taskdeck.Api.Tests/AuthControllerEdgeCaseTests.cs Fixes missing IUserContext constructor arg by adding an anonymous user context helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

</p>
</div>
<p v-else class="text-xs text-on-surface-variant italic">No capture provenance available.</p>
<p v-else class="text-xs text-on-surface-variant italic" data-testid="provenance-empty-state">Created manually — no capture provenance.</p>
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

The fallback UI text now specifically says "Created manually", but the modal currently shows the v-else empty state whenever loadingCaptureProvenance is false and captureProvenance is null. On open, props.isOpen watcher awaits fetchCardComments(...) before calling loadCaptureProvenance(), so the UI can briefly render this empty state for captured cards too (until provenance loading starts), which is misleading. Consider showing a neutral loading/placeholder state until provenance has been fetched at least once for the current card (e.g., gate the empty state on loadedCaptureProvenanceCardId === props.card.id, or start loadCaptureProvenance() before awaiting comments / run both in parallel).

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +41
const candidate = e as { response?: { status?: number } } | null
if (candidate?.response?.status === 404) {
// Manual cards have no capture provenance — treat absence as empty state, not an error.
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

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

getCardProvenance currently treats any 404 from the provenance endpoint as an expected empty state. On the backend, the same endpoint returns 404 both when provenance is missing (manual cards) and when the card itself is missing / board mismatch (see CardService.GetCaptureProvenanceAsync), so this can mask real "card not found" errors and lead callers to render the manual-card empty state incorrectly. Consider narrowing the null-return behavior to only the provenance-missing case (e.g., inspect error.response.data.message for the "Capture provenance not found" message, or ideally introduce/consume a more specific error code) and rethrow other 404s.

Suggested change
const candidate = e as { response?: { status?: number } } | null
if (candidate?.response?.status === 404) {
// Manual cards have no capture provenance — treat absence as empty state, not an error.
const candidate = e as { response?: { status?: number; data?: { message?: string } } } | null
if (
candidate?.response?.status === 404 &&
candidate.response.data?.message === 'Capture provenance not found'
) {
// Manual cards have no capture provenance — treat only that absence as empty state, not an error.

Copilot uses AI. Check for mistakes.
…mpty-state on loaded id

- cardsApi.getCardProvenance: only return null for 404s whose message
  starts with "Capture provenance not found"; rethrow card-not-found 404s
  so they surface as real errors instead of silently showing manual-card
  empty state (addresses Copilot review comment)
- http.ts interceptor: revert global 404 warn downgrade — the API layer
  now handles the expected absence silently so the interceptor no longer
  needs special-casing, and downgrading all 404s globally reduces
  observability for genuine missing-resource errors (addresses Gemini
  review comment)
- CardModal.vue: gate provenance empty-state on loadedCaptureProvenanceCardId
  === card.id so the "Created manually" message does not flash for
  capture-pipeline cards while comments are being awaited before
  provenance fetch starts (addresses Copilot review comment)
- cardsApi.spec.ts: update 404 test to use the narrowed message check,
  add test for card-not-found 404 that must rethrow (1547 tests pass)
Chris0Jeky added a commit that referenced this pull request Apr 4, 2026
Updates STATUS.md, TESTING_GUIDE.md, MANUAL_TEST_CHECKLIST.md, and
IMPLEMENTATION_MASTERPLAN.md to reflect the 9 PRs merged in the
2026-04-04 post-adversarial-review hardening wave.

- STATUS.md: adds new wave entry for PRs #741#756 covering product
  telemetry taxonomy, two bug fixes (#683/#744, #680/#754), six
  frontend regression test additions, and two backend webhook test
  additions; updates Last Updated date
- TESTING_GUIDE.md: updates frontend count from 1496 to 1592, backend
  estimate to ~3010+, combined total to ~4600+; marks #710 and #726 as
  delivered in wave table; adds resolved entries to Key Gaps section;
  updates telemetry taxonomy note to delivered; appends three new
  coverage sections for webhook HMAC, SSRF/delivery reliability, and
  frontend regression wave
- MANUAL_TEST_CHECKLIST.md: adds auth-flow toast check (Section A),
  WIP-limit dedup check (Section B), manual card provenance empty
  state check (Section B), board header presence label check (Section
  B), and D2 router auth guard and workspace state section
- IMPLEMENTATION_MASTERPLAN.md: adds item 128 for the post-adversarial
  hardening wave with all 9 issues, fixes, and wave progress update
@Chris0Jeky Chris0Jeky merged commit 93e4052 into main Apr 4, 2026
19 of 21 checks passed
@Chris0Jeky Chris0Jeky deleted the fix/680-manual-card-empty-provenance branch April 4, 2026 02:43
@github-project-automation github-project-automation bot moved this from Pending to Done in Taskdeck Execution Apr 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

UX-24: Manual cards should treat missing capture provenance as an expected empty state

2 participants