docs(12-04): complete digest-interval + Sofort-Alert UI plan
This commit is contained in:
@@ -410,7 +410,7 @@ Plans:
|
|||||||
3. Activating a new search profile with many pre-existing historical matches does not flood the user with individual emails -- backfill is suppressed/batched on first activation, and a match already notified once (digest or instant) is never notified again for the same tender+profile pair (explicit matched-vs-notified state)
|
3. Activating a new search profile with many pre-existing historical matches does not flood the user with individual emails -- backfill is suppressed/batched on first activation, and a match already notified once (digest or instant) is never notified again for the same tender+profile pair (explicit matched-vs-notified state)
|
||||||
4. Notification emails are sent through the tenant's own SMTP configuration (reusing the DKV mail pattern), not a shared/global system mailer
|
4. Notification emails are sent through the tenant's own SMTP configuration (reusing the DKV mail pattern), not a shared/global system mailer
|
||||||
|
|
||||||
**Plans**: 3/4 plans executed
|
**Plans**: 4/4 plans executed
|
||||||
**UI hint**: yes
|
**UI hint**: yes
|
||||||
|
|
||||||
**Wave 1**
|
**Wave 1**
|
||||||
@@ -424,7 +424,7 @@ Plans:
|
|||||||
**Wave 3** *(blocked on Wave 2)*
|
**Wave 3** *(blocked on Wave 2)*
|
||||||
|
|
||||||
- [x] 12-03-PLAN.md — Sofort-Alerts am Poll-Tick + Ein-Mail-Garantie über beide Kanäle (NOTIFY-02/03)
|
- [x] 12-03-PLAN.md — Sofort-Alerts am Poll-Tick + Ein-Mail-Garantie über beide Kanäle (NOTIFY-02/03)
|
||||||
- [ ] 12-04-PLAN.md — Web-UI: Digest-Intervall + Sofort-Alert-Toggle, Pref-Backend (NOTIFY-01/02)
|
- [x] 12-04-PLAN.md — Web-UI: Digest-Intervall + Sofort-Alert-Toggle, Pref-Backend (NOTIFY-01/02)
|
||||||
|
|
||||||
### Phase 13: Scraping Adapters & Cross-Source Deduplication
|
### Phase 13: Scraping Adapters & Cross-Source Deduplication
|
||||||
|
|
||||||
@@ -476,6 +476,6 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
|
|||||||
| 9. Cert Manager Module | 6/6 | Complete | 2026-07-02 |
|
| 9. Cert Manager Module | 6/6 | Complete | 2026-07-02 |
|
||||||
| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 6/6 | Complete | 2026-07-21 |
|
| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 6/6 | Complete | 2026-07-21 |
|
||||||
| 11. Filter Engine, Results UI & Saved Searches | 6/6 | In Progress| |
|
| 11. Filter Engine, Results UI & Saved Searches | 6/6 | In Progress| |
|
||||||
| 12. Tender Notifications | 3/4 | In Progress| |
|
| 12. Tender Notifications | 4/4 | In Progress| |
|
||||||
| 13. Scraping Adapters & Cross-Source Deduplication | 0/TBD | Not started | - |
|
| 13. Scraping Adapters & Cross-Source Deduplication | 0/TBD | Not started | - |
|
||||||
| 14. RSS, Email-Alert Ingestion & Module Rollout | 0/TBD | Not started | - |
|
| 14. RSS, Email-Alert Ingestion & Module Rollout | 0/TBD | Not started | - |
|
||||||
|
|||||||
+11
-9
@@ -4,16 +4,16 @@ milestone: v1.1
|
|||||||
milestone_name: Ausschreibungs-Radar
|
milestone_name: Ausschreibungs-Radar
|
||||||
current_phase: 12
|
current_phase: 12
|
||||||
current_phase_name: tender-notifications
|
current_phase_name: tender-notifications
|
||||||
status: executing
|
status: verifying
|
||||||
stopped_at: Completed 12-03-PLAN.md
|
stopped_at: Completed 12-04-PLAN.md
|
||||||
last_updated: "2026-07-22T07:23:12.233Z"
|
last_updated: "2026-07-22T07:32:37.037Z"
|
||||||
last_activity: 2026-07-22
|
last_activity: 2026-07-22
|
||||||
last_activity_desc: Phase 12 execution started
|
last_activity_desc: Phase 12 execution started
|
||||||
progress:
|
progress:
|
||||||
total_phases: 12
|
total_phases: 12
|
||||||
completed_phases: 10
|
completed_phases: 11
|
||||||
total_plans: 56
|
total_plans: 56
|
||||||
completed_plans: 54
|
completed_plans: 55
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -29,10 +29,10 @@ See: .planning/PROJECT.md (updated 2026-07-17)
|
|||||||
|
|
||||||
Phase: 12 (tender-notifications) — EXECUTING
|
Phase: 12 (tender-notifications) — EXECUTING
|
||||||
Plan: 4 of 4
|
Plan: 4 of 4
|
||||||
Status: Ready to execute
|
Status: Phase complete — ready for verification
|
||||||
Last activity: 2026-07-22 — Phase 12 execution started
|
Last activity: 2026-07-22 — Phase 12 execution started
|
||||||
|
|
||||||
Progress: [██████████] 96%
|
Progress: [██████████] 98%
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
@@ -89,6 +89,7 @@ Progress: [██████████] 96%
|
|||||||
| Phase 12 P01 | 35min | 3 tasks | 7 files |
|
| Phase 12 P01 | 35min | 3 tasks | 7 files |
|
||||||
| Phase 12 P02 | 8min | 3 tasks | 5 files |
|
| Phase 12 P02 | 8min | 3 tasks | 5 files |
|
||||||
| Phase 12 P03 | 15min | 2 tasks | 3 files |
|
| Phase 12 P03 | 15min | 2 tasks | 3 files |
|
||||||
|
| Phase 12 P04 | 12min | 3 tasks | 10 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -189,6 +190,7 @@ Recent decisions affecting current work:
|
|||||||
- [Phase ?]: TenderMailService swallows missing-SmtpConfig and send-failure into a single boolean (never throws) so the digest cron gets one clean success signal for stamping notifiedAt
|
- [Phase ?]: TenderMailService swallows missing-SmtpConfig and send-failure into a single boolean (never throws) so the digest cron gets one clean success signal for stamping notifiedAt
|
||||||
- [Phase ?]: TenderDigestScheduler.runDigest(now) takes an injectable clock parameter for testable Monday-only weekly-digest gating
|
- [Phase ?]: TenderDigestScheduler.runDigest(now) takes an injectable clock parameter for testable Monday-only weekly-digest gating
|
||||||
- [Phase ?]: Instant-Dispatch filtert die bereits geladenen savedSearches (kein zweiter Query); notifiedAt/channel='instant' nur nach Erfolg gestempelt — identisches Gate wie Digest (D-06)
|
- [Phase ?]: Instant-Dispatch filtert die bereits geladenen savedSearches (kein zweiter Query); notifiedAt/channel='instant' nur nach Erfolg gestempelt — identisches Gate wie Digest (D-06)
|
||||||
|
- [Phase ?]: Digest-interval selector inline in settings/page.tsx (already 'use client'); instantAlert toggle uses plain checkbox for chip-based SavedSearchBar UI
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -226,7 +228,7 @@ Items acknowledged and carried forward from previous milestone close:
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-07-22T07:23:12.215Z
|
Last session: 2026-07-22T07:32:37.021Z
|
||||||
Stopped at: Completed 12-03-PLAN.md
|
Stopped at: Completed 12-04-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
|
Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
---
|
||||||
|
phase: 12-tender-notifications
|
||||||
|
plan: 04
|
||||||
|
subsystem: api+ui
|
||||||
|
tags: [nestjs, prisma, class-validator, nextjs, tender-radar, notifications]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 12-tender-notifications (12-01/12-02/12-03)
|
||||||
|
provides: "TenderNotificationPref + instantAlert schema fields, digest scheduler (daily default due-check), instant-dispatch mail path"
|
||||||
|
provides:
|
||||||
|
- "GET/PUT /modules/tender-radar/notification-pref (per-user digestInterval CRUD, IDOR-safe)"
|
||||||
|
- "instantAlert passthrough on saved-search create/update DTOs + service"
|
||||||
|
- "Web UI: digest-interval selector (settings page) + Sofort-Alert toggle per profile (SavedSearchBar)"
|
||||||
|
affects: [14-tender-i18n-and-admin-sources]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Per-user pref CRUD via upsert on @@unique(userId) — same scoping convention as TenderSavedSearchService/TenderTriageService, no forTenant()/RLS"
|
||||||
|
- "Route declared before @Get(':id') to avoid the NestJS route-order shadowing pitfall"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- apps/api/src/tenders/dto/notification-pref.dto.ts
|
||||||
|
- apps/api/src/tenders/tender-notification-pref.service.ts
|
||||||
|
- apps/api/src/tenders/tender-notification-pref.service.spec.ts
|
||||||
|
modified:
|
||||||
|
- apps/api/src/tenders/dto/saved-search.dto.ts
|
||||||
|
- apps/api/src/tenders/tender-saved-search.service.ts
|
||||||
|
- apps/api/src/tenders/tender-saved-search.service.spec.ts
|
||||||
|
- apps/api/src/tenders/tenders.controller.ts
|
||||||
|
- apps/api/src/tenders/tenders.controller.spec.ts
|
||||||
|
- apps/api/src/tenders/tenders.module.ts
|
||||||
|
- apps/web/src/lib/tender-radar-api.ts
|
||||||
|
- "apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx"
|
||||||
|
- "apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx"
|
||||||
|
- "apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx"
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Digest-interval selector kept inline in settings/page.tsx (already a 'use client' shell) rather than split into a separate component — no standalone unit-test coverage was called for in this plan for that piece"
|
||||||
|
- "instantAlert toggle uses a plain HTML checkbox with aria-label 'Sofort-Alert für {name}', not a shadcn switch, matching the existing minimal chip-based SavedSearchBar UI"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Notification-pref GET/PUT routes declared immediately after the saved-searches DELETE route and before @Get(':id') — same static-route-before-param-route convention as source-config/coverage/triage/saved-searches"
|
||||||
|
|
||||||
|
requirements-completed: [NOTIFY-01, NOTIFY-02]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "Per-user digest interval (Täglich/Wöchentlich/Aus) is readable/writable via GET/PUT /modules/tender-radar/notification-pref, defaults to 'daily', IDOR-safe"
|
||||||
|
requirement: "NOTIFY-01"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-notification-pref.service.spec.ts#TenderNotificationPrefService (4 tests)"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tenders.controller.spec.ts#GET/PUT notification-pref (NOTIFY-01, per-user, T-12-14)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "Digest-interval selector in the tender-radar settings page loads current value on mount and persists changes"
|
||||||
|
requirement: "NOTIFY-01"
|
||||||
|
verification: []
|
||||||
|
human_judgment: true
|
||||||
|
rationale: "UI persistence-across-reload behavior is the end-of-phase UAT item (settings page reload check) — not covered by an automated component test in this plan"
|
||||||
|
- id: D3
|
||||||
|
description: "Per-profile Sofort-Alert toggle (instantAlert) persists via the existing saved-search create/update path, default off"
|
||||||
|
requirement: "NOTIFY-02"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tender-saved-search.service.spec.ts#instantAlert passthrough (3 tests)"
|
||||||
|
status: pass
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx#Sofort-Alert toggle (2 tests)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "All notification-pref and saved-search routes derive userId/tenantId exclusively from extractTriageContext(req), never body/query (IDOR)"
|
||||||
|
requirement: "NOTIFY-01"
|
||||||
|
verification:
|
||||||
|
- kind: unit
|
||||||
|
ref: "apps/api/src/tenders/tenders.controller.spec.ts#derives userId from req.user ... never from a query param (V4/IDOR)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
|
||||||
|
duration: 12min
|
||||||
|
completed: 2026-07-22
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 12 Plan 04: Digest-Intervall + Sofort-Alert Bedienoberfläche Summary
|
||||||
|
|
||||||
|
**Per-user digest-interval CRUD (GET/PUT notification-pref, default 'daily') plus instantAlert passthrough on saved-search create/update — both wired into a settings-page selector and a per-profile SavedSearchBar toggle, closing the loop from 12-01/02/03's pipeline to a user-controllable UI.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~12 min
|
||||||
|
- **Completed:** 2026-07-22
|
||||||
|
- **Tasks:** 3/3 completed
|
||||||
|
- **Files modified:** 10 (3 created, 7 modified)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- `TenderNotificationPrefService` (per-user digestInterval CRUD, default `'daily'` when no row exists, upsert on `@@unique(userId)`) + `UpdateNotificationPrefDto` (`@IsIn(['daily','weekly','off'])`)
|
||||||
|
- `GET`/`PUT /modules/tender-radar/notification-pref`, declared before `@Get(':id')` (route-order pitfall avoided, regression test added)
|
||||||
|
- `instantAlert` now flows through `Create/UpdateSavedSearchDto` and `TenderSavedSearchService.create/update` (optional, `@IsBoolean`, falls back to the Prisma column default `false` when omitted)
|
||||||
|
- `tender-radar-api.ts`: `fetchNotificationPref`/`saveNotificationPref`, and `SavedSearch`/`CreateSavedSearchPayload`/`UpdateSavedSearchPayload` extended with `instantAlert`
|
||||||
|
- Settings page: "Benachrichtigungen" section with a Täglich/Wöchentlich/Aus select, loads via `fetchNotificationPref` on mount, saves via `saveNotificationPref` on change
|
||||||
|
- `SavedSearchBar`: per-profile Sofort-Alert checkbox (`aria-label="Sofort-Alert für {name}"`), reflects `profile.instantAlert`, calls `updateSavedSearch(id, { instantAlert })` + reloads on toggle
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
1. **Task 1: Backend — Pref-Service + Routen + instantAlert-Durchreichung** - `9e7ba53` (feat, TDD RED→GREEN)
|
||||||
|
2. **Task 2: Frontend API-Client — Pref-Funktionen + instantAlert in SavedSearch** - `73a7e49` (feat)
|
||||||
|
3. **Task 3: Frontend UI — Digest-Intervall-Auswahl + Sofort-Alert-Toggle** - `d60080e` (feat)
|
||||||
|
|
||||||
|
_Note: no separate metadata commit yet — this SUMMARY/STATE/ROADMAP commit follows._
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `apps/api/src/tenders/dto/notification-pref.dto.ts` - `UpdateNotificationPrefDto` (`@IsIn`)
|
||||||
|
- `apps/api/src/tenders/tender-notification-pref.service.ts` - per-user digestInterval CRUD, default `'daily'`, upsert on `userId`
|
||||||
|
- `apps/api/src/tenders/tender-notification-pref.service.spec.ts` - RED-first TDD spec (4 tests)
|
||||||
|
- `apps/api/src/tenders/dto/saved-search.dto.ts` - `instantAlert?: boolean` on both Create/Update DTOs
|
||||||
|
- `apps/api/src/tenders/tender-saved-search.service.ts` - passes `instantAlert` through create/update when supplied
|
||||||
|
- `apps/api/src/tenders/tender-saved-search.service.spec.ts` - 3 new instantAlert passthrough tests
|
||||||
|
- `apps/api/src/tenders/tenders.controller.ts` - `getNotificationPref`/`setNotificationPref` routes + constructor DI
|
||||||
|
- `apps/api/src/tenders/tenders.controller.spec.ts` - fake pref service added to all 20 controller instantiations + new route-order/IDOR tests
|
||||||
|
- `apps/api/src/tenders/tenders.module.ts` - registers `TenderNotificationPrefService`, doc comment updated
|
||||||
|
- `apps/web/src/lib/tender-radar-api.ts` - `NotificationPref` type + `fetchNotificationPref`/`saveNotificationPref`; `instantAlert` added to `SavedSearch`/payloads
|
||||||
|
- `apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx` - Benachrichtigungen section (digest-interval select)
|
||||||
|
- `apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.tsx` - per-profile Sofort-Alert checkbox
|
||||||
|
- `apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx` - checkbox-state + toggle-calls-updateSavedSearch tests
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- Kept the digest-interval selector inline in `settings/page.tsx` rather than extracting a separate `NotificationPrefForm.tsx` component — the page is already a `'use client'` shell (unlike `SourceConfigForm`'s admin-only split), and this plan's file list/test scope didn't call for standalone unit coverage of that piece.
|
||||||
|
- Used a plain checkbox (not a switch) for the Sofort-Alert toggle to match the existing minimal chip-based `SavedSearchBar` styling.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] Updated `tenders.controller.spec.ts` fakes for the new constructor parameter**
|
||||||
|
- **Found during:** Task 1 (adding `TenderNotificationPrefService` to `TendersController`'s constructor)
|
||||||
|
- **Issue:** Adding a 5th required constructor parameter broke all 20 existing `new TendersController(...)` call sites in the spec file (missing-argument TS errors)
|
||||||
|
- **Fix:** Added a `makeFakeNotificationPrefService()` helper and appended it as the 5th argument to every existing constructor call; also added two new tests (route-declaration-order guard + GET/PUT IDOR-scoping proof) for the new routes
|
||||||
|
- **Files modified:** `apps/api/src/tenders/tenders.controller.spec.ts`
|
||||||
|
- **Verification:** `npx tsc --noEmit` clean; full API test suite (19 files / 209 tests) green
|
||||||
|
- **Committed in:** `9e7ba53` (Task 1 commit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 1 auto-fixed (blocking — test compilation)
|
||||||
|
**Impact on plan:** Necessary to keep the existing test suite compiling and passing after the planned constructor change. No scope creep — this is the direct, unavoidable consequence of the plan's own "Injiziere TenderNotificationPrefService in den Controller-Konstruktor" instruction.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## IDOR Mitigation (T-12-14/15/16)
|
||||||
|
|
||||||
|
- `UpdateNotificationPrefDto` and both saved-search DTOs carry no `userId`/`tenantId` field — both routes derive them exclusively from `extractTriageContext(req)` (cookie-backed auth context), never from body/query.
|
||||||
|
- `TenderNotificationPrefService` scopes every read/write by `userId` via the `@@unique(userId)` constraint — a foreign `userId` can never read or overwrite another user's pref (proven in `tender-notification-pref.service.spec.ts`: "getForUser() is scoped strictly by userId").
|
||||||
|
- `digestInterval` is validated server-side to exactly `'daily'|'weekly'|'off'` via `@IsIn` (T-12-15) before it reaches the service/scheduler due-check logic.
|
||||||
|
- `instantAlert` validated via `@IsBoolean`; ownership of the saved-search profile is still enforced in `TenderSavedSearchService.update()` (pre-existing NotFound-collapse pattern, T-11-14) — unchanged by this plan.
|
||||||
|
- No new package installs; no new trust boundary beyond the two already-modeled routes (matches `12-04-PLAN.md`'s threat register — no `## Threat Flags` needed).
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required. Both `TenderNotificationPref.digestInterval` and `TenderSavedSearch.instantAlert` columns already existed in the local dev DB (seeded in Plan 12-01) — no migration needed for this plan.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- NOTIFY-01/02 are now fully wired end-to-end: user sets digest interval / Sofort-Alert in the web UI → the 12-01/02/03 matching/digest/instant pipeline reads and honors those settings.
|
||||||
|
- Remaining end-of-phase UAT (per `12-04-PLAN.md` `<verification>`): manually confirm in the running app that (a) setting the interval to "Wöchentlich" and reloading the settings page keeps the value, and (b) toggling a profile's Sofort-Alert and reloading keeps it on. Requires a docker rebuild/restart of the web+api containers to pick up these changes (per project convention, the user performs the rebuild, not this agent).
|
||||||
|
- Phase 12 is otherwise code-complete pending that UAT + docker rebuild (consistent with the phase-level pause note in `eb10bd3`).
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 12-tender-notifications*
|
||||||
|
*Completed: 2026-07-22*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All 12 listed files/paths verified present on disk; all 3 task commit hashes (`9e7ba53`, `73a7e49`, `d60080e`) verified present in git log.
|
||||||
Reference in New Issue
Block a user