VS Build Tools 2022 does not ship Microsoft.VisualStudio.Tools.Office.targets
(only the full VS IDE editions do), so the VSTO project fails to import them
(MSB4226). The runner machine already has VS Pro 2022 with the Office workload
installed; switch from microsoft/setup-msbuild@v2 (which picks Build Tools via
-latest) to a manual vswhere step that explicitly requires the Office workload.
Also removes the temporary filesystem-debug step (root cause was the Win32 File
System Redirector pulling system32 paths to SysWOW64 for 32-bit nuget/MSBuild;
fixed at the runner level via USERPROFILE redirect, not in the workflow).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues surfaced from the addin log:
1) The four Klear ribbon PNGs were not in the assembly manifest
(Available resources listed only the XMLs and Properties.Resources).
The EmbeddedResource entries for Images got dropped from
OutlookAddin.csproj during a previous rebase. Re-add them in the same
spot they had before.
2) MatchingService failed for one of the contacts with:
System.Text.Json.JsonException at $.list[0].number
Cannot get the value of a token type Number as a string.
EspoCRM emits Case.number as a JSON number on installs that use
auto-increment, and as a string when a custom case-code format is
configured. Add a small FlexibleStringConverter that reads either
token type into the same string field, and attach it to
CaseEntity.Number.
Tests: 39 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The self-hosted Windows runner runs as LocalSystem and only sees the
machine PATH. winget-installed pwsh lands in the user PATH and is
invisible to the service. Built-in Windows PowerShell 5.1 (`powershell`)
handles the same syntax we use, so switch all steps to it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the previous commit the ribbon rendered with NO icons at all.
Cause: loadImage on customUI is the Office-2007 pattern; Office 2010+
silently falls back to no image when the callback signature or timing
isn't exactly what it expects. Some hosts also cache a null result.
Switch to the more reliable per-button pattern:
- Each button declares getImage=OnGetImage (no image= attribute).
- OnGetImage(IRibbonControl control) maps control.Id -> embedded
PNG name and returns a fresh Bitmap.
- If a resource is missing we log it AND the full list of
GetManifestResourceNames() so we can diagnose any future mismatch
from the addin log alone.
Builds clean. PNGs unchanged; csproj <EmbeddedResource> entries are the
same as before. The previous OnLoadImage callback is removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first v0.1.0 run failed because actions/checkout could not find Node
and msbuild/nuget were never on the LocalSystem PATH after winget install.
This commit pulls in the standard tool-setup actions and resolves the
.NET-Framework-bound tooling (mage.exe, signtool.exe) by walking the
Windows SDK / .NET 4.8 Developer Pack directories instead of hardcoding
versioned paths that drift across machines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the previous imageMso identifiers with PNG icons painted in the
Klear brand blue (#2563EB):
- klear-file.png — large white K on blue rounded square
- klear-sidebar.png — pane + divider + content lines
- klear-settings.png — three horizontal sliders with dots
- klear-compose.png — envelope with pencil overlay
All four are 32x32 PNGs embedded as resources under
OutlookAddin.Images.*. Ribbon XML now declares `image=<name>.png` and a
shared `loadImage=OnLoadImage` callback resolves each name to a Bitmap
from the assembly's manifest resources.
The ribbons affected:
- TabMail / Marcus-Law: Klear, פרטי תיק, הגדרות (3 buttons)
- TabNewMailMessage / Marcus-Law: כתוב מתיק (1 button)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace direct SSH rsync with build → push to registry → Coolify pull pattern,
matching the rest of the infra (decisions-court, ai-gateway, ...). Adds cdn/
Dockerfile + nginx.conf to serve .vsto/.application/.manifest/.deploy with
correct MIME types behind a StripPrefix Traefik middleware.
The new workflow has three jobs:
- build: Windows runner, nuget restore + msbuild + dotnet test.
- publish-clickonce: Windows runner, only on v* tag — sign assemblies +
mage manifests + upload publish/ as artifact.
- dockerize-and-deploy: Linux runner — download artifact, build & push
outlook-addin-cdn:<version> + :latest to
gitea.dev.marcus-law.co.il, then trigger Coolify
redeploy of app awdlvjlozz0dvn6kjlme9rd7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sidebar identified the contact but showed "(אין תיקים פתוחים)" even
when cases existed for them. The previous query —
GET /Case?where[0][type]=linkedWith&where[0][attribute]=contacts&where[0][value]=…
— came back empty against this EspoCRM build, likely because the link
name on Case differs from the default or the where[] syntax isn't
accepted on this host.
Switch to the contact-side related-records endpoint which is the
canonical EspoCRM way to fetch linked records:
GET /Contact/{contactId}/cases?select=…&orderBy=createdAt&order=desc&maxSize=5
Keep the old /Case?where[] query as a fallback for installs where the
relationship walk fails. Log the chosen path and result count so the
next "no cases shown" report can be diagnosed from the log file alone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three related polish fixes:
1. Modal dialogs (File-to-Case / Compose-from-Case / Settings) used to
open without an Owner, so they would sink behind other windows and
the user perceived Outlook as "frozen". AttachOwnerToOutlook now
sets WindowInteropHelper.Owner to the foreground HWND (Win32
GetForegroundWindow with MainWindowHandle as fallback) before
ShowDialog. Also sets WindowStartupLocation=CenterOwner and pumps
SetForegroundWindow + Activate on Loaded.
2. Sidebar pane title was "פרטי תיק (EspoCRM)" — renamed to
"Klear · פרטי תיק" to match the new branding.
3. New ribbon button "פרטי תיק" (Marcus-Law group, between Klear and
הגדרות) re-opens the sidebar after the user clicks its X.
Implementation:
- SidebarController.ShowForActiveExplorer() toggles existing pane
Visible back on, or attaches one if missing.
- ExplorerBinding.Pane is now public on the binding for this
reuse.
- AddInHost.ShowSidebar() forwards to the controller.
- ExplorerRibbon.OnShowSidebar wires the ribbon callback.
imageMso = ReadingPaneShow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. New WPF converters (UI.Converters/EntityTypeToHebrewConverter.cs):
- EntityTypeToHebrewConverter maps Case/Contact/Account/Lead/... →
תיק / איש קשר / לקוח / ליד / ...
- EntityTypeToBrushConverter per-type accent brushes
- EntityTypeToIconConverter per-type emoji glyph
2. FileToCaseDialog redesigned:
- Soft slate background (#F8FAFC) with card-style borders + 6px
rounded corners
- Search row turned into a card with a 🔍 leading icon (DockPanel.Dock
respects RTL so it's on the visual right)
- Group headers now show: emoji + Hebrew label (תיק / איש קשר / ...)
+ result count, with a colored stripe per entity type
- Custom ResultItem template: hover + selected backgrounds, rounded
selection
- Recents pane has a header strip and per-row colored chip showing
the Hebrew entity type
- Primary action ("תייק") in solid blue with hover state; secondary
("ביטול") in white with border; tertiary ("+ צור איש קשר") as a
muted link button
3. ComposeFromCaseDialog gets the same card + button styling for
visual consistency.
Builds clean, 39 tests still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. FileToCaseDialog + ComposeFromCaseDialog: under FlowDirection=RTL,
DockPanel flips Dock="Right" to render at visual left. Switch the
search label to Dock="Left" so it renders on the right (start of
Hebrew reading order), matching where the eye expects it.
2. Replace imageMso values that were either off-topic or rare:
- FileToEspoCrm: MeetingForwardToManager → MoveToFolder
- OpenEspoCrmSettings: ServerPropertiesDialog → FileOptions
- ComposeFromCase: MailMergeStartLetters → NewMailMessage
All three are present in every Outlook version ≥ 2010 and convey the
action better.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the "תייק ל-" / "File to" prefix; the action is described in the
screentip/supertip on hover. Ribbon real estate is tight and the brand
name alone reads cleaner in both locales.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two combined fixes for "user clicks תייק, nothing happens":
1. EspoEntityRef now also reads "entityName" and "_scope" into
EntityType — different EspoCRM versions emit different field names
from GlobalSearch. Without this the Submit button was clickable (Id
was populated) but GetSelectedFilingTarget bailed out because
EntityType was the empty string.
2. AddInHost.LaunchFileToCaseAsync now logs the dialog close outcome
AND the rejected target shape, and pops a Hebrew MessageBox when
filing is skipped due to missing entityType — instead of silently
returning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
After the previous fix made Username optional in the Settings dialog and
the credential store, TryInitializeCrm still bailed out when Username
was blank — leaving the add-in in "not configured" state even though
the user had saved a valid API key.
Only ApiKey is required now. The check matches the new contract used by
EspoCrmClient (X-Api-Key alone is sufficient for API-User credentials).
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>
Symptom: "Could not create SSL/TLS secure channel" on every CRM request
(GlobalSearch, MatchingService, TestConnection). TCP and DNS succeed
fine — the failure is at the schannel handshake stage.
Root cause: .NET Framework 4.8 inherits ServicePointManager.SecurityProtocol
from the host process. In Outlook 2019/M365 that ends up as SSL3/Tls1
which modern HTTPS servers reject.
Fix: in AddInHost ctor, OR Tls12 + Tls13 into SecurityProtocol before
any HttpClient is created. Tls13 (value 12288) is wrapped in try/catch
for pre-Win10-1809 systems.
Also surface WebExceptionStatus values in the Settings "Test connection"
dialog (SecureChannelFailure / TrustFailure / NameResolutionFailure /
ConnectFailure / Timeout) so future schannel issues report a clear
Hebrew explanation instead of "One or more errors occurred".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The catch-all in TestConnectionAsync was just showing
"שגיאת חיבור: One or more errors occurred." which is useless. Walk the
InnerException chain and translate the underlying SocketError /
TaskCanceled / AuthenticationException into a specific Hebrew message:
- HostNotFound → "השרת {host} לא נמצא ב-DNS"
- ConnectionRefused → "החיבור ל-{host}:{port} נדחה"
- TimedOut → "חיבור פג זמן"
- HostUnreachable / NetworkUnreachable → check VPN/network
- AuthenticationException → bad TLS cert
- TaskCanceled → 15s timeout
- otherwise → "{OuterType}: {OuterMsg} — {InnerType}: {InnerMsg}"
Full exception (with stack) keeps going to the addin log via
_logger.Warning, so the user can still file a diagnostic bundle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two VSTO-only build errors:
1. CustomTaskPanes.Add() requires a System.Windows.Forms.UserControl.
ElementHost derives from Control, not UserControl. Wrap it inside a
thin UserControl so the COM adapter is happy.
2. Outlook.Explorer exposes BOTH a Close() method and a Close event with
the same name; the C# compiler resolves "explorer.Close += handler"
to the method group. Cast through ExplorerEvents_10_Event to bind
the event explicitly.
Core/UI build clean. Host project still requires the VSTO build targets
to compile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trigger: tag push v*.
- Compute version from tag (vMAJOR.MINOR.PATCH → 4-part for mage)
- Sign DLLs + exe with signtool (CODESIGN_THUMBPRINT secret)
- Build ClickOnce .dll.manifest + .vsto via mage.exe, sign both, rename
payload files with .deploy convention for static-file serving
- Stage protocol handler bin/ + install-protocol.ps1 alongside the
ClickOnce artifact
- Upload publish/ artifact to GH Actions for inspection
- Upload to platform.dev:/var/www/outlook-addin/ via rsync (WSL) with scp
fallback. PLATFORM_DEV_SSH_KEY secret expects a base64-encoded private
key. Skips gracefully if the secret is empty.
- POST to MATTERMOST_WEBHOOK_RELEASES on success and failure
(#git-verelasim channel). Skips gracefully if the secret is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Strings.resx (Hebrew, default) + Strings.en-US.resx (English override)
cover every dialog/sidebar/ribbon string, plus filing summary format
templates (`Msg_FilingSummary_*`)
- LocalizationManager singleton wraps ResourceManager with Get() and
Format() helpers; SetCulture honors AppSettings.Locale
- LocExtension XAML markup ({l:Loc Key}) for bindings
- Themes/RtlFonts.xaml — global RightToLeft FlowDirection and Hebrew
font stack (Segoe UI / David / Frank Ruehl / Arial)
- AddInHost calls LocalizationManager.SetCulture(Settings.Locale) before
any UI thread reads a resource
Existing dialogs keep their hardcoded Hebrew for v1 — those are already
in the correct (he-IL) language. Future iterations can switch to
{l:Loc …} bindings; the resource files already enumerate every string so
the migration is mechanical.
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>
UI:
- ComposeFromCaseDialog (RTL, search-then-pick a single Case via
GlobalSearch + entityType=Case filter)
- ComposeFromCaseViewModel — debounced GlobalSearch, returns chosen
EspoEntityRef
Host:
- ComposeService.ApplyCaseToMailAsync:
* fetches Case + primary Contact
* stamps UserProperties EspoCaseId, EspoCaseName, OutlookAddinGuid
* pre-fills To/Subject and appends an RTL footer with a CRM deep link
- ItemSend hook (in AddInHost.HookItemSend): when the user sends a
tagged compose, register guid → target with SentItemsWatcher
- SentItemsWatcher: rooted Items reference (prevents GC), 30s expiry
sweep, 2-min default timeout per pending tag, MailMatched event
- AddInHost.OnSentMailMatched: extracts the sent MailItem, files via
FilingService, stamps "Filed: {name}" category
- LaunchComposeFromCaseAsync (Inspector ribbon) and
LaunchComposeFromCaseByIdAsync (URL protocol) entry points
- NamedPipeListener (\.\pipe\MarcusLaw.OutlookAddin) — listens for
"COMPOSE <caseId>" lines, dispatches on WPF Dispatcher
Ribbon:
- InspectorRibbon.xml — "כתוב מתיק" button on Mail Compose ribbon, hidden
for received/read mail via getVisible
- ExplorerRibbon class now serves both Explorer and Inspector ribbons
(single IRibbonExtensibility, dispatches by ribbonID)
URL protocol:
- New OutlookAddinProtocolHandler console project (net48 WinExe):
parses outlookaddin://compose?caseId=<id>, connects to named pipe,
writes "COMPOSE <id>\n"
- tools/install-protocol.ps1 + uninstall-protocol.ps1 register the
scheme under HKCU\Software\Classes\outlookaddin
Core/UI/Tests + protocol handler all build; 38 unit tests still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SettingsDialog (WPF, RTL): General tab (URL/username/API key/locale) +
About tab; PasswordBox-bound API key, Test Connection probe, Save/Cancel
- SettingsViewModel: loads from SettingsManager + DpapiCredentialStore,
Test Connection spins a transient HttpClient + EspoCrmClient and reports
401 vs network failures separately; Save persists then signals dialog
close
- Ribbon: second "הגדרות" button in Marcus-Law group; "File to EspoCRM"
now offers to open Settings inline when not configured
- AddInHost.LaunchSettings tears down and rebuilds CRM/filing/retry stack
so new credentials take effect immediately (CrmClientChanged event)
- Tag .taskmaster/tasks/tasks.json: Task #3 in-review with implementation
notes (was already locally modified)
Core/UI/Tests green (38 tests passing); VSTO host project still requires
VS Installer Repair to compile locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VSTO csproj template disables binding redirects, but System.Text.Json's
transitive deps (System.Text.Encodings.Web, System.Runtime.CompilerServices.Unsafe,
System.Memory) ship as out-of-band NuGet packs whose versions diverge
from .NET Framework 4.8's facade assemblies. Without redirects, the
JIT loads the wrong Unsafe.dll for DefaultJavaScriptEncoder's static
init, throwing FileLoadException wrapped in TypeInitializationException
when SettingsManager.JsonOptions is touched.
GenerateBindingRedirectsOutputType=true ensures the generated
{MarcusLaw.OutlookAddin.dll}.config goes alongside the assembly so
the VSTO loader actually sees it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Legacy VSTO csproj doesn't follow PackageReferences transitively from
ProjectReferences — at runtime the host's bin\ ends up without the
satellite DLLs that Core depends on. JIT'ing SettingsManager throws
a type-initializer exception because JavaScriptEncoder.UnsafeRelaxedJsonEscaping
(in System.Text.Encodings.Web, transitive of System.Text.Json) is missing.
Pull all four runtime NuGet packs into the host directly. Versions
mirror Core.csproj exactly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legacy VSTO csproj doesn't include System.Net.Http in its default
references. Adding it so HttpClient resolves in AddInHost.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The composition root was calling EspoCrmClient(baseUrl, user, key, logger)
which doesn't match the actual ctor signature
EspoCrmClient(HttpClient, string, string, string, ILogger). One missing
HttpClient argument = one compile error blocking the host build.
- AddInHost now owns a single HttpClient (Timeout=30s), passed to
EspoCrmClient on init, disposed alongside the retry processor and
Serilog on shutdown.
- baseUrl.ToString() satisfies the string parameter; the Uri.TryCreate
upstream still validates the URL.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legacy VSTO csproj defaults to C# 7.3, but the host code (e.g.
AddInHost, MailItemExtractor) uses nullable reference types — which
require C# 8.0+. Building fails with ~12x
"Feature 'nullable reference types' is not available in C# 7.3".
Sync with the SDK-style Core/UI/Tests projects that already declare
LangVersion=latest + Nullable=enable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OutlookAddin host project:
- ExplorerRibbon (IRibbonExtensibility + ExplorerRibbon.xml embedded): adds
"תייק ל-EspoCRM" button to Mail Home tab, enabled only when 1+ MailItems
selected; invalidates on Explorer.SelectionChange
- MailItemExtractor: STA-side conversion of Outlook.MailItem to MailEnvelope
primitives (subject, from, to/cc/bcc, body, PR_INTERNET_MESSAGE_ID,
attachments base64), with COM RCW release on each iteration
- RetryQueueProcessor: 30s background drain, max 3 attempts per item,
401 short-circuits the loop, items abandoned (not deleted) after max
- AddInHost: composition root — Logger, SettingsManager, DpapiCredentialStore,
EspoCrmClient, FilingService, RetryQueueProcessor. LaunchFileToCaseAsync
reads selection, extracts, opens dialog, files, stamps "Filed: {name}"
category on success, promotes target into Settings.RecentEntities (max 10),
shows summary MessageBox.
- ThisAddIn: instantiate AddInHost in Startup; CreateRibbonExtensibilityObject
returns ExplorerRibbon
VSTO targets are missing on this machine — host project not built locally.
Core/UI/Tests still green (38 tests passing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The hand-written legacy csproj at src/OutlookAddin/ is marked Unsupported
by VS 2022. Per PROJECT-BRIEFING.md (steps 5-11), the host project must
be regenerated from the VSTO template inside Visual Studio with the
Office workload — there is no CLI/dotnet template equivalent.
Removing now so the next VS session starts from a clean slate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Self-contained briefing for a fresh Claude Code session picking up work
on this project from a Windows machine running VS 2022. Covers:
- TL;DR + 4 features
- MailRouter API contract + EspoCRM endpoints
- Tech stack (locked decisions)
- Repo layout + current state
- Step-by-step recipe to regenerate the VSTO host project from the
template (the hand-written csproj is incompatible with VS)
- Conventions + deferred work + open risks
Read this before doing anything: docs/PROJECT-BRIEFING.md.
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>