Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8e67f97
feature/AB#9074 - Add Applicant Comments
plavoie-BC Mar 9, 2026
d42e584
Merge branch 'dev' into feature/AB#9074-applicant-comments
plavoie-BC Mar 10, 2026
3837bd1
Merge branch 'dev' into feature/AB#9074-applicant-comments
plavoie-BC Mar 16, 2026
78422d7
[AB#9074] Refactor comments widget manager and update counters
plavoie-BC Mar 16, 2026
4b8fb26
[AB#9074] Update comments widget to refresh counters on init
plavoie-BC Mar 16, 2026
2309307
[AB#9074] Update EmailCommentDto and adjust comment links
plavoie-BC Mar 16, 2026
c909a1e
[AB#9074] Replace NotImplementedException with ArgumentOutOfRangeExce…
plavoie-BC Mar 16, 2026
202dfae
Update customization: feature-planner.agent.md
plavoie-BC Mar 17, 2026
339b1db
Update customization: feature-planner.agent.md
plavoie-BC Mar 17, 2026
664a69a
Merge remote-tracking branch 'origin/dev' into feature/AB#9074-applic…
plavoie-BC Mar 27, 2026
2f087a8
[AB#9074] SonarQube fixes
plavoie-BC Mar 27, 2026
15e91f0
[AB#9074] Comment fix
plavoie-BC Mar 27, 2026
b45a936
Merge remote-tracking branch 'origin/dev' into feature/AB#9074-applic…
plavoie-BC Mar 27, 2026
fa5e634
[AB#9074] Refactor CommentType to use enum in EmailCommentDto
plavoie-BC Mar 28, 2026
3f019d7
[AB#9074] Add GetBaseUrlAsync method and refactor comment link genera…
plavoie-BC Mar 28, 2026
4cc3cd7
[AB#9074] Simplify CommentType enum by removing explicit values
plavoie-BC Mar 28, 2026
74f0a67
[AB#9074] Re-apply ApplicantComments migration
plavoie-BC Mar 28, 2026
5ba8a8e
[AB#9074] Apply suggestions from code review
plavoie-BC Mar 28, 2026
b63b26b
[AB#9074] Code Quality Improvements
plavoie-BC Mar 28, 2026
fe961c6
Merge branch 'dev' into feature/AB#9074-applicant-comments
plavoie-BC Mar 28, 2026
56b69fc
feature/AB#9074-FixImportsUnused
JamesPasta Apr 1, 2026
5f3bce4
Merge branch 'dev' into feature/AB#9074-applicant-comments
JamesPasta Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 124 additions & 28 deletions applications/Unity.GrantManager/.github/agents/feature-planner.agent.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,88 @@
---
name: feature-planner
description: Plans feature implementation across Domain, Application, EF Core, Web, and tests.
tools: ['fetch', 'githubRepo', 'problems', 'usages', 'search', 'todos', 'runSubagent']
argument-hint: Outline the goal or problem to research
target: vscode
disable-model-invocation: true
tools: ['search', 'read', 'web', 'vscode/memory', 'github/issue_read', 'github.vscode-pull-request-github/issue_fetch', 'github.vscode-pull-request-github/activePullRequest', 'execute/getTerminalOutput', 'execute/testFailure', 'agent', 'vscode/askQuestions']
agents: ['Explore']
handoffs:
- label: Start Implementation
agent: agent
prompt: 'Start implementation'
send: true
- label: Open in Editor
agent: agent
prompt: '#createFile the plan as is into an untitled file (`untitled:plan-${camelCaseName}.prompt.md` without frontmatter) for further refinement.'
send: true
showContinueOn: false
---

# ABP Feature Planner Agent

You are the feature planning specialist for Unity Grant Manager.
You are the FEATURE PLANNING AGENT for Unity Grant Manager, pairing with the user to create a detailed, actionable plan.

## Mission
You research the codebase → clarify with the user → capture findings and decisions to convert a feature request into a comprehensive plan that respects ABP modular layering and delivery flow. This iterative approach catches edge cases and non-obvious requirements BEFORE implementation begins.

Convert a feature request into an implementation plan that respects ABP modular layering and delivery flow.
Your SOLE responsibility is planning. NEVER start implementation.

**Current plan**: `/memories/session/plan.md` - update using #tool:vscode/memory.

<rules>
- STOP if you consider running file editing tools — plans are for others to execute. The only write tool you have is #tool:vscode/memory for persisting plans.
- Use #tool:vscode/askQuestions freely to clarify requirements — don't make large assumptions
- Present a well-researched plan with loose ends tied BEFORE implementation
</rules>

<workflow>
Cycle through these phases based on user input. This is iterative, not linear. If the user task is highly ambiguous, do only *Discovery* to outline a draft plan, then move on to alignment before fleshing out the full plan.

## 1. Discovery

Run the *Explore* subagent to gather context, analogous existing features to use as implementation templates, and potential blockers or ambiguities. When the task spans multiple independent areas (e.g., frontend + backend, different features, separate modules), launch **2-3 *Explore* subagents in parallel** — one per area — to speed up discovery.

Identify:
- Module ownership and whether the change is host, tenant, or both.
- Work split by ABP layer: Domain.Shared → Domain → Application.Contracts → Application → EntityFrameworkCore → HttpApi/Web → Tests.
- Dependencies and ordering constraints between layers.
- Cross-module impacts and permission/localization requirements.

Update the plan with your findings.

## 2. Alignment

If research reveals major ambiguities or if you need to validate assumptions:
- Use #tool:vscode/askQuestions to clarify intent with the user.
- Surface discovered technical constraints or alternative approaches.
- If answers significantly change the scope, loop back to **Discovery**.

## 3. Design

Once context is clear, draft a comprehensive implementation plan structured around ABP layers.

The plan should reflect:
- Structured concisely enough to be scannable and detailed enough for effective execution.
- Step-by-step implementation with explicit dependencies — mark which steps can run in parallel vs. which block on prior steps.
- For plans with many steps, group into named phases that are each independently verifiable.
- Verification steps for validating the implementation, both automated and manual.
- Critical architecture to reuse or use as reference — reference specific functions, types, or patterns, not just file names.
- Critical files to be modified (with full paths).
- Explicit scope boundaries — what's included and what's deliberately excluded.
- Reference decisions from the discussion.
- Leave no ambiguity.

Save the comprehensive plan document to `/memories/session/plan.md` via #tool:vscode/memory, then show the scannable plan to the user for review. You MUST show the plan to the user, as the plan file is for persistence only, not a substitute for showing it to the user.

## 4. Refinement

On user input after showing the plan:
- Changes requested → revise and present updated plan. Update `/memories/session/plan.md` to keep the documented plan in sync.
- Questions asked → clarify, or use #tool:vscode/askQuestions for follow-ups.
- Alternatives wanted → loop back to **Discovery** with new subagent.
- Approval given → acknowledge, the user can now use handoff buttons.

Keep iterating until explicit approval or handoff.
</workflow>

## Inputs

Expand All @@ -19,30 +91,54 @@ Convert a feature request into an implementation plan that respects ABP modular
- Target module(s).
- Any constraints (timeline, migration risk, tenant scope, security requirements).

## Process

1. Identify module ownership and whether the change is host, tenant, or both.
2. Split work by layer:
- Domain.Shared
- Domain
- Application.Contracts
- Application
- EntityFrameworkCore
- HttpApi/Web
- Tests
3. List dependencies and ordering constraints.
4. Flag cross-module impacts and permission/localization requirements.

## Output Format

Return sections in this order:

1. Scope summary.
2. Layer-by-layer implementation tasks.
3. Migration and data impact.
4. Test plan summary.
5. Risks and mitigations.
6. Definition of done checklist.
<plan_style_guide>
```markdown
## Plan: {Title (2-10 words)}

{TL;DR - what, why, and how (your recommended approach).}

**Steps**

### Phase 1 — Domain & Contracts
1. {Domain.Shared changes — enums, consts, error codes}
2. {Domain entity/aggregate changes — note dependency ("*depends on N*") or parallelism ("*parallel with step N*") when applicable}
3. {Application.Contracts — DTOs, IAppService interfaces, permissions}

### Phase 2 — Application & Persistence
4. {Application service implementation}
5. {EntityFrameworkCore — DbContext, entity config, migration}

### Phase 3 — API & Frontend
6. {HttpApi controller / AutoAPI}
7. {Web — Pages, JS, localization}

### Phase 4 — Tests
8. {Unit and integration tests}

**Relevant files**
- `{full/path/to/file}` — {what to modify or reuse, referencing specific functions/patterns}

**Migration & Data Impact**
- {Host vs tenant migration scope, data backfill needs, breaking schema changes}

**Verification**
1. {Verification steps for validating the implementation (**Specific** tasks, tests, commands, MCP tools, etc; not generic statements)}

**Decisions** (if applicable)
- {Decision, assumptions, and includes/excluded scope}

**Risks & Mitigations** (if applicable)
- {Risk and mitigation strategy}

**Definition of Done**
- [ ] {Checklist item}
```

Rules:
- NO code blocks — describe changes, link to files and specific symbols/functions.
- NO blocking questions at the end — ask during workflow via #tool:vscode/askQuestions.
- The plan MUST be presented to the user, don't just mention the plan file.
</plan_style_guide>

## Guardrails

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: abp-cli
description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install libraries, or use ABP Suite.
description: ABP CLI commands - generate-proxy, install-libs, add-package-ref, new-module, install-module, abp update, abp clean, abp suite generate. Use when the user asks how to run ABP CLI commands, generate proxies, install NPM libraries, or use ABP Suite.
---

# ABP CLI Commands
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using System;
using System.Collections.Generic;
using Unity.Notifications.Comments;

namespace Unity.Notifications.Emails;

[Serializable]
public class EmailCommentDto
{
public string Subject { get; set; } = string.Empty;
public string From { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
public string ApplicationId { get; set; } = string.Empty;
public string OwnerId { get; set; } = string.Empty;
Comment thread
plavoie-BC marked this conversation as resolved.
public CommentType CommentType { get; set; }
public List<string> MentionNamesEmail { get; set; } = [];
public string? EmailTemplateName { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Unity.GrantManager.Notifications;
using Unity.Notifications.Emails;
using Unity.Notifications.Permissions;
using Unity.Notifications.Settings;
Expand All @@ -14,9 +17,8 @@
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
using Volo.Abp.SettingManagement;
using Microsoft.AspNetCore.Http;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.Users;
using Unity.GrantManager.Notifications;

namespace Unity.Notifications.EmailNotifications;

Expand All @@ -27,8 +29,8 @@ public class EmailNotificationService(
EmailNotificationManager emailNotificationManager,
IExternalUserLookupServiceProvider externalUserLookupServiceProvider,
ISettingManager settingManager,
IHttpContextAccessor httpContextAccessor,
IFeatureChecker featureChecker) : ApplicationService, IEmailNotificationService
IFeatureChecker featureChecker,
IAppUrlProvider appUrlProvider) : ApplicationService, IEmailNotificationService
{

public async Task<Guid> InitializeDraftAsync(Guid applicationId)
Expand Down Expand Up @@ -71,6 +73,12 @@ protected virtual async Task NotifyTeamsChannel(string chesEmailError)
await notificationAppService.PostToTeamsAsync(activityTitle, activitySubtitle);
}

public async Task<string> GetBaseUrlAsync()
{
var appUrl = await appUrlProvider.GetUrlAsync(appName: "MVC");
return appUrl;
}

public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto input)
{
HttpResponseMessage res = new();
Expand All @@ -79,18 +87,33 @@ public async Task<HttpResponseMessage> SendCommentNotification(EmailCommentDto i
if (await featureChecker.IsEnabledAsync("Unity.Notifications"))
{
var defaultFromAddress = await SettingProvider.GetOrNullAsync(NotificationsSettings.Mailing.DefaultFromAddress);
var scheme = "https";
var request = (httpContextAccessor.HttpContext?.Request) ?? throw new InvalidOperationException("HttpContext or Request is null.");
var host = request.Host.ToUriComponent();
var pathBase = "/GrantApplications/Details?ApplicationId=";
var baseUrl = $"{scheme}://{host}{pathBase}";
var commentLink = $"{baseUrl}{input.ApplicationId}";
var baseUrl = await GetBaseUrlAsync();

string commentLink = input.CommentType switch
{
Comments.CommentType.ApplicationComment or Comments.CommentType.AssessmentComment =>
QueryHelpers.AddQueryString($"{baseUrl}/GrantApplications/Details", "ApplicationId", input.OwnerId),
Comments.CommentType.ApplicantComment =>
QueryHelpers.AddQueryString($"{baseUrl}/GrantApplicants/Details", "ApplicantId", input.OwnerId),
_ => throw new InvalidOperationException("Invalid comment type.")
};

var subject = $"Unity-Comment: {input.Subject}";
var fromEmail = defaultFromAddress ?? "NoReply@gov.bc.ca";

var hasSurname = !string.IsNullOrWhiteSpace(CurrentUser.SurName);
var hasName = !string.IsNullOrWhiteSpace(CurrentUser.Name);

var currentUserText = (hasSurname, hasName) switch
{
(true, true) => $"{CurrentUser.SurName}, {CurrentUser.Name}",
_ => CurrentUser.UserName ?? "Unknown User"
};

string htmlBody = $@"
<html lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:v='urn:schemas-microsoft-com:vml' xmlns:o='urn:schemas-microsoft-com:office:office'>
<body style='font-family: Arial, sans-serif;'>
<h3 style='color: #0a58ca;'>{input.From} mentioned you in a comment.</h3>
<h3 style='color: #0a58ca;'>{currentUserText} mentioned you in a comment.</h3>
<table style='width: 100%; background-color: #f9f9f9; border-left: 3px solid #ccc;'>
<tr>
<td style='padding: 15px;'>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;

namespace Unity.Notifications.Comments;

/// Edit Unity.GrantManager.Comments.CommentType if changing this enum as it is shared between the two projects and used in the API layer.
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum CommentType
{
ApplicationComment,
AssessmentComment,
ApplicantComment,
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public GrantManagerApplicationAutoMapperProfile()
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.AssessmentId));
CreateMap<ApplicationComment, CommentDto>()
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicationId));
CreateMap<ApplicantComment, CommentDto>()
.ForMember(dest => dest.OwnerId, opt => opt.MapFrom(src => src.ApplicantId));
CreateMap<CommentListItem, CommentDto>()
.ForMember(dest => dest.Badge, opt => opt.MapFrom(src => src.CommenterBadge))
.ForMember(dest => dest.Commenter, opt => opt.MapFrom(src => src.CommenterDisplayName))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
namespace Unity.GrantManager.Comments
using System.Text.Json.Serialization;

namespace Unity.GrantManager.Comments;

/// If changing this enum, also update the corresponding Unity.Notifications.Comments.CommentType enum, as it is shared between the two projects and used in the API layer.
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum CommentType
{
public enum CommentType
{
ApplicationComment,
AssessmentComment
}
ApplicationComment,
AssessmentComment,
ApplicantComment,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace Unity.GrantManager.Comments;

public class ApplicantComment : CommentBase
{
public Guid ApplicantId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System;

namespace Unity.GrantManager.Comments
namespace Unity.GrantManager.Comments;

public class ApplicationComment : CommentBase
{
public class ApplicationComment : CommentBase
{
public Guid ApplicationId { get; set; }
}
public Guid ApplicationId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System;

namespace Unity.GrantManager.Comments
namespace Unity.GrantManager.Comments;

public class AssessmentComment : CommentBase
{
public class AssessmentComment : CommentBase
{
public Guid AssessmentId { get; set; }
}
public Guid AssessmentId { get; set; }
}
Loading
Loading