---
phase: 13-user-directory-viewmodel
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - SharepointToolbox/Core/Models/GraphDirectoryUser.cs
  - SharepointToolbox/Services/IGraphUserDirectoryService.cs
  - SharepointToolbox/Services/GraphUserDirectoryService.cs
  - SharepointToolbox.Tests/Services/GraphUserDirectoryServiceTests.cs
autonomous: true
requirements:
  - UDIR-03

must_haves:
  truths:
    - "GraphDirectoryUser record includes a UserType property (string?) alongside the existing five properties"
    - "GraphUserDirectoryService.MapUser populates UserType from the Graph User object"
    - "IGraphUserDirectoryService.GetUsersAsync accepts an optional bool includeGuests parameter defaulting to false"
    - "When includeGuests is false, the Graph filter remains 'accountEnabled eq true and userType eq Member' (backward compatible)"
    - "When includeGuests is true, the Graph filter is 'accountEnabled eq true' (no userType restriction) and userType is in the select set"
    - "Existing tests continue to pass with no changes required (default parameter preserves old behavior)"
  artifacts:
    - path: "SharepointToolbox/Core/Models/GraphDirectoryUser.cs"
      provides: "Directory user record with UserType for client-side member/guest filtering"
      contains: "UserType"
    - path: "SharepointToolbox/Services/IGraphUserDirectoryService.cs"
      provides: "Interface with includeGuests parameter"
      contains: "includeGuests"
    - path: "SharepointToolbox/Services/GraphUserDirectoryService.cs"
      provides: "Implementation branching filter based on includeGuests"
      contains: "includeGuests"
  key_links:
    - from: "SharepointToolbox/Services/GraphUserDirectoryService.cs"
      to: "SharepointToolbox/Core/Models/GraphDirectoryUser.cs"
      via: "MapUser"
      pattern: "UserType"
---

<objective>
Extend GraphDirectoryUser with a UserType property and add an includeGuests parameter to GraphUserDirectoryService so that Phase 13-02 can load all users and filter members/guests in-memory.

Purpose: SC3 requires "Members only / Include guests" toggle that filters in-memory without a new Graph request. The service must fetch all users (members + guests) when requested, and the model must carry UserType for client-side filtering.

Output: Updated model, interface, implementation, and tests.
</objective>

<execution_context>
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/13-user-directory-viewmodel/13-RESEARCH.md

<interfaces>
<!-- Current GraphDirectoryUser model -->
From SharepointToolbox/Core/Models/GraphDirectoryUser.cs:
```csharp
public record GraphDirectoryUser(
    string DisplayName,
    string UserPrincipalName,
    string? Mail,
    string? Department,
    string? JobTitle);
```

<!-- Current interface -->
From SharepointToolbox/Services/IGraphUserDirectoryService.cs:
```csharp
public interface IGraphUserDirectoryService
{
    Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
        string clientId,
        IProgress<int>? progress = null,
        CancellationToken ct = default);
}
```

