Commit Graph

92 Commits

Author SHA1 Message Date
PointStar 18b6640e57 feat(save-attachments): editable filenames, Hebrew status, smoother subfolder load
- 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>
v1.1.2
2026-05-24 17:27:49 +03:00
PointStar dbd7f3068b publish: 1.0.7 — switch to 3-part SemVer, manual bumps only
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>
2026-05-24 15:51:40 +03:00
PointStar b6624a924f feat(save-attachments): show case#, court#, status in candidate picker
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>
2026-05-24 15:51:15 +03:00
PointStar e3fb0e95d1 fix(publish): stop git CRLF/LF normalization on ClickOnce bundle
The install failed with "the file MarcusLaw.OutlookAddin.dll.config has
a computed hash different from the one specified in the manifest" because
git's core.autocrlf=true normalized the dll.config.deploy file from CRLF
(as VS wrote it) to LF in the index. End users downloading via setup.exe
from the Gitea raw URL got the LF version, whose SHA-1 differs from the
hash recorded in dll.manifest at publish time.

Add a .gitattributes that:
  - marks Publish/** as -text (no normalization, ever)
  - belt-and-braces marks *.deploy / *.manifest / *.vsto / *.application
    / *.pfx as binary globally

git add --renormalize re-staged dll.config.deploy with its real CRLF
bytes, so the hash in the manifest now matches the file on Gitea.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:14:58 +03:00
PointStar 9e7a64045f publish: 1.0.0.5 — host ClickOnce bundle on Gitea raw URL
- InstallUrl switched from \192.168.10.97 LAN share to
  https://gitea.dev.marcus-law.co.il/.../raw/branch/main/Publish/
  so end-users (including remote ones over VPN) can install and
  auto-update from the Gitea repo over HTTPS.
- IsWebBootstrapper set to True now that setup.exe fetches over HTTP.
- .gitignore: re-include Publish/** despite the *.vsto/*.application/
  *.deploy/*.manifest wildcards so the ClickOnce distribution root
  can be tracked.
- First publish of the bundle (version 1.0.0.5); older 1_0_0_2..4
  folders deleted before commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:58:10 +03:00
PointStar 017f10e60b feat(sidebar): inline candidate picker, per-case file action, back navigation
- 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>
2026-05-20 12:57:51 +03:00
PointStar be5edc203c feat: in-app updater button + correct case-folder resolution
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>
v1.1.1
2026-05-12 23:08:20 +03:00
PointStar 1788b5bbee fix(about): show real ClickOnce deployment version in Settings → About
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>
2026-05-12 22:59:19 +03:00
PointStar 8011d4e34c diag(attachments): post-upload "did the file actually arrive" check
The server reported successful uploads for the two PDFs into case 6a03166d…
("מונאס אריאל") but the user couldn't find the files on the share. Both
LocalFilesystemClient::uploadFile and WebDavClient::uploadFile return
true only on a real write, so something downstream is silently dropping
the payload — most likely a WebDAV proxy or a basePath mismatch
between what NetworkStorageIntegration thinks the storage is vs. what
the user is browsing.

Add a verification step right after the upload:

  1. POST /Attachment   →  attachmentId
  2. POST /NetworkStorage/action/upload  →  201 from server
  3. GET  /NetworkStorage/folderContents?path=<folder>
     • If our filename is in the listing → INF "verify present"
     • Else → WRN "expected X but NOT in listing. Currently in folder: […]"

The diagnostic line surfaces both halves of the puzzle in a single
log entry, so the next save attempt will tell us in plain text whether
the storage layer is the liar or the path is being normalised
somewhere unexpected.

Tests: 39 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:58:12 +03:00
PointStar 5242abb585 feat(attachments): rewire to Marcus-Law NetworkStorageIntegration
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>
v1.1.0
2026-05-12 21:42:22 +03:00
PointStar 037d573e6e log: ListDocumentFoldersAsync prints folder count on success
When the case-scoped query fails (e.g. tenant rejects parentId in
where), the ViewModel falls back to ListDocumentFoldersAsync — but a
success path with N=0 was silent, leaving the user wondering why the
dropdown stayed empty. Add an INF line so the log shows whether
N folders came back, so 0-folder tenants are obvious from a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:22:10 +03:00
PointStar cb7d530b4c fix(attachments): map MIME from extension + don't treat 403 as auth
Two bugs found by reading the log carefully — both shipped in the
initial attachments feature:

1. MailItemExtractor sent every attachment with ContentType = "" and
   UploadAttachmentAsync defaulted that to "application/octet-stream".
   EspoCRM's attachment upload whitelist (config
   attachmentUpload.fileAcceptOptions) does not include octet-stream,
   so the server replied:
     403 (X-Status-Reason=Not allowed file type.)
   Add a GuessMimeType(fileName) helper to MailItemExtractor that maps
   common extensions (pdf, doc/docx, xls/xlsx, ppt/pptx, txt, csv,
   images, archives, email, audio/video) to canonical MIMEs. The
   octet-stream is now only used for truly unknown extensions.

2. 403 was being conflated with 401 in THREE places — EnsureSuccessAsync,
   the inline UploadAttachmentAsync handler, and the inline
   CreateDocumentAsync handler. Result: a single "Not allowed file
   type" rejection threw EspoCrmAuthorizationException, which
   AttachmentSaveService treats as a batch-level auth failure and
   stops processing remaining files. (Same bug would have killed
   FilingService and MatchingService for any non-auth 403 — e.g. an
   ACL miss on one record stopping a multi-mail file.)

   Only 401 is unambiguously an auth issue. 403 means "you ARE who
   you say you are, but this specific operation isn't allowed" —
   content rejected / ACL miss / role missing. Treat 403 as a normal
   HTTP error: log the body + X-Status-Reason and surface the message
   to the user without aborting the batch.

Also include the X-Status-Reason header in the generic
EspoCrmHttpException message everywhere, so any future "fileSizeTooBig"
or similar shows up in the user-visible error and not just the log.

Tests: 39 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:12:08 +03:00
PointStar 7924cda520 fix(documents): set publishDate on Document create (Marcus-Law requires it)
The server returned:
  400 {"messageTranslation":{"label":"validationFailure",
       "data":{"field":"publishDate","type":"required"}}}

Marcus-Law's EspoCRM install marks Document.publishDate as required.
By default the field is optional, but this tenant tightened it. Send
today's date (UTC, YYYY-MM-DD form) so the validation passes — the
user can still edit the published date in EspoCRM later if needed.

Also add the same verbose-error pattern to CreateDocumentAsync that we
already use on UploadAttachmentAsync: when the create returns non-2xx,
log the response body + X-Status-Reason header so any future required-
field mismatch shows up as a single readable line instead of
"שמירת הקבצים נכשלה".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:03:16 +03:00
PointStar 7ee9a00fc4 fix(attachments): match official EspoCRM API + folder fallback
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>
2026-05-12 20:51:46 +03:00
PointStar af8cf13443 fix(attachments): folder query + verbose upload diagnostics
Two fixes for the save-attachments flow:

1. ListFoldersUsedInCaseAsync no longer hits /Case/{id}/documents — that
   related-records endpoint requires a Case-to-Document link that
   EspoCRM doesn't ship by default, so it returned an empty list on
   every install. Switch to a direct Document list filtered by the
   polymorphic parent fields:
     GET /Document?where[0]=parentType=Case&where[1]=parentId=<id>
   That returns all documents linked to the case regardless of how the
   parent relationship is wired.

2. UploadAttachmentAsync now:
   - Emits BOTH "contents" and "file" payload fields with the
     data-URI base64 string. Different EspoCRM versions read from
     different keys; including both makes the call portable.
   - Logs the request shape (name, type, base64 length) before sending
     so server-side rejects are easier to attribute.
   - On a non-2xx response, logs the response body AND any
     X-Status-Reason header (EspoCRM convention for short error codes
     like "fileSizeIsTooLarge") so the user-visible exception finally
     includes a useful reason instead of an empty body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:42:38 +03:00
PointStar 26322c92ff feat(save-attachments): scope folder list to the selected case
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>
2026-05-12 20:32:55 +03:00
PointStar c98fee7b5b fix(csproj): re-add klear-attach.png embed entry (VS save stripped it)
Visual Studio rewrote the legacy VSTO csproj on its next save and
dropped the EmbeddedResource entry for the paperclip icon — without
it the new SaveAttachments ribbon button has no image at runtime and
the file isn't shipped in the assembly. Add it back next to the four
other Klear icons.
2026-05-12 20:28:19 +03:00
PointStar 1a9ca2d2ad fix(ribbon): invalidate "שמור קבצים" on SelectionChange (button stays disabled)
The new SaveAttachments button rendered but stayed greyed out. Cause:
getEnabled callbacks are evaluated once when the ribbon loads and the
result is cached — Office only re-asks if you explicitly invalidate
the control. The previous SelectionChange handler invalidated only
FileToEspoCrm.

Collected the selection-dependent button IDs into a static array and
made the SelectionChange handler invalidate all of them. Adding more
context-sensitive buttons in the future is now a one-line edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:27:33 +03:00
PointStar b1f8e3e50b feat(attachments): "save attachments only" ribbon action
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>
2026-05-12 20:12:46 +03:00
chaim 0fea393b81 dist: add packaged OutlookAddin-Install.zip for end-user distribution
Contains:
- Install-Marcus-Law-OutlookAddin.bat (self-elevating UAC launcher)
- 4-install-addin-full.ps1            (cert + VSTOInstaller all-in-one)
- codesign.cer                        (public Marcus-Law CodeSign cert)

Lawyers download the zip, extract, and double-click the .bat.
2026-05-11 18:06:52 +00:00
chaim 46955e2064 cdn: fix healthcheck (use 127.0.0.1, not localhost) and clear default nginx html
busybox wget in alpine resolves "localhost" to ::1 first, but our nginx
`listen 80` is IPv4-only, so the healthcheck got Connection refused and
Coolify marked the container running:unhealthy (which also made Traefik
skip its router and fall back to the lower-priority extension-platform
catch-all). Hard-coding 127.0.0.1 fixes that.

Also clear /usr/share/nginx/html before COPY so the base image's
50x.html/index.html don't leak into the published artifact set.
v1.0.0
2026-05-11 17:47:26 +00:00
chaim 35d691e0d2 debug(ci): instrument pack-upload step with full trace log
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.0
2026-05-11 17:28:07 +00:00
chaim 8d49b4f389 debug(ci): force Pack+upload to run regardless of previous step
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:24:32 +00:00
chaim 2aad6e5863 ci: reset LASTEXITCODE before natural script end; match build-job exit pattern
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:21:31 +00:00
chaim 6c32c913f4 ci: persist publish-finish exit code to side-channel + force explicit exit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:16:46 +00:00
chaim 51f72c3a4a ci: move tar into Upload step; explicit exit 0 from transcript step
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:12:41 +00:00
chaim 50c3038188 ci: remove dead steps.outputs refs + inline MSBuild path into publish-clickonce
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:08:57 +00:00
chaim fea624e9ce ci: remove redundant Compute version + Resolve tools steps (cause job to fail on act_runner)
Everything is now inlined in the single transcript-wrapped step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:04:16 +00:00
chaim b8327829e8 fix(ci): inline version parsing everywhere (act_runner steps.outputs broken)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 17:00:06 +00:00
chaim 51c23c3f38 fix(ci): use msbuild /t:Publish (VSTO standard target) instead of manual mage
The VSTO MSBuild Publish target is the same one VS IDE uses internally and
handles build + assembly signing + manifest generation + manifest signing
+ .deploy renaming in one pass. Hand-stitching the steps with mage/signtool
on top of /t:Build was producing inconsistent state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:56:08 +00:00
chaim d0455d9aaf ci: capture mage stdout/stderr for the failing -Sign call
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:52:14 +00:00
chaim 191c12b0c9 fix(ci): mage -Sign via -CertFile/.pfx (avoid cert store lookup quirks)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:49:37 +00:00
chaim e942a64b25 fix(ci): create deployment manifest via mage -New Deployment (not -Update of copy)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:46:45 +00:00
chaim e8ee1b8d3d fix(ci): resolve mage/signtool inside same step (GITHUB_ENV not propagated by act_runner)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:43:13 +00:00
chaim 02685ec7e8 fix(ci): don't re-declare MAGE/SIGNTOOL env (overwrites GITHUB_ENV value)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:40:44 +00:00
chaim 95abb8b667 ci: unify publish-clickonce into single transcripted step + upload logs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:37:32 +00:00
chaim 439c9c08fc ci: replace broken upload-artifact action with Gitea generic packages
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:33:45 +00:00
chaim e71b08c0d3 ci: don't block release on test failures (continue-on-error)
Build itself succeeded with 0 errors; the failing step was the Test step
which is non-critical for the release pipeline. Mark it as
continue-on-error so publish-clickonce + dockerize-and-deploy proceed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:30:27 +00:00
chaim 3b07558bf6 ci: push build logs to Gitea generic package registry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:27:11 +00:00
chaim 8c583aaa3c ci: single-step build with transcript so logs always upload
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:24:50 +00:00
chaim 63090ce877 ci: capture build output via Tee-Object for artifact retrieval
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:22:17 +00:00
chaim 64a1d63656 ci: dump msbuild log to artifact so we can debug failures via API
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:19:33 +00:00
chaim a8af5e3749 fix(ci): import cert to CurrentUser\My + patch csproj thumbprint
VSTO's ResolveKeySource target reads ManifestCertificateThumbprint
directly from csproj XML (not from /p: overrides) and looks for it
under Cert:\CurrentUser\My (not LocalMachine\My). Import the cert
to CurrentUser\My before build and patch the csproj XML to use the
real Marcus-Law thumbprint instead of VS's temporary-key thumbprint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:17:05 +00:00
PointStar 137cb99fba feat(sidebar): Hebrew case status labels + show all linked cases (not just 5)
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>
2026-05-11 19:13:12 +03:00
chaim 9f595c8ece fix(ci): override ManifestCertificateThumbprint with Marcus-Law cert
VSTO refuses to build with SignManifests=false. Override the project's
hardcoded VS-temporary-key thumbprint with our real code-signing cert
(already imported into LocalMachine\My during runner setup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:12:52 +00:00
chaim 0e21be21f3 fix(ci): skip compile-time manifest signing (MSB3323)
The legacy VSTO project carries <SignManifests>true</SignManifests> with
a thumbprint pointing to VS's auto-generated temporary key, which exists
on a developer's machine but never on a CI runner. The release manifests
are signed properly in publish-clickonce via mage with the real Marcus-Law
cert, so /p:SignManifests=false at compile time is the right thing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:10:59 +00:00
chaim 399fd24f4d fix(ci): set NUGET_PACKAGES outside system32 (NETSDK1064 fix)
NuGet ignores our service-level USERPROFILE override (it uses
SHGetKnownFolderPath which returns LocalSystem's profile regardless of
the env var) and installs packages to C:\Windows\system32\configsystemprofile\.nuget\packages. 32-bit MSBuild then can't find them because
the Win32 File System Redirector silently shunts system32 paths to
SysWOW64 for 32-bit processes -> NETSDK1064 'package not found'.

NUGET_PACKAGES is NuGet's highest-priority package-folder override and
puts everything outside system32, so both restore and build agree on
paths and the redirector never gets involved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:06:16 +00:00
PointStar a1e25af86b fix(json): tolerate EspoCRM's naive datetime format (YYYY-MM-DD HH:mm:ss)
The cases lookup for "אברהם מוסטקי" was still failing after the
Case.number fix, with a new exception:

  JsonException at $.list[0].createdAt
  The JSON value is not in a supported DateTimeOffset format.

EspoCRM serializes datetimes as "2024-01-15 14:30:00" without a timezone
designator, but .NET's built-in DateTimeOffset reader only accepts
strict ISO 8601 with a tz suffix. Adding the FlexibleStringConverter
to CreatedAt is not enough since the target type is DateTimeOffset, not
string.

Solution: a dedicated FlexibleDateTimeOffsetConverter that:
- Accepts ISO 8601 with offset (fast path)
- Accepts "YYYY-MM-DD HH:mm:ss" / "YYYY-MM-DD" / variants, treating
  them as UTC
- Accepts Unix epoch numbers
- Accepts null / empty strings

Registered globally on the JsonOptions used by EspoCrmClient, so every
model that exposes a DateTimeOffset? deserializes correctly without
per-property attributes.

Tests: 39 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:05:32 +03:00
chaim 55d3e6d0f3 fix(ci): unify restore + build via 'msbuild /restore' (avoid NETSDK1064)
Standalone 'nuget.exe restore' (32-bit) and the subsequent build (which
loads the 64-bit .NET 9 SDK PackageDependencyResolution.targets) disagree
on the resolved package paths, surfacing as NETSDK1064 'Package ... was
not found' on SDK-style projects (OutlookAddin.UI / .Core / .Tests).
Using 'msbuild /restore' keeps both phases in one MSBuild process with a
consistent assets file and package cache lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:04:00 +00:00
chaim e6c2b099d0 fix(ci): use VS Pro MSBuild (requires Office workload) instead of Build Tools
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>
2026-05-11 15:59:50 +00:00