Add adhoc gRPC/REST backend for flutter-adhoc (M3 group B, WIP) - #1074
Merged
Conversation
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>
…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>
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>
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
marked this pull request as ready for review
July 23, 2026 12:35
There was a problem hiding this comment.
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" /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spec/plan: flutter-adhoc repo docs/superpowers/plans/2026-07-21-flutter-adhoc-m3-backend-transport.md (group B)
🤖 Generated with Claude Code