Skip to content

Add adhoc gRPC/REST backend for flutter-adhoc (M3 group B, WIP) - #1074

Merged
renemadsen merged 36 commits into
stablefrom
feature/adhoc-backend
Jul 23, 2026
Merged

Add adhoc gRPC/REST backend for flutter-adhoc (M3 group B, WIP)#1074
renemadsen merged 36 commits into
stablefrom
feature/adhoc-backend

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Summary

  • Bump Microting.EformBackendConfigurationBase to 10.0.46 (adhoc entities; NOTE: restore fails until that version is published to nuget.org — the base repo PR must merge+tag first)
  • Protos/adhoc.proto wire contract + generated server stubs
  • BackendConfigurationAdhocService: task CRUD with server-side visibility predicates (37 integration tests)
  • Draft/WIP: B3-B7 (reference data, photos, gRPC/REST facades, registration) still to come; opened to run CI verification (local test runs are prohibited)

Spec/plan: flutter-adhoc repo docs/superpowers/plans/2026-07-21-flutter-adhoc-m3-backend-transport.md (group B)

🤖 Generated with Claude Code

renemadsen and others added 24 commits July 21, 2026 17:54
Microting.EformBackendConfigurationBase 10.0.46 (adhoc entities) declares
transitive dependencies on Microting.eForm >= 10.0.36 and
Microting.eFormApi.BasePn >= 10.0.30; bump the plugin's own direct
references to those two packages in lockstep to avoid a NU1605
package-downgrade restore error (both versions are already published on
nuget.org). 10.0.46 itself is not yet on nuget.org pending human release
approval - restored locally from a packed nupkg via an ad-hoc
--source flag (no nuget.config committed); see report for the exact
command.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Defines the AdhocGrpc service and message set for ad-hoc tasks, per
docs/superpowers/plans/2026-07-21-flutter-adhoc-m3-backend-transport.md
(Task B1). AdhocTask field numbers 1-26 match the plan's canonical field
mapping table 1:1. csharp_namespace is BackendConfiguration.Pn.Grpc.Adhoc;
the entity is named AdhocTaskEntity (base repo) precisely so the generated
AdhocTask C# type here stays collision-free. Registers Protos\adhoc.proto
in the csproj; dotnet build regenerates the C# stubs cleanly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…ates

Introduces the shared task CRUD + visibility service layer for ad-hoc
tasks (plan Task B2), consumed by both the mobile gRPC facade (B5) and
the dashboard REST facade (B6):

- Infrastructure/Models/Adhoc/AdhocTaskModel.cs: read/write models
  (AdhocTaskModel, AdhocTaskCreateModel, AdhocCommentModel,
  AdhocAssignmentLogModel, AdhocTagModel, AdhocAreaModel,
  AdhocWorkerModel, AdhocTaskFiltersModel) plus the TaskScopeFilter enum.
- Services/BackendConfigurationAdhocService: IBackendConfigurationAdhocService
  + implementation covering ListTasks/GetTask/CreateTask/UpdateTask/
  SetCompleted/Archive/Reopen/Delete/AddComment, each taking the
  caller's workerId explicitly plus an isAdmin bypass (per B6's REST
  caller-identity design) so the dashboard facade needs no signature
  rework later.
- Visibility (CanSee/MatchesScope) replicates
  lib/features/tasks/domain/task_visibility.dart's canSee/matchesScope
  exactly, including the property-access gate applying unconditionally
  before creator/everyone/assigned checks.
- Creator-only enforcement for update/archive/reopen/delete; soft-delete
  cascade over children (assignments, logs, comments, photos, tag joins)
  via PnBase.Delete; one AdhocTaskAssignmentLog row appended whenever an
  assignment delta changes; area-belongs-to-property validation on
  create/update; typed AdhocTaskNotFoundException/
  AdhocTaskUnauthorizedException for facades to map to NotFound/
  PermissionDenied.
- AdhocServiceTaskCrudTests.cs / AdhocServiceVisibilityTests.cs
  (BackendConfiguration.Pn.Integration.Test, Testcontainers MariaDB
  fixture) test-drive every behavioral rule from the plan, including a
  regression test for a property-access gate bug caught during
  self-review (CanSee must exclude even the creator once their
  PropertyWorker access is revoked - previously only enforced in
  ListTasks' bulk query, not on the single-task paths).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
… tags)

Extends BackendConfigurationAdhocService with ListProperties/ListAreas/
ListWorkers/ListTags/CreateTag/RenameTag/DeleteTag so the mobile/dashboard
task UI can populate its property/area/worker/tag pickers. Workers are
resolved from PropertyWorkers with display names looked up in the SDK's
Site table via IEFormCoreService, matching the access pattern
GrpcSiteResolver/EventsGrpcService already use. Tags are global (no owner)
or personal (OwnerWorkerId = caller); deleting a personal tag soft-deletes
its AdhocTaskTag joins too. 18 new integration tests in
AdhocServiceReferenceDataTests.cs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Adds SavePhoto/GetPhoto to BackendConfigurationAdhocService, following the
same S3+UploadedData pipeline as EventsGrpcService.UploadPhoto /
BackendConfigurationTaskManagementService.CreateTask (checksum, two-phase
UploadedData.FileName write, image/jpeg|jpg|png content-type validation).
Both methods gate on the same canSee predicate as GetTask/AddComment,
applied to the photo's owning task.

The actual S3 byte transfer is routed through a new IAdhocPhotoStorage seam
(AdhocPhotoStorage delegates to Core.PutFileToS3Storage/GetFileFromS3Storage
in production) because Core is a concrete SDK type with no public S3 test
double, and there is neither S3 nor a MinIO container in this repo's local
or CI test environment (confirmed: no such service in dotnet-core-pr.yml;
the Testcontainers-seeded 420_SDK.sql fixture ships s3Enabled=false with
placeholder credentials). Tests substitute FakeAdhocPhotoStorage, an
in-memory double, so the actual round-trip (and UpdateTask's PhotoIds
reconciliation against a photo created via SavePhoto) is exercised for
real rather than mocked away. 12 new integration tests in
AdhocServicePhotoTests.cs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
# Conflicts:
#	eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/BackendConfiguration.Pn.csproj
Maps every AdhocGrpc RPC (task CRUD, reference data, tags, meta-first
photo upload/download streams) onto IBackendConfigurationAdhocService,
following EventsGrpcService's conventions: GrpcSiteResolver-resolved
worker id, Unauthenticated on an unresolvable caller, UTC Timestamp
mapping, and typed service exceptions -> NotFound/PermissionDenied/
InvalidArgument RpcException status codes.

