The two %APPDATA% / %LOCALAPPDATA% paths in Settings → אודות are now
hyperlinks. Click to open the folder (creates it lazily if it's not
there yet, so the link works before the first log is written).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User-facing strings only — ClickOnce identity, AssemblyName, namespaces,
DPAPI entropy and on-disk paths all stay MarcusLaw so the 10 lawyers
auto-update without any manual uninstall/reinstall.
What lawyers see change:
- About panel: dark-navy header card (#121d2e from KlearBranding) with
the Klear logo (32KB PNG mirrored from
espocrm-extensions/KlearBranding/files/client/custom/img/logo-klear.png)
and the title "Klear" instead of "Marcus-Law OutlookAddin"
- All MessageBox titles ("Klear" instead of "Marcus-Law OutlookAddin")
- Ribbon group label ("Klear" instead of "Marcus-Law") in both the
Explorer and the new-message Inspector ribbons
- Startup-failure dialog ("תוסף Klear נכשל" instead of "תוסף Marcus-Law")
What stays MarcusLaw (deliberate — Layer 1 only):
- ClickOnce app identity → preserves auto-update across this release
- %APPDATA%\MarcusLaw\…\settings.json + creds.dat → no migration needed
- DpapiCredentialStore EntropySeed → API keys remain decryptable
- Internal namespaces / pipe name / AssemblyCompany attribute
Cleanup:
- Removed the entire Publish/ directory from git. It was the orphaned
manual-release channel under gitea raw/branch/main/Publish/ that no
one actually uses — production installs are served from
platform.dev.marcus-law.co.il via the Gitea Actions pipeline. The
pipeline writes to a local publish/ workspace and uploads a tar.gz
to the generic registry; it never reads or writes the repo's Publish/.
- Wiped the local LegalCRM_1_0_*_0 leftovers from the failed rename
experiment.
Bumped to 1.2.0 — MINOR because the rebrand is a user-visible change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs reported after v1.1.2:
1. False-negative "נכשלו" toast even though the file was on disk. Root
cause from the log: Polly's 15s timeout on /NetworkStorage/action/upload
fires while the server is still writing larger PDFs, so the client sees
TimeoutRejectedException → Outcome.Failed, while the server quietly
finishes the write. The outcome is now decided by listing the target
folder, not by the upload response. If the upload exception fires, the
listing is retried up to 4 times with 2s delays to let the server
finish. Saved if the file appears, Failed only if it never does.
2. The filename TextBox in the attachments list exposed the extension
(.pdf / .docx). Users have to delete it manually when renaming and
risk dropping it. AttachmentRowViewModel now splits FileName into
BaseName (editable) and Extension (frozen at construction). The XAML
binds the TextBox to BaseName and shows the Extension as a muted
adjacent chip. The on-disk filename = BaseName + Extension always.
Bump to 1.1.3.0 for the patch release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- DataGrid case picker now shows status via CaseStatusToHebrewConverter
(added PendingHearing / PendingDecision mappings)
- Attachment row replaces the read-only filename TextBlock with a TwoWay
TextBox so users can rename a file before saving (flows through
EspoAttachment.Name into the upload + the on-disk path)
- SaveAttachmentsViewModel.IsLoadingSubfolders gates the folder ComboBox
while EnsureCaseSubfolders / GetCase / ListNetworkStorageFolder run,
so users can't pick a stale "(שורש התיק)" before the real list arrives
- About tab: ResolveDisplayVersion now reads from the host VSTO assembly
(*.OutlookAddin) instead of the UI assembly. The UI csproj is SDK-style
with no <Version> so it always reported 1.0.0.0, which leaked into the
About line on dev installs
- Bump to 1.1.2.0 (csproj + AssemblyInfo) to match the v1.1.2 tag that
will trigger the Gitea Actions auto-publish to platform.dev
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Versioning policy: MAJOR.MINOR.PATCH stored as MAJOR.MINOR.PATCH.0
in the ClickOnce manifest (it requires 4 components). The trailing
.0 is hidden by SettingsViewModel.ResolveDisplayVersion so the About
tab shows "1.0.7" not "1.0.7.0".
AutoIncrementApplicationRevision is now false; every published change
must bump <ApplicationVersion> and AssemblyVersion/AssemblyFileVersion
together by hand. MAJOR bumps need explicit owner approval.
Previously AssemblyVersion was pinned at 1.0.0.0 while ClickOnce
auto-bumped only ApplicationVersion, so the deployed DLL's
FileVersion never matched the manifest. Now they're locked at 1.0.7.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the single-line ListBox with a DataGrid (case number, name,
court case number, status) so users disambiguate same-named cases
(e.g. two "שלומוב זויה" cases). Hydrates each search hit via
GET /Case/{id}?select=id,name,number,status,cCourtCaseNumber in
parallel so columns populate without an extra click. CourtCaseNumber
is mapped to the EspoCRM custom field cCourtCaseNumber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Ambiguous lookups (2+ contacts on one email) now dedupe by Contact Id
and fetch each candidate's full record so the picker shows
name + account + email instead of just the bare search payload.
- Per-row "תייק" button on each linked case lets the user pick the
correct case to file to when a contact has multiple cases; bottom
"תייק לתיק הזה" button is hidden in that scenario.
- "→ חזור לרשימת אנשי קשר" link restores the candidate list after
drilling into one of them.
- Contact name + case rows in the sidebar are now hyperlinks that
open the entity in Klear.
- AddInHost.LaunchFileToCaseAsync accepts an optional MatchResult and
auto-files when the sidebar resolved a single linked case, skipping
the manual search dialog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related improvements:
1. About tab → "בדוק עדכונים" button. Calls ClickOnce's
ApplicationDeployment.CurrentDeployment.CheckForDetailedUpdate +
Update on background threads, so the user can pull a new release
immediately without leaving Outlook — no PowerShell, no
CleanOnlineAppCache dance.
Status messages cover the four real outcomes:
• not deployed via ClickOnce (F5 / dev install) → explicit error
• on the latest version → green "already up to date"
• update available → progress text, then "installed — restart
Outlook"
• download / manifest / generic exception → red, with reason
2. Case folder resolution now matches the server. The previous client
logic used Case.name (the lawsuit title, e.g. "מונאס אריאל נ' ביטוח
לאומי") which produced "47-מונאס אריאל נ ביטוח לאומי". But
NetworkStorageIntegration's buildEntityFolderName uses the primary
contact's name from Case.contactsIds[0] → server actually creates
"47-אריאל מונאס" for the same case, so the two diverged and our
uploads went to a phantom folder.
New flow on case selection:
a) EnsureCaseSubfoldersAsync (creates server's canonical folders)
b) GetCase with select=contactsIds + networkStorageFolderPath
c) GetContactAsync on contactsIds[0] → grab Name
d) ResolveCaseFolderPath(case, contactName) → exact match
e) Cache result on vm.ResolvedCaseFolderPath; AddInHost reads it
instead of recomputing.
ResolveCaseFolderPath fallback order is now (1) the integration's
stored path, (2) <number>-<primary contact name>, (3) <number>-<case
name>, (4) the contact / case name alone — matching the server's
buildEntityFolderName logic step by step.
Tests: 39 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The About tab was always showing "1.0.0" because it read the DLL's
AssemblyVersion, which we never bumped in source — even when the
ClickOnce manifest had been rolled to 1.1.0 by the CI tag.
Read System.Deployment.Application.ApplicationDeployment.CurrentDeployment
.CurrentVersion at runtime when the add-in is running under ClickOnce
(IsNetworkDeployed=true). That's the version the user actually
installed. Fall back to AssemblyVersion when dev-running under F5 (no
ClickOnce context). Adds System.Deployment GAC reference to the UI
project.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After reading the NetworkStorageIntegration repo I realized the previous
"save attachments" flow was wrong on every level — Marcus-Law doesn't
use EspoCRM's stock Document/DocumentFolder model. Folders are real
filesystem paths under the network share, named after the Case (format
"<number>-<primary contact name>") with four standard subfolders:
מסמכים, אסמכתאות, התכתבויות, כללי.
Rewire the save pipeline against the integration's real endpoints
(documented in routes.json on the server repo):
Core — three new IEspoCrmClient methods:
- EnsureCaseSubfoldersAsync(caseId)
POST /NetworkStorage/action/createSubfolders
Idempotent — creates case folder + defaults if missing.
- ListNetworkStorageFolderAsync(path)
GET /NetworkStorage/folderContents?path=…
Returns folders + files; we filter to type=folder for the dropdown.
- UploadAttachmentToNetworkStorageAsync(attachmentId, targetPath)
POST /NetworkStorage/action/upload
Moves the staged Attachment binary onto the share at <path>/<name>.
CaseEntity gets a new `networkStorageFolderPath` field (set by the
integration's hook on first Document save; empty until then).
NetworkStorageItem DTO captures the folder-listing rows.
AttachmentSaveService rewritten — no more Document creation, no more
DocumentFolder picker. The new contract is:
SaveAsync(attachments, targetFolderPath)
Where targetFolderPath = "<case folder>[/subfolder]". For each
attachment: upload binary via standard /Attachment, then move via
NetworkStorage upload.
UI — SaveAttachmentsViewModel:
- Subfolder dropdown switched from DocumentFolder objects to strings.
- Static defaults loaded up-front:
"(שורש התיק)" + 4 server-side defaults.
- When the user picks a case:
1) hydrate Case (number + networkStorageFolderPath) via GetCase
2) call EnsureCaseSubfoldersAsync to materialise standard folders
3) listFolder at the case path to merge any custom subfolders into
the dropdown.
- ResolveCaseFolderPath: use Case.networkStorageFolderPath if set,
otherwise compute "<number>-<contact-name>" with a client-side
SanitizeFolderSegment that mirrors LocalFilesystemClient::sanitizeFileName
on the server (geresh/gershayim/quotes removed, illegal chars → hyphen).
AddInHost.LaunchSaveAttachmentsAsync:
- Composes <caseFolder>[/subfolder] from the VM choice.
- Pre-flight ensureCaseSubfolders as a safety net (in case the user
submitted before the dialog's lazy refresh completed).
- Summary MessageBox now shows the resolved storage path instead of a
bare entity name.
Tests: 39 passing. The build is clean across Core / UI / Tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the EspoCRM Attachment API doc (documentation.espocrm.com/development/api/attachment/),
the binary is sent in the "file" field as a data URI. The previous
extra "contents" alias was a legacy convention that strict tenants
reject with HTTP 400 (with an empty body, so the user only ever saw
"שמירת הקבצים נכשלה"). Drop "contents" — match the documented payload:
{
"name": "report.pdf",
"type": "application/pdf",
"role": "Attachment",
"relatedType": "Document",
"field": "file",
"file": "data:application/pdf;base64,..."
}
Folder dropdown: previously case-scoped only. The /Document filter by
parentId returned 400 on this tenant and case-scoped queries can be
fragile across versions. Now: try case-scoped first (so the most-
relevant folders surface); if it comes back empty or errors, fall back
to ListDocumentFoldersAsync (the full tenant list). The user always has
something to choose from, and the "(בלי תיקייה)" sentinel still works
as a "no folder" pick.
Tests: 39 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The folder dropdown was showing every DocumentFolder in EspoCRM (global
list). The user expected "folders that exist in THIS case" — only the
folders already in use for the chosen case's documents.
Change: when the user picks a case in the SaveAttachments dialog, the
folder list refreshes by walking
GET /Case/{id}/documents?select=id,folderId,folderName&maxSize=200
and deduping the folderId values it finds. EspoCRM denormalizes
folderName onto Document, so no second lookup is needed.
If a case has no documents yet, the dropdown shows just the sentinel
"(בלי תיקייה)" — same default behavior as before, but no more
unrelated folders from other cases.
Files:
- Core: DocumentEntity gets FolderName; IEspoCrmClient and EspoCrmClient
get ListFoldersUsedInCaseAsync(caseId)
- UI: SaveAttachmentsViewModel re-fetches on SelectedCase change
(LoadFoldersForCaseAsync); AddInHost no longer calls the old
LoadFoldersAsync.
Tests: 39 still passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New workflow alongside "תייק ל-Klear": save just the attachments of a
mail to a Case in EspoCRM (no Email entity created), with an optional
DocumentFolder.
Core:
- AttachmentEntity, DocumentEntity, DocumentFolder DTOs
- IEspoCrmClient gets three new endpoints:
UploadAttachmentAsync (POST /Attachment, data-URI base64, role=Attachment)
CreateDocumentAsync (POST /Document, fileId + parent + folderId)
ListDocumentFoldersAsync (GET /DocumentFolder, returns envelope)
- AttachmentSaveService orchestrates per-attachment upload + Document
create, short-circuits on EspoCrmAuthorizationException, aggregates
per-file Saved/Failed/AuthRequired outcomes into AttachmentSaveSummary
UI:
- SaveAttachmentsDialog (RTL, card layout matching FileToCaseDialog):
Case search + folder dropdown + per-attachment checkbox list
- SaveAttachmentsViewModel: debounced GlobalSearch filtered to Case,
optional folder selector (with a "(no folder)" sentinel row),
Attachments collection of AttachmentRowViewModel rows
- AttachmentRowViewModel: IsSelected checkbox + display name + size
computed from base64 length
Host:
- AddInHost.LaunchSaveAttachmentsAsync(MailItem) — extracts on the STA
via MailItemExtractor, fetches folders, shows the dialog (anchored to
Outlook via AttachOwnerToOutlook), runs AttachmentSaveService on OK,
reports a Hebrew MessageBox summary
- AttachmentSaveService field on AddInHost + dispose hook
Ribbon:
- New "שמור קבצים" button in the Marcus-Law group, between Klear and
פרטי תיק. Enabled only when exactly one MailItem is selected and it
has at least one attachment
- klear-attach.png paperclip icon (32x32, blue rounded square),
embedded as a resource and resolved by the existing OnGetImage
callback (new case in the switch)
Tests: 39 still passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two quality-of-life tweaks on the sidebar:
1. New CaseStatusToHebrewConverter translates standard EspoCRM Case
statuses (New / Assigned / Pending / Closed / Rejected / Duplicate /
In Progress / On Hold / Resolved / Cancelled / Closed Won / Closed
Lost / Open) to Hebrew. Unknown values pass through unchanged so
custom statuses configured per-tenant still render.
Wired in ReadingPaneView.xaml via Status binding.
2. MatchingService now pulls up to 50 cases per contact instead of 5.
The sidebar's ScrollViewer already handles long lists. 50 is well
below EspoCRM's default page size and large enough for any realistic
attorney/contact relationship. Section header changed from
"תיקים אחרונים" to "תיקים מקושרים" to reflect that it's the
complete list, not just the most recent.
Tests: 39 passing.
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>
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>
- 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>
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>