The sidebar lookup was raising
System.Text.Json.JsonException: The JSON value could not be converted
to MarcusLaw.OutlookAddin.Core.Models.SearchResult
because EspoCRM's /EmailAddress/search endpoint returns
[{id, entityType, emailAddress, name}, ...] (and on some hosted builds
{entityId, entityName, ...}), not the GlobalSearch-style envelope.
- IEspoCrmClient.EmailAddressSearchAsync now returns
Task<List<EmailAddressMatch>>.
- EspoCrmClient parses the bare array, with a fallback that tolerates
{"list":[...]} wrapping if some plugin reshapes the response.
- EmailAddressMatch accepts both the {id, entityType} and the newer
{entityId, entityName} field naming.
- MatchingService updated for the new contract.
The GlobalSearch endpoint (FileToCaseDialog search) still uses the
{total, list} envelope and is untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user left Username blank and clicked Save, the dialog persisted
the literal string "(api-key)" as the username. On reopen, that value
loaded back and the next Test Connection / save would send a poisoned
Espo-Authorization Basic header — base64("(api-key)":<apiKey>) — which
the server correctly rejects with 401, overshadowing the valid
X-Api-Key header.
Fix:
- DpapiCredentialStore.Save accepts a null/empty username and stores it
as an empty string. ApiKey is still mandatory.
- SettingsViewModel passes null when the field is blank instead of the
placeholder.
- EspoCrmClient already skips the Espo-Authorization header when
username is blank — combined with this fix, no fallback header is
sent for API-only credentials.
Tests: added Save_AcceptsEmptyUsername; updated Save_RejectsBlankInput
→ Save_RejectsBlankApiKey to match the new contract. 39/39 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EspoCRM uses TWO different auth schemes:
* X-Api-Key: <apiKey> — for API-type users (no password)
* Espo-Authorization: base64(u:p) — for regular users with passwords
The PRD-derived constructor only set Espo-Authorization with a fake
base64(username:apiKey) which the server rejects with 401 for API users.
Fix:
- Always send X-Api-Key when an apiKey is provided.
- Also send Espo-Authorization Basic-style header when a username is
supplied, so regular-user credentials still work without code changes.
- Username is now optional (constructor + Settings dialog). API key is
sufficient on its own; Settings credential store falls back to
"(api-key)" as the recorded username for logging when blank.
Tests updated:
- FileMailAsync_AddsBothApiKeyAndBasicAuthHeaders — asserts X-Api-Key
is the primary header and Espo-Authorization remains the fallback.
- Constructor_RejectsBlankCredentials — empty username now allowed,
empty apiKey / baseUrl still throw.
All 38 tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Host:
- FolderResolver: walks Outlook Stores/Folders by stored StoreId +
backslash-separated FolderPath; RCWs released at each hop
- FolderWatcher: rooted Items references in a Subscription list
(preventing the GC from silently dropping the COM event sink),
HashSet<EntryID> dedup, marshals category stamping back to the WPF
dispatcher. Per-folder mode: AutoFile when MatchingService returns a
confident unique match AND confidence ≥ ConfidenceThreshold; otherwise
stamps "Needs filing"
- FolderTreeBuilder: one-shot STA traversal (max depth 4, max 500 entries)
into Core-side FolderTreeNode list
UI:
- FolderTreeNode in Core for cross-project transport
- FolderRowViewModel: per-row IsWatched / Mode / ConfidenceThreshold
- SettingsViewModel.LoadFolderTree(tree) merges existing
AppSettings.WatchedFolders into the loaded list
- Save now writes WatchedFolders alongside URL/creds
- SettingsDialog: new "תיקיות" tab with a DataGrid (checkbox + mode combo
per folder)
AddInHost:
- Builds FolderResolver + FolderWatcher inside TryInitializeCrm, applies
Settings.WatchedFolders on startup and after Save
- LaunchSettings: invokes FolderTreeBuilder, passes the flat list to the
ViewModel, and re-applies the watch list after the dialog closes
Core/UI/Tests green (38 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial repo scaffold for a Windows VSTO/COM Outlook Add-in (C#/.NET FW 4.8)
that integrates Outlook desktop with EspoCRM via the existing MailRouter
v1.2.0 endpoint (POST /api/v1/MailRouter/file).
Includes:
- Solution + 4 csproj projects (host, Core, UI, Tests) under src/
- VSTO host project stub (ThisAddIn.cs, requires VS 2022 + Office workload to fully resolve)
- Core/UI/Tests as SDK-style net48 projects (NuGet: Polly, System.Text.Json,
Serilog, CommunityToolkit.Mvvm, xUnit, Moq, FluentAssertions)
- .gitea/workflows/build.yml for Windows runner CI (publish/sign placeholders)
- docs/ARCHITECTURE.md (component diagram, threading model, per-feature flow)
- docs/ONBOARDING.md (Hebrew lawyer-facing install guide)
Server-side already in place: MailRouter v1.2.0 — see plan
~/.claude/plans/resilient-sauteeing-feather.md for the 6-week implementation
plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>