GetCurrentWorker's display_name has no home in the shared service (a
gRPC-only concept — REST/dashboard callers have no single SDK worker
identity), so IGrpcSiteResolver gains GetDisplayNameAsync(siteId),
keeping AdhocGrpcService's dependencies limited to fakeable interfaces
for mapping-only unit tests (BackendConfiguration.Pn.Test/GrpcServices/AdhocGrpcServiceMappingTests.cs).

Registers IBackendConfigurationAdhocService/IAdhocPhotoStorage in DI
and MapGrpcService<AdhocGrpcService>() in the endpoints block; no
nav-menu/claims change (deferred to M5).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Full REST route table over IBackendConfigurationAdhocService (task
CRUD, completed/archive/reopen, comments, reference data, tags CRUD,
multipart photo upload + streamed download), following
TaskManagementController's [Authorize] + try/catch -> OperationDataResult
convention. The index route applies area/status/tag(AND-OR)/search
filtering plus paging/sort in-memory over the caller's full visible
set, since the shared service's ListTasks only scopes by property
(mobile's need) — spec's dashboard table query shape (§5.2).

Dashboard callers have no per-request SDK worker identity, so every
route uses a synthetic workerId=0 plus an isAdmin flag resolved once
from IUserService.IsAdmin(): true bypasses the shared service's
visibility predicates entirely (admin sees/mutates every task for the
customer); false naturally denies everything, since worker 0 has no
PropertyWorker row and is never a task's creator/assignee — no
separate non-admin code path needed.

Extends the isAdmin bypass to the three service methods B2/B4 left
without one (CreateTask, SavePhoto, GetPhoto) so the dashboard can
create tasks and manage photos on the customer's behalf; regression
tests added to AdhocServiceTaskCrudTests.cs/AdhocServicePhotoTests.cs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
SavePhoto/GetPhoto NRE'd in AdhocServicePhotoTests because CreateSut()
wired Substitute.For<IEFormCoreService>() unstubbed, so
coreHelper.GetCore() returned a null Core and SavePhoto's
core.DbContextHelper.GetDbContext() dereferenced it. Wire the
substitute to a real Core from TestBaseSetup.GetCore(), matching the
pattern already used by EventDeployServiceTest and
CalendarMultiLanguageTitleDescriptionTests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
The Adhoc* tables were added after SQL/420_eform-backend-configuration-plugin.sql
was last regenerated, so TestBaseSetup.Setup's DROP+CREATE pass (which resets
every other table) never touches them. Rows accumulated across every test in
AdhocServiceReferenceDataTests, so ListAreas_ReturnsAreasForProperty and
ListTags_ReturnsGlobalAndOwnTags_ButNotOthersPersonalTags picked up leftover
rows from earlier tests whenever a freshly-reset auto-increment id happened
to collide with an old, orphaned one.

Add a [SetUp] cleanup pass (RemoveRange in FK-safe order) to both Adhoc test
fixtures, mirroring the existing convention used by CalendarUpdateTaskScopeTests,
CalendarTaskListIndexTest and TaskListBatchWorkersTest for tables in the same
situation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Adds the "Enable adhoc" plugin permission (ClaimName "adhoc_enable" as a
plain string literal, with a TODO to upstream it to
BackendConfigurationClaims once a base-repo release train is open - none
is open right now, and the 10.0.46 NuGet publish this branch already
depends on is itself still a pending-human step) and a new top-level nav
item ("Adhoc overview" / Danish "Adhoc overblik") linking to
/plugins/backend-configuration-pn/adhoc-tasks, deliberately distinct from
the existing Danish-labeled "Task management" item per the M5 plan's
namespace-collision constraint.

Verified nav-menu propagation: PluginMenuItemsLoader.Load (host repo,
SimpleLinkLoader) unconditionally appends new MenuItem rows and is only
invoked when a plugin transitions Disabled -> Enabled (Program.cs /
PluginsManagementService), never on every restart. For this already-
enabled plugin, the new item will not appear in an existing install until
an admin toggles it off/on in Plugin settings - documented for T3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
IBackendConfigurationAdhocService gains IndexTasks (dashboard table query:
property/area/tag(AND-OR)/search/property-access/visibility all pushed
into SQL against BackendConfigurationPnDbContext, status counts computed
against every filter except Status itself, only the current page mapped
to the full AdhocTaskModel shape) and ListHistory (Historik timeline:
AdhocTaskHistoryEventModel rows derived from AdhocTaskEntity's own scalar
timestamps plus AdhocTaskAssignmentLog/AdhocTaskComment rows - not
version-table diffing).

Addresses B6's own "in-memory filtering/paging is a scale risk" concern:
AdhocController.Index now calls IndexTasks directly, and the old
ApplyFiltersAndPaging/ApplySort private methods (which ran over the
caller's ENTIRE visible task set, each fully hydrated with tags/photos/
assignment log/comments, before any area/status/tag/search/paging filter
was applied) are removed.

VERIFY-AT-EXECUTION resolutions (full detail in
flutter-adhoc/.superpowers/sdd/m5-p1-p3-report.md):
- History event derivation: reading the mockup's actual renderHistoryTable/
  getHistoryFilteredSorted showed Historik is a per-task listing of
  currently completed/archived tasks, not a full audit-log timeline - a
  reopened task simply returns to the open bucket and disappears from
  Historik entirely. Scalar-timestamps + assignment-log + comments (the
  plan's own stated default) covers this exactly; version-table diffing
  is not needed for v1. "reopened" is intentionally not an emitted event
  type - documented as an accepted limitation, not an oversight.
- Index in-memory-filtering concern: resolved by pushing every
  AdhocTaskFiltersModel filter into SQL via IQueryable composition
  (correlated EXISTS subqueries for tag/assignment checks), keeping
  ListTasks itself untouched for mobile's gRPC scope-based needs.

New tests: AdhocServiceIndexTests.cs (12), AdhocServiceHistoryTests.cs (14).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…dashboard

IBackendConfigurationAdhocService gains CopyTask(workerId, isAdmin, taskId,
includeComments): duplicates a task's core fields/tags/assignments (with a
fresh assignment-log entry) under the copier as the new creator, always
starting uncompleted/unarchived regardless of the source's state; photos
are duplicated as new AdhocTaskPhoto rows sharing the source's
UploadedDataId (verified safe - no FK cascade to UploadedData, GetPhoto
asserts no single-owner exclusivity); comments are only duplicated when
includeComments is true, preserving AuthorWorkerId/text verbatim
(CreatedAt itself cannot survive the shared PnBase.Create helper, which
always stamps "now" - a documented v1 limitation, not a fabricated
"copied" annotation).

SetCompleted gains an optional completedByWorkerId (the dashboard's
"Vælg hvem der udfører opgaven" performer select): when set, that worker
- not the caller - is stamped as CompletedByWorkerId, after validating
they have a PropertyWorker row for the task's property.

CreateTag gains an optional isAdmin: true creates a global tag
(OwnerWorkerId = null) instead of a personal one, so a dashboard admin can
curate shared tags; mobile's gRPC CreateTag (always isAdmin=false) is
unchanged.