<!-- Current implementation (key parts) -->
From SharepointToolbox/Services/GraphUserDirectoryService.cs:
```csharp
config.QueryParameters.Filter = "accountEnabled eq true and userType eq 'Member'";
config.QueryParameters.Select = new[]
{
    "displayName", "userPrincipalName", "mail", "department", "jobTitle"
};

internal static GraphDirectoryUser MapUser(User user) =>
    new(
        DisplayName: user.DisplayName ?? user.UserPrincipalName ?? string.Empty,
        UserPrincipalName: user.UserPrincipalName ?? string.Empty,
        Mail: user.Mail,
        Department: user.Department,
        JobTitle: user.JobTitle);
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Add UserType to GraphDirectoryUser</name>
  <files>
    SharepointToolbox/Core/Models/GraphDirectoryUser.cs
  </files>
  <behavior>
    - GraphDirectoryUser record has 6 positional parameters: DisplayName, UserPrincipalName, Mail, Department, JobTitle, UserType
    - UserType is nullable string (string?) — appended as last parameter for backward compat
  </behavior>
  <action>
    1. Edit `SharepointToolbox/Core/Models/GraphDirectoryUser.cs`:
       Add `string? UserType` as the last parameter:
       ```csharp
       public record GraphDirectoryUser(
           string DisplayName,
           string UserPrincipalName,
           string? Mail,
           string? Department,
           string? JobTitle,
           string? UserType);
       ```

    2. Check for any existing code that constructs GraphDirectoryUser (MapUser, tests) and add the UserType parameter.
       Search for `new GraphDirectoryUser(` and `new(` in test files to find all construction sites.
  </action>
  <verify>
    <automated>dotnet build --no-restore -warnaserror</automated>
  </verify>
  <done>GraphDirectoryUser has UserType property. All construction sites updated. Build passes.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Add includeGuests parameter to interface and implementation</name>
  <files>
    SharepointToolbox/Services/IGraphUserDirectoryService.cs,
    SharepointToolbox/Services/GraphUserDirectoryService.cs
  </files>
  <behavior>
    - IGraphUserDirectoryService.GetUsersAsync has a new `bool includeGuests = false` parameter
    - When includeGuests=false: filter is "accountEnabled eq true and userType eq 'Member'" (unchanged)
    - When includeGuests=true: filter is "accountEnabled eq true" (fetches members + guests)
    - "userType" is always in the select set (needed for MapUser)
    - MapUser includes user.UserType in the mapping
  </behavior>
  <action>
    1. Update `IGraphUserDirectoryService.cs`:
       ```csharp
       Task<IReadOnlyList<GraphDirectoryUser>> GetUsersAsync(
           string clientId,
           bool includeGuests = false,
           IProgress<int>? progress = null,
           CancellationToken ct = default);
       ```

    2. Update `GraphUserDirectoryService.cs`:
       - Update method signature to match interface
       - Add `userType` to Select array
       - Branch filter based on includeGuests:
         ```csharp
         config.QueryParameters.Filter = includeGuests
             ? "accountEnabled eq true"
             : "accountEnabled eq true and userType eq 'Member'";
         config.QueryParameters.Select = new[]
         {
             "displayName", "userPrincipalName", "mail", "department", "jobTitle", "userType"
         };
         ```
       - Update MapUser:
         ```csharp
         internal static GraphDirectoryUser MapUser(User user) =>
             new(
                 DisplayName: user.DisplayName ?? user.UserPrincipalName ?? string.Empty,
                 UserPrincipalName: user.UserPrincipalName ?? string.Empty,
                 Mail: user.Mail,
                 Department: user.Department,
                 JobTitle: user.JobTitle,
                 UserType: user.UserType);
         ```
  </action>
  <verify>
    <automated>dotnet build --no-restore -warnaserror</automated>
  </verify>
  <done>Interface and implementation updated. Default parameter preserves backward compat. Build passes.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: Update tests</name>
  <files>
    SharepointToolbox.Tests/Services/GraphUserDirectoryServiceTests.cs
  </files>
  <behavior>
    - Existing MapUser tests pass with UserType parameter added
    - New test: MapUser populates UserType from User.UserType
    - New test: MapUser returns null UserType when User.UserType is null
  </behavior>
  <action>
    1. Read `SharepointToolbox.Tests/Services/GraphUserDirectoryServiceTests.cs`
    2. Update any existing `MapUser` test assertions to include the UserType field
    3. Add test: MapUser_PopulatesUserType — set User.UserType = "Member", verify GraphDirectoryUser.UserType == "Member"
    4. Add test: MapUser_NullUserType — set User.UserType = null, verify GraphDirectoryUser.UserType is null
    5. Run tests
  </action>
  <verify>
    <automated>dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~GraphUserDirectoryService" --no-build -q</automated>
  </verify>
  <done>All MapUser tests pass including UserType coverage.</done>
</task>

</tasks>

<verification>
```bash
dotnet build --no-restore -warnaserror
dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~GraphUserDirectoryService" --no-build -q
```
Both must pass with zero failures.
</verification>

<success_criteria>
- GraphDirectoryUser has UserType (string?) as last positional parameter
- IGraphUserDirectoryService.GetUsersAsync has bool includeGuests = false parameter
- When includeGuests=false, filter unchanged (backward compatible)
- When includeGuests=true, filter omits userType restriction
- MapUser populates UserType from Graph User object
- userType always in select set
- All tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/13-user-directory-viewmodel/13-01-SUMMARY.md`
</output>
