Merge v17 into v18 (2026-09-08) - #1072
Merged
Merged
Conversation
* Fix and finish uSync's background processing mode (opt-in, default unchanged) The background sync path (SyncProcessingMode.Background) existed but was half-wired: it passed the per-run requestId as the long-running-operation *type* (so allowConcurrentExecution never worked), discarded the enqueue result, and returned an unrelated id the client could never use to check on the run. There was no way to poll progress or reattach after a page reload, and export-to-file downloaded before the export had actually finished. - Give each run a stable operation type (uSync:<Action>) and thread the real operation id back to the client; surface an "already running" rejection instead of reporting a phantom success. - Add GET Status/Running endpoints backed by a small per-run progress cache, so the client can poll for progress and reattach to a run it didn't start itself (e.g. after F5). Cache entries expire after an hour. - Fix export-to-file timing in the client - it now downloads on actual completion instead of immediately after the background enqueue returns. - De-static a shared Stopwatch in SyncActionService that made elapsed-time tracking a cross-run/cross-user race; key it by requestId instead. - Make status polling staleness-aware: a crashed/restarted server used to leave the client stuck reporting "running" indefinitely (Umbraco's GetStatusAsync doesn't expire a stale Enqueued/Running row the way GetByTypeAsync does). Now self-heals within Umbraco's expiration window, plus a manual "reset this view" escape hatch in the UI for when that's not fast enough. - Poll only as a SignalR fallback (not unconditionally every 2s) and back off while a run drags on - the earlier fixed interval was itself enough concurrent DB traffic to trigger SQLite 'table is locked' errors during a background import. - Keep ProcessingMode defaulting to Normal - this is opt-in, not a default change mid-version. Document the SQLite locking caveat for anyone who turns Background mode on (docs/perf/background-processing-mode.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Polish the background-run banner UI - Replace the full-width "reset this view" button with a compact icon-only dismiss (top-right of the alert), styled to match the banner's own colours so it blends in rather than competing for attention - it's a rarely-needed escape hatch, not a primary action. - Move the banner to the top of the page so it's the first thing seen regardless of scroll position or other banners/content below it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Make ISyncManagementService additions truly non-breaking Give GetOperationStatusAsync/GetRunningOperationAsync default interface implementations instead of requiring every implementer to add them. ISyncManagementService is public, so an external implementation of it would otherwise fail to compile against this change; the defaults report "not supported"/"nothing running" - safe, honest answers for an implementation that predates background processing - and uSyncManagementService's own explicit overrides still take precedence as normal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Support a load-balanced backoffice in background processing mode
Background mode already ran the full handler pipeline on one server, but
progress/status was served from an in-process cache, so a status request
landing on a different server (round-robin, no sticky sessions) came back
empty. The client also gated its polling fallback on the SignalR socket
being "connected", which under a load-balanced backoffice is true even when
the socket is pinned to the wrong server - so a completed run could hang the
UI forever.
- Mirror background-run progress into the shared umbracoLongRunningOperation
row (summaries on every step, full results once complete) so status/running
checks resolve correctly from any server.
- Poll on SignalR message *liveness*, not socket state, so a dead-in-practice
connection falls back to polling instead of hanging.
- Wrap the startup import in ILongRunningOperationService.RunAsync so exactly
one server runs it - the previous ServerRole.Subscriber-only guard doesn't
fire under the documented load-balanced-backoffice setup, where every
server reports SchedulingPublisher.
- Warn at startup if running Normal mode with multiple active servers, since
that mode can't be made safe under a load-balanced backoffice.
- Document the load-balancing requirements and limitations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Tighten background-mode poll interval to 1s (from 5s)
Now that polling is gated on SignalR message liveness rather than being
the sole progress mechanism, a 5s starting interval leaves the progress
bar visibly frozen for the first several seconds of any run whose socket
isn't delivering (e.g. under a load-balanced backoffice with no SignalR
backplane). Confirmed via manual load-balanced testing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Surface a progress message during polling fallback, not just SignalR
usync-progress-box only ever populated its message line from a live
SignalR 'update' push. Under polling fallback (e.g. a background run
whose SignalR socket is pinned to a different server behind a load
balancer), that left the box blank for the whole run even though the
handler icons themselves were updating correctly.
Add a small public setter on uSyncSignalRContext that publishes into the
same update state/observable the progress box already consumes, so no
component changes are needed. The status poll calls it with the
per-handler-step message ("Processing Import"/"Completed") whenever a
poll returns one - coarser than SignalR's per-item message, but visibly
better than nothing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* update message / fix obsolete
* Change processing message.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…views fallback (#1059) When importing a template we now ask Umbraco's ITemplateService for the file content first (the way Umbraco does it), before falling back to the view filesystem, and finally to a placeholder when the views are compiled. Two things had to be fixed for that fallback to actually work: * TemplateService.GetFileContentStreamAsync never returns null - the repository hands back Stream.Null when the file is missing. Checking for null meant we always took the "found it" branch, read an empty string, and created the template with no content - so the view filesystem fallback and the compiled-views placeholder below it were unreachable, and a genuinely missing template file was silently imported as an empty one instead of failing. * The placeholder we hand Umbraco is parsed by TemplateContentParserService to work out the master template, and its regex requires a trailing semi-colon. "{ Layout = "master" }" never matched, so the parent was never set. It is now written as valid razor - @{ Layout = "master.cshtml"; } - and root templates get Layout = null; rather than an empty alias. Also aligns ViewPath (and the handler's equivalent) with how Umbraco names the view file - the alias verbatim, see TemplateRepository.SetVirtualPath - rather than stripping spaces, so we don't look for or delete the wrong file. Tidy up while in here: * remove the CleanseNode override - it looked for a "Content" element, but the element is "Contents", so it has never done anything. TemplateTracker explicitly tracks /Contents, so stripping it now would make the hash and the tracker disagree - behaviour is unchanged, the dead code is gone. * drop the unused _shortStringHelper field (constructor signature kept) * flatten the dead null checks in ShouldGetContentFromNode * make GetContentFromFile private and tidy its stream disposal * remove a stray Lucene using Tests: covers creating a child template when there is no file on disk, asserting the placeholder parses back to the parent alias using Umbraco's own parser, plus the root-template and views-not-compiled cases. BuildFileSystems now sets up IIOHelper.PathStartsWith, without which PhysicalFileSystem throws instead of reporting a missing file. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…view (#1060) Word-diffing pretty-printed JSON gave users a wall of text for a single block insertion or property edit inside a block list/grid. Classify each change detail (masked, single-sided, JSON, XML/text lines, word, scalar) and render JSON changes as a flat list of leaf changes, matching block array entries by identity (key/udi/alias+culture) rather than index so a single insertion doesn't make every element look changed. Long text/XML gets a hand-rolled line diff with collapsed unchanged runs, since Umbraco only re-exports diffWords. Each change is now an expandable row instead of a table cell, collapsed by default once there is more than one, with a raw-values toggle for the full old/new text. Also fixes two accidental-precedence bugs in the change count check, and a case where Error/Warning notice details were word-diffed against ''. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Wires up package-build.yml per Jumoo/nightly-feed-config: repacks
each package with the {majorMinorPatch}-build.{run_number} version
scheme and pushes to https://nightly.jumoo.uk on every push to
v17/main, gated to push events so a workflow_dispatch rehearsal
never publishes a real nightly build.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
It still listed the old gulp build toolchain (gulp, gulp-sourcemaps, etc.) even though package.json dropped those dependencies long ago. No workflow installs from the repo root (only usync-assets and history-client), so the lockfile was just dead weight — and it's what Dependabot's security-update job kept failing on, since it can't upgrade postcss past 7.x while @gulp-sourcemaps/identity-map pins it to ^7.0.16. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…terTemplateAlias renamed to LayoutTemplateAlias Forward-ported from v17's #1059 (Get template content from the template service, and fix the compiled-views fallback). Umbraco 18's ITemplateContentParserService renamed MasterTemplateAlias to LayoutTemplateAlias; uSync.BackOffice/SyncHandlers/Handlers/TemplateHandler.cs already used the new name, only these tests still referenced the old one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Forward-ports the outstanding v17 work onto v18, plus migrates v18's nightly
feed to nightly.jumoo.uk (replacing v18's separate Azure Artifacts publish
job).
Since the last forward merge (#1050) was squash-merged, git's merge-base
between the branches is stale (predates the v17/v18 split), so a plain
git mergewould replay years of already-diverged history as falseconflicts. Commits below were cherry-picked individually instead, onto a
branch off
v18/main.publish-nightlyjob with v17'snightly.jumoo.uk(R2-backed) feed, keeping v18's existing all-projectspackage-upjob as the source of packagespackage-lock.jsonPlus a fix-up commit:
TemplateSerializerTestsused the oldTemplateContentParserService.MasterTemplateAliasname; Umbraco 18 renamedit to
LayoutTemplateAlias(the product code already used the new name viaITemplateContentParserService, only these new tests needed updating).Not ported (per discussion): the dependency-bump commits (#1057, #1061,
#1062, #1063, #1064) — v18's client lockfile has already diverged/moved
ahead independently, so these should go through dependabot on v18 directly
rather than being forward-ported.
Already applied to v18 independently, so skipped: #1052 (landed as
#1053), #1056 and #1058 (v18 already achieved the same effect via different
commits), and two purely-v17-specific commits (
cbc3726eworkflow renames,69581edbnightly pack-list additions) that are moot once #1068 wasresolved against v18's own
package-upjob.Test plan
dotnet build ./uSync.slnx -c Release— succeeds, 0 errorsdotnet test ./uSync.Tests/uSync.Tests.csproj -c Release— 218/218 passednpm ci && npm run typescript:build && npm run build— succeedsBuild and Package/ PR checksPlease merge with a merge commit, not squash, so the next forward-port
run can find this baseline directly from ancestry instead of parsing a
squashed commit body.
🤖 Generated with Claude Code