AdhocController: new POST {id}/copy route, POST {id}/completed accepts
completedByWorkerId in its body, POST tags passes IsAdmin through for the
global-tag path.

New tests: AdhocServiceCopyTests.cs (10); extended
AdhocServiceTaskCrudTests.cs (2, performer completion) and
AdhocServiceReferenceDataTests.cs (1, admin-global tag).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
New adhocState slice (state/adhoc/) for the "Adhoc overblik" dashboard
module (M5/F2): AdhocFiltrationModel (property/area/status/tagIds+
tagLogic OG-ELLER/search, initial status 'open'), AdhocHistoryFiltrationModel
(period preset 30d/60d/90d/6m/12m/24m/custom + property/area/AND-only
tagIds), pagination (initial sort CreatedAt desc) and hiddenColumns
(column-picker persistence). Registered as adhocState in
backendConfigurationPn's StoreModule.forFeature, typed on
BackendConfigurationState, actions/reducer/selectors following the
task-management slice's exact shape (createAction/createReducer/
createSelector, spread-merge on partial updates, hiddenColumns replaced
wholesale not merged).

Authored (not run per repo convention - jest runs from the host
frontend only) adhoc.reducer.spec.ts: initial-state defaults, each
action's merge semantics, and that switching one filter/sub-state
doesn't disturb sibling state.

Sequencing note: built before the F1 (models) commit, reversing the
plan's listed F1-before-F2 order. AdhocState has zero dependency on
the F1 models, while F1's adhoc-task-request.model.ts imports
AdhocFiltrationModel from this slice (mirroring how
task-management-request.model.ts imports TaskManagementFiltrationModel
from state/task-management) - building state first keeps every
individual pushed commit on this branch independently compiling, since
CI's Angular build is whole-program and would break on the forward
reference otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
New models/adhoc/ TS interfaces for the "Adhoc overblik" dashboard
(M5/F1), verified field-for-field against the merged M3-B2/M5-P2/P3
canonical C# models (Infrastructure/Models/Adhoc/*.cs), not the plan's
own projection: AdhocTaskModel carries only ids (propertyId, areaId,
createdByWorkerId, assignedWorkerIds, tagIds) - there are no *Name
projections on the wire model, so the table/drawer resolve display
names client-side from the reference-data endpoints. Also:
AdhocTaskPhotoModel/AdhocCommentModel/AdhocAssignmentLogModel (nested
value objects), AdhocTaskCreateModel, AdhocTaskFiltersModel +
AdhocTaskStatusFilter enum (wire-level index filters),
AdhocTaskIndexResultModel (Paged<AdhocTaskModel> + open/completed/
archived counts), AdhocTaskRequestModel (UI-facing filters+pagination
bundle, shape parity with task-management-request.model.ts),
AdhocTagModel/AdhocTagCreateModel, AdhocAreaModel, AdhocPropertyModel,
AdhocWorkerModel, AdhocTaskHistoryEventModel + AdhocHistoryFiltersModel
+ AdhocHistoryRequestModel, and the small REST-only mutation bodies
(AdhocSetCompletedModel, AdhocCommentCreateModel, AdhocCopyTaskModel).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
New BackendConfigurationPnAdhocService (M5/F3), @Injectable providedIn
root + ApiBaseService, wrapping every route on the merged AdhocController
(M3-B6, extended M5-P2/P3): task CRUD (getTasks/getTask/createTask/
updateTask/deleteTask), completion/archive/reopen (setCompleted with
the dashboard-only completedByWorkerId performer select, archiveTask,
reopenTask), copyTask, addComment, history (getHistory), reference
data (getProperties/getAreas/getWorkers), tag CRUD (getTags/createTag/
renameTag/deleteTag), and photos (uploadPhoto via postFormData mirroring
BackendConfigurationPnCalendarFilesService's {file} pattern, photoUrl
builder + getPhotoBlob for authenticated fetches). Every route spelling
verified against the actual controller source, not the plan's
projection.

Authored (not run per repo convention) backend-configuration-pn-adhoc.
service.spec.ts: spies ApiBaseService and asserts URL + body per
method, following backend-configuration-pn-calendar-files.service.spec's
pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
New modules/adhoc/ (M5/F4): AdhocModule (import set copied from
task-management.module.ts for the F5-F8 follow-ups still to land) +
AdhocRouting (single '' route to AdhocContainerComponent for now -
child routes for the table view and Historik are added by F5/F8 once
those components exist) + AdhocContainerComponent, the dashboard's top
bar: page title resolved via AppMenuStateService.getTitleByUrl (same
pattern as TaskManagementContainerComponent), the Overblik/Historik
segment toggle (E2E ids backend-configuration-pn-adhoc-view-list/
-view-history per the plan's distinct-namespace rule) and the "Ny
opgave" button (backend-configuration-pn-adhoc-new-task; opens the
drawer once F7 lands).

Wired into the plugin shell: lazy 'adhoc-tasks' child route in
backend-configuration-pn.routing.ts gated by a new PermissionGuard
requiring BackendConfigurationPnClaims.enableAdhoc ('adhoc_enable',
matching the P1 seed's claim string). i18n additions to da.ts/enUS.ts
only (enUS values = key text, matching this repo's established
convention - other locale files fall back to the key itself and are
not touched). Reused existing keys instead of duplicating where the
repo already had an exact-meaning entry (Task, Assigned to, Completed
by, Urgent, Copy task, Complete task) - the latter two exist with
wording that differs slightly from the mockup ("Kopiér opgave"/
"Færdiggør opgaven" vs the plan's "Kopier opgave"/"Udfør opgave");
flagged for F7/F8 to decide whether to reuse as-is or introduce
differently-worded keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
AdhocStateService (F5) composes the wire-level AdhocTaskFiltersModel from
the adhocState store slice (status string -> AdhocTaskStatusFilter enum,
tagLogic -> tagsMatchAll, pageIndex -> 1-based pageNumber) and owns the
reference-data caches the table needs: AdhocTaskModel carries ids only (no
propertyName/areaName/createdByName/tag or worker names - confirmed against
the merged AdhocTaskModel.cs), so properties/tags are loaded once (global)
and areas/workers are loaded lazily per propertyId and cached for the
container's lifetime. Pure id->name resolvers + deadline-band/assignedTo
display helpers live in adhoc-display.util.ts (band colors match the plan:
overdue #b3261e, today #ef6c00, 1-5d #f9a825, 6+d #2e7d32 - banding formula
verified against the mockup's own deadlineVisual()).

AdhocTableComponent: mtx-grid with Task/Content/Property/Area/Tags/Created
by/Assigned to/Last deadline/Completed by/Created/Status/Actions columns,
a column picker (disabled outside the Åbne status per the mockup), and a
row action menu (Vis/Rediger/Kopier/Slet - reusing existing 'Copy task'/
'Delete task' i18n keys). Row menu handlers and the "Complete task" trigger
on open rows' status cell are stubbed pending the drawer (F7) and modals
(F8), matching F4's own stub pattern for "Ny opgave".

AdhocContainerComponent now orchestrates the Overblik view directly (table
+ eform-pagination), embedded in its own template rather than as a routed
child - only Historik (F8) will be an actual nested route through the
existing <router-outlet>.

Specs authored, not run (jest runs from the host frontend only): pure-
function deadline-banding/assignedTo tests, and a spy-store/spy-service
AdhocStateService spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
AdhocFiltersComponent (M5/F6): tag filter popup (checkbox rows + OG/ELLER
toggle mapping to AdhocTaskFiltersModel.tagsMatchAll, inline tag
create/delete against the F3 tag endpoints, refreshing AdhocStateService's
tag cache on success), a 500ms-debounced search box, a property select
(mtx-select, no counts per the mockup - a deliberate deviation from the
mobile app's property list), a dependent area select (reloaded on property
change, cleared when the property is cleared), and a status select whose
option labels are built from the P2 index response's per-status counts
("Åbne (12)"/"Løste (4)"/"Arkiverede (1)").

EformSharedTagsModule's EformsTagsComponent was evaluated per the plan's
own VERIFY-AT-EXECUTION note and doesn't fit: it drives a flat tag-CRUD
dialog against the global EformTagService with no AND/OR filter semantics
and no "selected for this filter" concept - a small custom popup is
simpler and correct here, so no new module dependency was added.

Every mutation goes through AdhocStateService.updateFilters, which the
container already reloads the table on (F5's filters-selector
subscription) - no new wiring needed in the container beyond passing
`counts` down.

Spec authored, not run (jest runs from the host frontend only): spy-service
tests for tag select/toggle/create/delete, status option labels, and the
property->area cascade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…tion form

AdhocTaskDrawerComponent (M5/F7): a right-anchored, full-height MatDialog
(position {top:'0', right:'0'}, height 100vh - ViewEncapsulation.None so
the stylesheet can reach the CDK overlay pane via the `adhoc-drawer-panel`
panelClass) with three modes - create ("Ny opgave"), view (read-only, no
action buttons, close via ×) and edit ("Rediger opgave" - disabled form
fields drive the view-mode readonly state via `this.form.disable()`).

Three mat-expansion-panel sections mirror the mobile form/mockup
`#ny-opgave-drawer`: "Ejendom og opgave" (property/area/title/description/
urgent/tags with inline create/pill-deselect/comments read-only + "Ny
kommentar" in edit mode/photos with thumbnails, upload, ng-gallery
lightbox and reconciliation-by-photoIds-on-update per M3-B4); "Tildel til
personer" (worker chips, Kun tildelte/Alle executionRule toggle - NO teams
- and a read-only assignment-change log); "Udfør senest og visning første
gang" (deadline + reminder + weekday-repeat, visibleFrom + reminder, both
reminder times editable, default 08:00 matching the mockup).

Create flow: createTask -> sequential (concatMap) uploadPhoto for every
queued file -> dialogRef.close(true). Edit flow: updateTask (server-side
photo reconciliation) -> close(true). AdhocContainerComponent now opens
this dialog for "Ny opgave"/Vis/Rediger and reloads the table whenever the
drawer closes with a truthy result.

Spec authored, not run (jest runs from the host frontend only): create/
view/edit mode defaults, tag/worker toggle + executionRule-clears-
assignees logic, photo removal reflected in the saved photoIds, and the
createTask/updateTask/addComment wire calls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
AdhocDeleteModalComponent (pattern: task-management-delete-modal),
AdhocCopyModalComponent (Annuller/Nej-uden-kommentarer/Ja-med-kommentarer ->
copyTask(id, includeComments) -> closes with the new copy so the caller
opens it straight into the edit drawer, mockup behavior) and
AdhocCompleteModalComponent ("Udfør opgave": performer select from the
task's property workers -> setCompleted(id, true, completedByWorkerId),
success toast via ToastrService - no plugin under plugins/modules uses
MatSnackBar, confirmed by repo-wide grep - and NO PIN field, a deviation
from the mockup's #udfør-modal noted here). All three only need
`{id, title}` (+ `propertyId` for complete), not a full AdhocTaskModel, so
both the table (F5, full model in hand) and the Historik timeline (only
`taskId`/`taskTitle` on its event rows) can open them identically.

AdhocContainerComponent now wires Kopier/Slet/Udfør for real (previously
F7 stubs) and recognizes the drawer's new `{action:'complete', task}`
close-result shape (the drawer's own "Udfør opgave" button, added here)
to chain the complete modal on top - satisfies "triggered from the status
cell on open rows AND from the drawer" without the drawer component
needing to know AdhocCompleteModalComponent exists.

AdhocHistoryComponent (`history` child route, now registered in
adhoc.routing.ts): period chips (30d/60d/90d/6m/12m/24m + custom range,
resolved client-side into AdhocHistoryFiltersModel.dateFrom/dateTo),
collapsible property/area/AND-only-tags filter details, and a
day-grouped timeline of AdhocTaskHistoryEventModel rows. Unlike the table
(F5), no id->name resolution is needed here - the history event model
already carries taskTitle/propertyName/areaName/actorName/tagNames as
plain strings. Row click fetches the full task (getTask) and opens the
drawer in view mode; row menu offers Kopier (same modal, opens the copy in
edit mode), Arkiver (only when completed and not yet archived), Slet.

AdhocModule is now feature-complete for M5's F4-F8 dashboard UI scope.

Specs authored, not run (jest runs from the host frontend only): each
modal's success/cancel/failure paths, the history component's period-range
resolution and day-grouping, and the drawer's new canComplete/
onCompleteTask behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Page object (BackendConfigurationAdhoc.page.ts, pattern:
BackendConfigurationTaskTracker.page.ts) covering the Overblik table +
toolbar filters (F5/F6), the "Ny opgave"/"Vis opgave" drawer (F7), the
delete/copy/complete modals and Historik tab (F8) - every selector grepped
against the actual merged F4-F8 component templates, not invented (mtx-grid
`.mat-column-<field>` cells, row mat-menu `.adhocViewBtn`/`.adhocEditBtn`/
`.adhocCopyBtn`/`.adhocDeleteBtn` classes - no archive action in the table,
only in Historik's `adhocHistoryArchiveBtn-{taskId}`, reactive-form fields
via their literal `formcontrolname` DOM attribute per
n/calendar-default-board.spec.ts's established convention, collapsed
mat-expansion-panel sections in the drawer needing an explicit header click
before their fields are interactable).

Both `x` and `y` are already taken in this plugin's own shard matrix
(pn-playwright-test, dotnet-core-pr.yml/dotnet-core-master.yml) - used the
next free letter, `z`.

Three specs under `z/`, each `test.describe.serial` with its own UI-seeded
property/worker/tag (shard z has no `420_SDK.sql` dump - only shard `a`
does - so, like every other non-`a` shard, it starts from an empty DB):

- `adhoc-table-filter.spec.ts` (flow 1): two properties + one task each,
  then search/property/status filter narrowing and a column-picker toggle
  asserting the header disappears.
- `adhoc-create-task-drawer.spec.ts` (flow 2): fills all three drawer
  sections (property, title, urgent, inline tag create+auto-select, worker
  assign via the assignment section's `mat-checkbox` list, executionRule,
  deadline via the shared `selectDateOnNewDatePicker` helper) and asserts
  the saved row's Haster badge, tag chip, "Tildelt til" worker and
  "Sidste frist" deadline bar.
- `adhoc-complete-archive.spec.ts` (flow 3): completes with a selected
  performer (moves off Åben, appears under Løste with Udført af populated),
  archives from the Historik row menu (asserts under Arkiverede), deletes
  from the table row menu (asserts gone).

Runs as admin@admin.com throughout - the `adhoc-tasks` route's
PermissionGuard/`adhoc_enable` gate relies on the same convention every
other PermissionGuard-gated route in this plugin's existing shard suite
already relies on (no per-group permission grant before navigating); the
P1-documented "plugin off/on toggle" concern for existing installs does not
apply to a fresh CI shard container, since the workflow's own
`EformPlugins Status=2` update + docker restart (for matrix.test != 'a') IS
that transition on a brand-new database.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
devinstall.sh/.py copied the Angular module, C# plugin, and wdio/cypress
e2e assets into the host frontend but never `playwright/` - add the same
rm+cp pair for `playwright/e2e/plugins/backend-configuration-pn` (mirroring
the CI "Copy dependencies" step) plus a `playwright.config.ts` copy, since
neither existed in the host checkout for a plugin dev-install session to
pick up the new adhoc dashboard shard.

devgetchanges.sh/.py (the reverse direction, pulling live-edited e2e specs
from the host frontend back into this repo) already mirrors the cypress
e2e assets the same way - added the matching reverse copy for the
playwright plugin directory. `playwright.config.ts` is intentionally NOT
reverse-synced, consistent with the existing convention of never pulling
back shared/global config (no wdio or cypress top-level config is
reverse-synced either, only the plugin's own spec content).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
… items array

Two independent CI-only bugs surfaced by shard z's first run (PR #1074):

1. `BackendConfigurationAdhocPage.searchInput()` located `#search`
   unscoped, which is a Playwright strict-mode violation — the app
   shell's own left-nav has an unrelated `<a id="search">` (entity-search
   link) sharing that literal id. Scoped the locator under `#main-list-view`.

2. `AdhocFiltersComponent.statusOptions` was a plain getter bound via
   `[items]="statusOptions"`, so it handed ng-select a brand-new array
   reference on nearly every change-detection tick (not just when
   `counts` actually changed). ng-select's own `ngOnChanges` treats any
   `items` reference change as a full items-list reset, which can null
   out an in-flight option click before its select handler commits —
   reproduced exactly by the complete-archive spec: selecting "Løste"
   left the status dropdown open with the typed filter text still
   showing and the table stuck on the (stale) "Åben" result set. Memoized
   `statusOptions` into a field recomputed only from `ngOnChanges` when
   `counts` changes, matching the updated unit spec.

CI evidence: gh run 29987305654 log + playwright-report-z artifact
(error-context.md snapshots showed the open-panel/no-op-selection state
directly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@renemadsen renemadsen closed this Jul 23, 2026
@renemadsen renemadsen reopened this Jul 23, 2026
…tFilters

Round-2 CI run (29990266498) cleared both round-1 failures (10/12 passed)
and surfaced a new one, the first-ever exercise of the Historik tab in
this shard: "archive from the Historik row menu" timed out waiting for
the just-created/completed task's history row - the table rendered
"Ingen opgaver fundet" and none of the period-preset chips showed as
selected (error-context.md snapshot).

Root cause: `currentFilters` is populated by a fresh, component-local
`store.select(selectAdhocHistoryFilters)` subscription set up inside this
component's own `ngOnInit`, but the very next line already reads
`this.currentFilters.propertyId` unguarded - unlike this exact same field's
`currentFilters?.` optional-chaining everywhere it's read in the template.
That asymmetry (defensive in the template, not in the component's own
code) left `ngOnInit`'s `updateTable()` call - and therefore the very
first `getHistory()` request - dependent on the store's first emission
having already landed synchronously; the existing unit spec already
implicitly needed a real default too (spreads `component.currentFilters`
before any lifecycle hook runs). Defaulted the field to
`adhocInitialState.historyFilters` so a fresh Historik visit never
silently no-ops its own initial fetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@renemadsen renemadsen closed this Jul 23, 2026
@renemadsen renemadsen reopened this Jul 23, 2026
Round 2's fix (fff28c4) produced a byte-identical failure screenshot on
rerun (same "Ingen opgaver fundet" empty Historik table) - re-analysis
shows that fix was provably a no-op for this bug: AdhocHistoryComponent's
store subscription reassigns `this.currentFilters` synchronously on the
very first line of ngOnInit, before anything else reads it, so a
component-property default can never have been the difference-maker here.

AdhocController.HistoryIndex wraps every failure into a caught
`OperationDataResult(false, "...: " + e.Message)` that's sent to Sentry,
never to the console - so a genuine backend exception for this exact
scenario (task created by the DashboardWorkerId=0 admin sentinel,
completed by a real device-user performer, viewed via Historik for the
first time in any shard) would be completely invisible in CI's ASP.NET
stdout log, which is what both prior rounds' log archaeology actually hit.

Instead of another blind guess, `goToHistory()` now awaits and logs the
real `history/index` response (status/success/message/total/entities) -
mirrors BackendConfigurationPropertyWorkers.page.ts's existing
create-device-user logging pattern already used successfully elsewhere in
this suite. Next run's CI log will show whether the backend genuinely
returns zero events or a swallowed server error, closing this out
precisely either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
renemadsen and others added 10 commits July 23, 2026 11:49
Round 3's diagnostic (ec91ec4) confirmed the real backend response for
"archive from the Historik row menu": status=200, success=true, total=2,
entities=2 - the fetch was correct all along. The empty "Ingen opgaver
fundet" render was a pure frontend bug: `groupedEvents` called
`.slice(0, 10)` on `event.occurredAt` assuming a raw string, but the host
frontend's global `DateInterceptor` (date.interceptor.ts) recursively
converts every ISO-datetime string in every HTTP response body into a real
`Date` before it reaches any component - `Date` has no `.slice()`, so the
getter threw on every change-detection pass once the 2 real events landed
in `this.events`, aborting each render and leaving the view stuck on its
initial (pre-fetch) empty-state paint forever after.

`groupedEvents` now branches on `instanceof Date` (formatting via
date-fns, already imported) vs. falling back to the previous string
`.slice()` path; `AdhocTaskHistoryEventModel.occurredAt`/`lastCommentAt`
widened to `string | Date` to reflect this same reality already true of
other interceptor-touched fields elsewhere in the app (e.g.
`DocumentModel.endDate`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w menus can open

Verified live against the local stack (create → complete → Historik →
archive → Arkiverede → delete, zero console errors): after the Date-
occurredAt fix (b7dadb9) the Historik rows render, but the row-menu
mat-menu could not open at all — clicking the more_vert trigger produced
an empty cdk-overlay every time. Cause: `groupedEvents` was an unmemoized
getter returning brand-new group objects on every change-detection pass,
so the template's identity-diffed *ngFor destroyed and recreated every
row each pass, tearing down the [matMenuTriggerFor] anchor the moment its
menu tried to open. The CI spec's very next step after its current
failure point (`archiveFromHistory`) waits for .mat-mdc-menu-panel, so
shard z would have stayed red even with b7dadb9 alone.

groupedEvents now memoizes on the events array's identity (the spec's
direct `component.events = [...]` assignments still recompute), giving
*ngFor stable identities across passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…classes, no bespoke pills

Rene asked for certainty that the adhoc dashboard doesn't invent new
button designs. A read-only audit against every sibling module in
backend-configuration-pn and the host frontend's styles.scss found the
module conformant on the header card, create button, mtx-grid, chips,
row menus and delete modal, but flagged bare variant-less btn-secondary
toggle pills (view toggle, Historik period chips, Or/And tag logic,
drawer execution rule) - a construct no sibling uses, and one the host
styles.scss explicitly warns falls back to the browser-default outset
border - plus mat-raised-button Cancel/Save in the create/edit drawer
where every sibling create/edit modal uses btn-cancel + btn-primary.

All toggles now render active = btn-primary btn-primary--icon-left
(filled pill), inactive = btn-secondary btn-secondary--icon-left (the
documented outlined sibling variant) - zero new CSS, ids unchanged, the
.active marker class kept. Drawer footer switched to btn-cancel +
btn-primary btn-primary--icon-left with new adhocDrawerSaveBtn/
adhocDrawerCancelBtn ids (page object's color="primary" locator updated
- that attribute is gone). Verified live at :4200 on Overblik, Historik,
and the drawer: filled/outlined states match Ny opgave and the
surrounding platform buttons in both states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
Dashboard-created tasks are stamped CreatedByWorkerId = 0 and the REST
controller maps every authenticated web user to DashboardWorkerId = 0, so
RequireCreator's plain equality check let ANY non-admin web user pass the
creator gate on every dashboard-created task (update/archive/reopen/delete).
Worker id 0 now never satisfies a creator check unless the caller is admin;
admins (worker 0 + isAdmin) keep access via the existing early return, and
gRPC callers (real positive site ids) are unaffected.

Tests: non-admin worker-0 denied Update/Archive/Reopen/Delete on a
worker-0-created task; admin worker-0 still allowed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…n-admin REST identity

RenameTag/DeleteTag had no isAdmin parameter and LoadOwnTagOrThrowAsync
required OwnerWorkerId == workerId, so admin-created GLOBAL tags
(OwnerWorkerId = null) could never be renamed or deleted by anyone, and all
non-admin REST callers shared pseudo-identity 0. IsAdmin is now threaded
through the controller and interface into RenameTag/DeleteTag: admins may
rename/delete any tag (notably global ones), while the non-admin REST
pseudo-identity (workerId 0, isAdmin false) is denied tag create/rename/
delete outright - identity 0 owns nothing (same principle as C1). RenameTag
also stops hardcoding IsUserTag = true (wrong for a renamed global tag).
Mobile gRPC tag semantics are unchanged (real site ids, default isAdmin).

Tests: admin renames/deletes a global tag; non-admin worker-0 denied
create/rename/delete, including on a tag stamped OwnerWorkerId = 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
… past 1 are reachable

AdhocContainerComponent.updateTable() consumed entities and the three
status counts but discarded data.model.total, so the pagination slice's
total stayed 0 and eform-pagination's [length] never allowed navigating
past page 1. AdhocStateService.getTasks() now taps successful responses
and dispatches adhocUpdatePagination with the fetched total (the ngrx
equivalent of Historik's local `total` field); the container's
[pagination] binding picks it up through the existing selector
subscription. No re-fetch loop: the container only re-fetches on filter
changes and explicit pagination events.

Spec: getTasks dispatches '[Adhoc] Update pagination' with the fetched
total on success, and dispatches nothing on failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
The Historik timeline renders one row per EVENT, but the menu trigger and
menu item ids were keyed by taskId alone - a multi-event task (created +
assigned + completed + ...) duplicated the same DOM id across rows. Ids now
append eventType plus group/row indices AFTER the existing prefix+taskId
(adhocHistoryActionMenu-{taskId}-{eventType}-{gi}-{ei}; eventType alone is
not sufficient since a task can emit several 'assigned' events). The
adhocHistoryActionMenu-/adhocHistoryCopyBtn-/adhocHistoryArchiveBtn-/
adhocHistoryDeleteBtn- prefixes are preserved; the shard-z page object only
matches these via row-scoped id^= prefix locators, so no locator changes
are needed (doc comments updated to match).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
…for admin, drop e2e diag logging

(a) IndexTasks/ListHistory (the paged REST paths; mobile gRPC uses the
unpaged ListTasks) now clamp caller-supplied PageSize to 1-100 via a shared
ClampPageSize helper (non-positive still defaults to 25), so a single
request can never hydrate an unbounded page. Test: 105 tasks, PageSize
1000 -> 100 entities, total 105.

(b) A dashboard admin (synthetic workerId 0) completing a task WITHOUT
selecting a performer now stamps CompletedByWorkerId = null instead of 0
(the entity column is a nullable int?; all readers already handle null).
Worker 0 is not a real SDK site, so nothing should be recorded as the
performer. Test: SetCompleted(0, ..., isAdmin: true) with no performer
yields CompletedByWorkerId null.

(c) BackendConfigurationAdhoc.page.ts goToHistory drops the diagnostic
response-body logging left by diag commit ec91ec4; the waitForResponse on
history/index is kept so callers still see a populated timeline rather than
racing the fetch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
The drawer's three sections were mat-expansion-panels rendered as
floating rounded cards - a panel style used nowhere else in the plugin.
The calendar module's create/edit form (task-create-edit-modal) is the
established design: flat always-visible rows led by a muted 20px icon
(`gcal-row` / `gcal-icon` / `gcal-row-content`), hairline separators
between logical blocks, no expansion panels or card chrome.

- adhoc-task-drawer.component.html: drop mat-accordion/mat-expansion-
  panel; the three sections become plain always-visible `gcal-section`
  divs of icon-led rows (business/place/notes/label/image/people/
  schedule/visibility icons, hidden-icon title row like the calendar's).
  Reminder time+repeat now sit inline under their checkbox. Every
  formControlName, input id, button id and the four e2e section ids
  (#ny-sektion-ejendommen, #ny-sektion-tags, #ny-sektion-tildeling,
  #ny-sektion-deadline-paamindelse) are unchanged, as are the global
  btn-primary/btn-secondary toggle and btn-cancel/btn-primary footer.
- adhoc-task-drawer.component.scss: gcal row/icon/section rules mirrored
  from the calendar modal, all scoped under .adhoc-drawer because the
  component uses ViewEncapsulation.None (must not restyle the calendar's
  own gcal classes).
- adhoc.module.ts: drop the now-unused MatExpansionModule import.
- BackendConfigurationAdhoc.page.ts (e2e-compat): expandSection() is now
  no-op-safe - it only clicks when a mat-expansion-panel-header actually
  exists inside the section - so every existing spec passes unchanged
  against the flat markup (sections 2-3 no longer need expanding; the
  helper simply returns). No spec files touched.

Live-verified against http://localhost:4200: drawer opens, all three
sections interactable immediately (worker assignment + deadline reminder
exercised), 0 console errors, side-by-side match with the calendar
popover's design language.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
The filter bar rooted on `.toolbar-card` - a class defined nowhere - and
rendered as a detached block below the sub-header card. Siblings
(task-management-filters, documents-filters) instead render their
controls inline in the container's `eform-sub-header` mat-card via a
`.filters-inline` display:contents wrapper.

- adhoc-container.component.html: move <app-adhoc-filters> into the
  sub-header row (between the Overblik/Historik toggle and the Ny opgave
  button, which now carries .ml-auto to stay right-aligned); row switches
  to align-items-end like the siblings'.
- adhoc-filters.component.html: root class toolbar-card -> filters-inline;
  drop the equally-undefined toolbar__search-col. Tag popup markup/logic
  and every id (#search, #ejendom, #toolbar-omraade, #task-status-filter,
  #tag-filter-btn, #tag-filter-panel, #toolbar-tag-ny, ...) unchanged;
  [ngModel] bindings untouched.
- adhoc-filters.component.scss: add the sibling `.filters-inline`
  display:contents block + 200px field-width constraint (copied shape
  from documents-filters/task-management-filters); bump the tag popup to
  z-index 110 so it clears the mtx-grid's sticky header (z 100) now that
  it anchors directly above the table.
- BackendConfigurationAdhoc.page.ts (e2e-compat): searchInput() rescopes
  #search from #main-list-view to app-adhoc-filters, since the filters no
  longer live inside #main-list-view; still strict-mode-safe against the
  app shell's a#search. No spec files touched.

Live-verified: header + filters render on one sub-header row matching
task-management's look, #search resolves uniquely, tag popup opens above
the sticky table header, 0 console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Az5wyw7FLmb3xFnrP89hA3
@renemadsen
renemadsen marked this pull request as ready for review July 23, 2026 12:35
Copilot AI review requested due to automatic review settings July 23, 2026 12:35
@renemadsen
renemadsen merged commit 8eefead into stable Jul 23, 2026
29 checks passed

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

Adds the initial “adhoc” (ad-hoc tasks) transport + service layer to the BackendConfiguration plugin, along with a new dashboard module and Playwright E2E coverage to exercise the new UI flow.

Changes:

  • Introduces an ad-hoc task backend contract (new adhoc.proto) and supporting backend service abstractions/models/exceptions.
  • Adds an Angular “Adhoc overview” dashboard module (NGRX state, REST façade, components, i18n) and routes/claims gating.
  • Extends CI/dev-install scripts and Playwright shard matrix to run new z-shard adhoc E2E tests.

Reviewed changes

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

Show a summary per file
File Description
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/GrpcServices/GrpcSiteResolver.cs Adds a site display-name lookup used by adhoc gRPC caller identity.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/IBackendConfigurationAdhocService.cs Defines the backend ad-hoc task CRUD/visibility/photo/tag/history service contract.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/IAdhocPhotoStorage.cs Adds a seam to abstract SDK S3 photo storage for testability.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/AdhocTaskUnauthorizedException.cs Adds a dedicated exception type for adhoc authorization failures.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/AdhocTaskPhotoNotFoundException.cs Adds a dedicated exception type for missing adhoc photos.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/AdhocTaskNotFoundException.cs Adds a dedicated exception type for missing/removed adhoc tasks.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/AdhocTagNotFoundException.cs Adds a dedicated exception type for missing/removed adhoc tags.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Services/BackendConfigurationAdhocService/AdhocPhotoStorage.cs Implements IAdhocPhotoStorage using the SDK Core S3 APIs.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Protos/adhoc.proto Introduces the wire contract for the adhoc gRPC API.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Infrastructure/Models/Adhoc/AdhocTaskHistoryEventModel.cs Adds the history/timeline event DTO for the dashboard history view.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Infrastructure/Models/Adhoc/AdhocHistoryFiltersModel.cs Adds history filter DTO (date/property/area/tags/paging).
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/Infrastructure/Data/Seed/Data/BackendConfigurationPermissionsSeedData.cs Seeds an “Enable adhoc” permission/claim.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/EformBackendConfigurationPlugin.cs Registers adhoc services and maps the new gRPC service; adds a menu entry for the adhoc dashboard.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn/BackendConfiguration.Pn.csproj Bumps backend base dependency and adds adhoc.proto to server-side gRPC codegen.
eFormAPI/Plugins/BackendConfiguration.Pn/BackendConfiguration.Pn.Integration.Test/FakeAdhocPhotoStorage.cs Provides an in-memory IAdhocPhotoStorage test double for integration tests.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/index.ts Exports the new adhoc NGRX slice from the state barrel.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/backend-configuration.state.ts Extends the feature state shape with adhocState.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/adhoc/index.ts Adds the adhoc state barrel exports.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/adhoc/adhoc.selector.ts Adds selectors for adhoc filters/pagination/hidden columns/history filters.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/adhoc/adhoc.reducer.ts Adds adhoc reducer + initial filter/pagination defaults.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/adhoc/adhoc.reducer.spec.ts Unit tests for the adhoc reducer behavior.
eform-client/src/app/plugins/modules/backend-configuration-pn/state/adhoc/adhoc.actions.ts Adds adhoc actions for filters/pagination/hidden-columns/history-filters.
eform-client/src/app/plugins/modules/backend-configuration-pn/services/index.ts Exports the new adhoc REST façade service.
eform-client/src/app/plugins/modules/backend-configuration-pn/services/backend-configuration-pn-adhoc.service.ts Implements dashboard REST endpoints for adhoc tasks/tags/photos/history.
eform-client/src/app/plugins/modules/backend-configuration-pn/services/backend-configuration-pn-adhoc.service.spec.ts Unit tests for the adhoc REST façade request shapes/routes.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/store/index.ts Exports the adhoc state façade service.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/store/adhoc-state.service.ts Adds a façade that maps UI state to wire filters and caches reference data.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/store/adhoc-state.service.spec.ts Unit tests for façade mapping and caching behaviors.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/index.ts Exports adhoc feature components.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-task-drawer/adhoc-task-drawer.component.spec.ts Unit tests for the task drawer behaviors.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-task-drawer/adhoc-task-drawer.component.scss Drawer styling for the right-anchored, full-height dialog panel.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-table/adhoc-table.component.ts Table component for the overview page (column picker, sorting, actions).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-table/adhoc-table.component.spec.ts Pure-function tests for adhoc display utilities used by the table/drawer.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-table/adhoc-table.component.scss Styles for the overview table (deadline bar, picker panel, badges).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-table/adhoc-table.component.html Table template (columns, deadline visuals, actions, status cell).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-history/adhoc-history.component.spec.ts Unit tests for the history view behaviors.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-history/adhoc-history.component.scss Styles for the history timeline view.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-history/adhoc-history.component.html History timeline template and filters UI.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-filters/adhoc-filters.component.ts Toolbar filters component (status/property/area/search + tag popup).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-filters/adhoc-filters.component.spec.ts Unit tests for filter behaviors and tag popup logic.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-filters/adhoc-filters.component.scss Inline-filter layout and tag popup styling.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-filters/adhoc-filters.component.html Toolbar filter template for overview view.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-delete-modal/adhoc-delete-modal.component.ts Delete confirmation modal for adhoc tasks.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-delete-modal/adhoc-delete-modal.component.spec.ts Unit tests for the delete modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-delete-modal/adhoc-delete-modal.component.scss (New) Stylesheet placeholder for the delete modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-delete-modal/adhoc-delete-modal.component.html Delete modal template.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-copy-modal/adhoc-copy-modal.component.ts Copy-task confirmation modal (with/without comments).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-copy-modal/adhoc-copy-modal.component.spec.ts Unit tests for the copy modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-copy-modal/adhoc-copy-modal.component.scss (New) Stylesheet placeholder for the copy modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-copy-modal/adhoc-copy-modal.component.html Copy modal template.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-container/adhoc-container.component.ts Container wiring for overview/history tabs, drawers, and modals.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-container/adhoc-container.component.scss Container styling notes (mostly relies on host global styles).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-container/adhoc-container.component.html Container layout for tab toggle, filters, table, and router outlet.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-complete-modal/adhoc-complete-modal.component.ts “Complete task” modal to select performer and post completion.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-complete-modal/adhoc-complete-modal.component.spec.ts Unit tests for the complete modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-complete-modal/adhoc-complete-modal.component.scss (New) Stylesheet placeholder for the complete modal.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/components/adhoc-complete-modal/adhoc-complete-modal.component.html Complete modal template.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/adhoc.routing.ts Adds the adhoc feature routing (history nested route).
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/adhoc.module.ts Declares/imports the adhoc feature module and its components.
eform-client/src/app/plugins/modules/backend-configuration-pn/modules/adhoc/adhoc-display.util.ts Adds pure helpers for id→name resolution and deadline visuals.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/index.ts Exports new adhoc models from the models barrel.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/index.ts Adhoc model barrel exports.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-worker.model.ts Adhoc worker reference model.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-tasks-paged.model.ts Index response model (paged tasks + counts).
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-task.model.ts Canonical adhoc task wire model + related request DTOs.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-task-request.model.ts UI-facing filter/pagination bundle type for adhoc requests.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-task-filters.model.ts Wire-level filters model + status enum.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-task-create.model.ts Create/update DTO for adhoc tasks.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-tag.model.ts Tag model + tag create DTO.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-property.model.ts Property reference model.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-history.model.ts History event + filters models for the timeline view.
eform-client/src/app/plugins/modules/backend-configuration-pn/models/adhoc/adhoc-area.model.ts Area reference model.
eform-client/src/app/plugins/modules/backend-configuration-pn/i18n/enUS.ts Adds translations for the adhoc dashboard UI.
eform-client/src/app/plugins/modules/backend-configuration-pn/i18n/da.ts Adds Danish translations for the adhoc dashboard UI.
eform-client/src/app/plugins/modules/backend-configuration-pn/enums/backend-configuration-pn-claims.const.ts Adds the adhoc_enable claim constant for route guarding.
eform-client/src/app/plugins/modules/backend-configuration-pn/backend-configuration-pn.routing.ts Adds the adhoc-tasks feature route guarded by adhoc_enable.
eform-client/src/app/plugins/modules/backend-configuration-pn/backend-configuration-pn.module.ts Registers the adhoc NGRX reducer under the feature store.
eform-client/playwright/e2e/plugins/backend-configuration-pn/z/adhoc-table-filter.spec.ts Adds Playwright E2E: overview table + filters flow.
eform-client/playwright/e2e/plugins/backend-configuration-pn/z/adhoc-create-task-drawer.spec.ts Adds Playwright E2E: create task drawer flow.
eform-client/playwright/e2e/plugins/backend-configuration-pn/z/adhoc-complete-archive.spec.ts Adds Playwright E2E: complete/archive/delete lifecycle flow.
devinstall.sh Copies Playwright plugin tests/config into the host frontend workspace.
devinstall.py Copies Playwright plugin tests/config into the host frontend workspace.
devgetchanges.sh Copies Playwright plugin tests back into this repo.
devgetchanges.py Copies Playwright plugin tests back into this repo.
.github/workflows/dotnet-core-pr.yml Adds shard z to the Playwright test matrix.
.github/workflows/dotnet-core-master.yml Adds shard z to the Playwright test matrix.

Comment on lines +133 to +137
<mat-chip color="primary">
<span *ngIf="row.status === 'open' || (!row.completed && !row.archived)">{{ 'Open' | translate }}</span>
<span *ngIf="row.completed && !row.archived">{{ 'Solved' | translate }}</span>
<span *ngIf="row.archived">{{ 'Archived' | translate }}</span>
</mat-chip>
<PackageReference Include="Microting.eFormApi.BasePn" Version="10.0.30" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
<PackageReference Include="Microting.EformBackendConfigurationBase" Version="10.0.45" />
<PackageReference Include="Microting.EformBackendConfigurationBase" Version="10.0.46" />
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