Begin modeling pictures in MiniLcm#2300
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
C# Unit Tests165 tests 165 ✅ 20s ⏱️ Results for commit 759a337. ♻️ This comment has been updated with latest results. |
|
@CodeRabbit - Please examine how MediaUri is handled in audio files, then suggest how to handle the MediaUri property in the Picture class. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Search for MediaUri in the codebase ==="
rg -n "MediaUri" --type cs -C 3
echo ""
echo "=== Search for audio-related model files ==="
fd -e cs -i "audio" --exec echo {}
echo ""
echo "=== Search for Picture model files ==="
fd -e cs -i "picture" --exec echo {}
echo ""
echo "=== Search for ExampleSentence or audio model definitions ==="
rg -n "class.*Audio\|AudioFile\|Sound" --type cs -C 5Length of output: 45536 🏁 Script executed: #!/bin/bash
echo "=== Find MiniLcm model files ==="
fd -e cs . --search-path "MiniLcm" | head -60
echo ""
echo "=== Search for all MediaUri occurrences ==="
rg -rn "MediaUri" -C 5Length of output: 57201 🏁 Script executed: #!/bin/bash
echo "=== Search for PictureFileRA (LCM picture file property) ==="
rg -n "PictureFileRA\|InternalPath\|PictureFolder\|Pictures folder\|TypeToLinkedFolder" --type cs -C 3
echo ""
echo "=== Look at UpdatePictureProxy in full ==="
cat backend/FwLite/FwDataMiniLcmBridge/Api/UpdateProxy/UpdatePictureProxy.cs
echo ""
echo "=== Look at CreatePictureChange in full ==="
cat backend/FwLite/LcmCrdt/Changes/CreatePictureChange.cs
echo ""
echo "=== Look at PictureSync in full ==="
cat backend/FwLite/MiniLcm/SyncHelpers/PictureSync.cs
echo ""
echo "=== Search for TypeToLinkedFolder and AudioVisualFolder constant ==="
rg -n "TypeToLinkedFolder\|AudioVisualFolder\|PictureFolder\|Pictures\b" --type cs -C 2Length of output: 5543 🏁 Script executed: #!/bin/bash
echo "=== Find AudioVisualFolder and TypeToLinkedFolder in FwDataMiniLcmApi.cs ==="
grep -n "AudioVisual\|TypeToLinked\|PictureFile\|InternalPath\|Pictures\|LinkedFiles\|CmFile\|cmFile" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs | head -60
echo ""
echo "=== View lines around the folder/type mapping function ==="
grep -n "TypeToLinkedFolder\|AudioVisualFolder\|const string\|private.*Folder" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== Grep for picture file path references in FW LCM ==="
rg -n "PictureFile\|CmFile\|InternalPath\|\.PictureFilePath\|picture\..*[Ff]ile\|\.ImageFile" \
backend/FwLite/FwDataMiniLcmBridge/ -C 2
echo ""
echo "=== Check what LCM picture interface looks like ==="
rg -rn "PictureFileRA\|ICmPicture\|CmPicture" --type cs -C 3 | head -80Length of output: 3346 🏁 Script executed: #!/bin/bash
echo "=== LcmToPicture method context ==="
grep -n "LcmToPicture\|private.*Picture\|FromLcmPicture\|ToMiniLcmPicture\|ICmPicture" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== View lines 795-830 of FwDataMiniLcmApi.cs (picture mapping) ==="
sed -n '795,835p' backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== View lines 1940-1960 (TypeToLinkedFolder) ==="
sed -n '1940,1960p' backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== Search for ICmPicture file-path related properties in LCM NuGet/dlls ==="
rg -rn "\.PictureFile\|picture\.\|ICmPicture\b" --type cs backend/FwLite/ -C 2 | head -60
echo ""
echo "=== Check the SIL LCModel ICmPicture or CmPicture in dlls/packages ==="
find . -name "*.cs" | xargs grep -l "ICmPicture" 2>/dev/null | head -10
echo ""
echo "=== View LfClassicMiniLcmApi picture mapping (line ~350) ==="
grep -n "picture\.\|Picture\|MediaUri\|CmFile\|InternalPath" backend/LfClassicData/LfClassicMiniLcmApi.cs | head -30Length of output: 4911 🏁 Script executed: #!/bin/bash
echo "=== Search for ICmFile usage anywhere ==="
rg -rn "ICmFile\|PictureFileRA\|InternalPath\|\.PicturePath\b" --type cs -C 3 | head -60
echo ""
echo "=== Look for how FW code sets picture file path - CreatePicture context ==="
sed -n '1810,1850p' backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== Search for Pictures folder const or PictureFolder ==="
rg -rn "const.*[Pp]icture\|PictureFolder\b" --type cs -C 2
echo ""
echo "=== Look for nuget packages that contain LCModel ==="
find . -name "*.csproj" | xargs grep -l "LCModel\|liblcm\|SIL.LCModel" 2>/dev/null | head -5
echo ""
echo "=== Check any existing usages that access picture.PictureFile ==="
rg -rn "picture\." --type cs backend/FwLite/FwDataMiniLcmBridge/ -C 1 | head -40Length of output: 5205
How
|
| Step | Audio | Picture |
|---|---|---|
| FW storage | MultiString[audioWs] = relative filename |
ICmPicture.PictureFileRA.InternalPath = relative path |
| Subfolder | LinkedFilesRootDir/AudioVisual/ |
LinkedFilesRootDir/Pictures/ |
| Read (FW→MiniLcm) | mediaAdapter.MediaUriFromPath(fullPath) |
same |
| Write (MiniLcm→FW) | mediaAdapter.PathFromMediaUri() → update string |
same, but find/create ICmFile, set PictureFileRA |
| NotFound guard | UpdateDictionaryProxy.ShouldSet skips write |
SetLcmPictureFile returns early if MediaUri.NotFound |
|
Current code is partly working, but I haven't yet told Entity Framework how to map a Picture object into the DB. Which results in this exception when I run a sync: System.InvalidOperationException: The property 'Picture.MediaUri' could not be mapped because it is of type 'MediaUri', which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IConventionTypeBase structuralType, IConventionModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidatePropertyMapping(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal.SqliteModelValidator.Validate(IModel model, IDiagnosticsLogger`1 logger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.CreateModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime)
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkRelationalServicesBuilder.<>c.<TryAddCoreServices>b__3_3(IServiceProvider p)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService(IInfrastructure`1 accessor, Type serviceType)
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor)
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.get_Dependencies()
at Microsoft.EntityFrameworkCore.Infrastructure.DatabaseFacade.Microsoft.EntityFrameworkCore.Storage.IDatabaseFacadeDependenciesAccessor.get_Dependencies()
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.GetFacadeDependencies(DatabaseFacade databaseFacade)
at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.CloseConnectionAsync(DatabaseFacade databaseFacade)
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in /src/backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 181
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in /src/backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 194
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in /src/backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 194
at FwHeadless.Services.SyncWorker.SetupCrdtProject(String crdtFile, IProjectLookupService projectLookupService, Guid projectId, CrdtProjectsService projectsService, String projectFolder, Guid fwProjectId, String lexboxUrl) in /src/backend/FwHeadless/Services/SyncHostedService.cs:line 371
at FwHeadless.Services.SyncWorker.ExecuteSync(CancellationToken stoppingToken, Boolean onlyHarmony) in /src/backend/FwHeadless/Services/SyncHostedService.cs:line 188
at FwHeadless.Services.SyncWorker.ExecuteSync(CancellationToken stoppingToken, Boolean onlyHarmony) in /src/backend/FwHeadless/Services/SyncHostedService.cs:line 276
at FwHeadless.Services.SyncHostedService.ExecuteAsync(CancellationToken stoppingToken) in /src/backend/FwHeadless/Services/SyncHostedService.cs:line 33 |
|
@CodeRabbit - Look at the exception in #2300 (comment) and look at the Entity Framework Core documentation. Summarize how to map the MediaUri property, and provide links to the documentation explaining how to map properties in EF Core. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Find MediaUri type definition ==="
rg -n "class MediaUri\|struct MediaUri\|record MediaUri" --type cs -C 5
echo ""
echo "=== Find existing EF Core value converters in LcmCrdt ==="
rg -n "ValueConverter\|HasConversion\|OnModelCreating\|EntityTypeBuilder\|modelBuilder" --type cs backend/FwLite/LcmCrdt/ -C 3 | head -100
echo ""
echo "=== Find DbContext in LcmCrdt ==="
fd -e cs . backend/FwLite/LcmCrdt/ | xargs grep -l "DbContext\|OnModelCreating" 2>/dev/null
echo ""
echo "=== Check if MediaUri implements any interface or has implicit conversion ==="
rg -n "implicit\|explicit\|operator\|ToString\|Parse\|TryParse" --type cs -l | xargs grep -l "MediaUri" 2>/dev/nullLength of output: 3199 🌐 Web query:
Length of output: 20413 🏁 Script executed: #!/bin/bash
echo "=== MediaUri definition - search all file types ==="
rg -rn "MediaUri" --type cs | grep -i "class\|record\|struct" | head -20
echo ""
echo "=== Find the file that defines MediaUri ==="
rg -rn "MediaUri" --type cs -l
echo ""
echo "=== Read LcmCrdtDbContext.cs ==="
cat backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs
echo ""
echo "=== Read MiniLcm MediaUri source file ==="
fd -e cs "MediaUri" | head -5Length of output: 6757 🏁 Script executed: #!/bin/bash
echo "=== Read MediaUri.cs ==="
cat backend/FwLite/MiniLcm/Media/MediaUri.csLength of output: 1864
The fixAdd a converter class and register it in // In LcmCrdtDbContext.ConfigureConventions:
protected override void ConfigureConventions(ModelConfigurationBuilder builder)
{
builder.Properties<MultiString>()
.HaveColumnType("jsonb")
.HaveConversion<MultiStringDbConverter>();
builder.Properties<RichString?>()
.HaveColumnType("jsonb")
.HaveConversion<RichStringDbConverter>();
builder.Properties<RichMultiString>()
.HaveColumnType("jsonb")
.HaveConversion<RichMultiStringDbConverter>();
builder.Properties<WritingSystemId>()
.HaveConversion<WritingSystemIdConverter>();
// Add this:
builder.Properties<MediaUri>()
.HaveConversion<MediaUriConverter>();
}
// Add this converter class alongside the others:
private class MediaUriConverter() : ValueConverter<MediaUri, string>(
uri => uri.ToString(),
str => new MediaUri(str));
Relevant EF Core documentation
After adding the converter, you'll also need to create a new EF migration ( |
|
@CodeRabbit - When I run Please analyze the port-forwarding setup for the |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Find Taskfile for fw-lite-web ==="
fd -i "Taskfile" | head -20
echo ""
echo "=== Search for fw-lite-web in Taskfiles ==="
rg -n "fw-lite-web\|fw_lite_web" --type yaml -C 5
echo ""
echo "=== Search for port 3050 references ==="
rg -rn "3050" -C 3 | head -60
echo ""
echo "=== Search for port forwarding or devtunnel setup ==="
rg -rn "devtunnel\|port.forward\|portforward\|forward.*3050\|3050.*forward" -C 5 | head -60Length of output: 28033 🏁 Script executed: #!/bin/bash
echo "=== Search for OIDC/auth configuration with localhost ==="
rg -rn "localhost:3050\|localhost.*3050\|3050.*localhost" -C 3 | head -80
echo ""
echo "=== Search for OidcAuthority or Authority config ==="
rg -rn "Authority\|OidcAuthority\|OpenIdConnect\|oidc" --type cs -C 3 | head -80
echo ""
echo "=== Search for auth login endpoint ==="
rg -rn "api/auth/login\|AuthController\|login.*controller" --type cs -C 5 | head -60Length of output: 637 🏁 Script executed: #!/bin/bash
echo "=== Root Taskfile.yml - search for fw-lite-web or related ==="
grep -n "fw-lite-web\|fw_lite_web\|fw-lite\|fwlite\|5137\|3050\|https-proxy\|devtunnel\|forward" Taskfile.yml | head -60
echo ""
echo "=== backend/FwLite/Taskfile.yml - full content ==="
cat backend/FwLite/Taskfile.yml
echo ""
echo "=== frontend/https-proxy/vite.config.ts - full content ==="
cat frontend/https-proxy/vite.config.ts
echo ""
echo "=== backend/Taskfile.yml - search for fw-lite or 3050 or 5137 ==="
grep -n "fw-lite\|3050\|5137\|https-proxy\|devtunnel\|forward" backend/Taskfile.yml | head -40Length of output: 10948 🏁 Script executed: #!/bin/bash
echo "=== Search for 5137 in all files ==="
rg -rn "5137" -C 2 | head -60
echo ""
echo "=== Search for lexbox server URL config in appsettings or launchSettings ==="
fd -e json "appsettings\|launchSettings" backend/FwLite/ | head -20
fd -e json "appsettings\|launchSettings" backend/FwLite/ | xargs grep -l "localhost\|lexbox\|3050\|Authority\|oidc\|auth" 2>/dev/null | head -10
echo ""
echo "=== Search for auth login in FwLite web project ==="
rg -rn "login\|auth\|oidc\|Authority" backend/FwLite/FwLiteWeb/ -C 3 | head -80
echo ""
echo "=== Find FwLiteWeb project structure ==="
fd . backend/FwLite/FwLiteWeb/ | head -40Length of output: 14226 🏁 Script executed: #!/bin/bash
echo "=== AuthRoutes.cs ==="
cat backend/FwLite/FwLiteWeb/Routes/AuthRoutes.cs
echo ""
echo "=== Program.cs ==="
cat backend/FwLite/FwLiteWeb/Program.cs
echo ""
echo "=== appsettings.json files in FwLiteWeb ==="
fd -e json "appsettings" backend/FwLite/FwLiteWeb/ | xargs -I{} sh -c 'echo "--- {} ---"; cat {}'
echo ""
echo "=== frontend Taskfile.yml - fw-lite-infr task ==="
grep -n "fw-lite-infr\|https-proxy\|3050\|lexbox" frontend/Taskfile.yml -A 10 | head -60
echo ""
echo "=== FwLiteWebConfig.cs ==="
cat backend/FwLite/FwLiteWeb/FwLiteWebConfig.cs
echo ""
echo "=== ServerRedirectUrlProvider.cs ==="
cat backend/FwLite/FwLiteWeb/Services/ServerRedirectUrlProvider.csLength of output: 9618 🏁 Script executed: #!/bin/bash
echo "=== Find AuthService and AuthConfig ==="
fd -e cs "AuthService\|AuthConfig\|OAuthService" backend/FwLite/ | head -10
echo ""
echo "=== AuthConfig.cs ==="
fd -e cs "AuthConfig" backend/FwLite/ | xargs cat
echo ""
echo "=== AuthService.cs ==="
fd -e cs "AuthService" backend/FwLite/ | xargs cat
echo ""
echo "=== OAuthService.cs ==="
fd -e cs "OAuthService" backend/FwLite/ | xargs cat
echo ""
echo "=== UrlContext.cs ==="
cat backend/FwLite/FwLiteWeb/UrlContext.cs
echo ""
echo "=== Search for ValidateAuthority or ValidIssuers or MSAL config ==="
rg -rn "ValidateAuthority\|ValidIssuers\|PublicClientApplicationBuilder\|WithAuthority\|OidcWellKnown\|openid-configuration" --type cs -C 5 | head -80
echo ""
echo "=== Root Taskfile lines 113-120 (fw-lite-web task definition) ==="
sed -n '100,130p' Taskfile.ymlLength of output: 22755 🏁 Script executed: #!/bin/bash
echo "=== Find OAuthClientFactory and OAuthClient ==="
fd -e cs "OAuthClient" backend/FwLite/ | head -10
echo ""
echo "=== OAuthClientFactory.cs ==="
fd -e cs "OAuthClientFactory" backend/FwLite/ | xargs cat
echo ""
echo "=== OAuthClient.cs ==="
fd -e cs -x echo {} backend/FwLite/ | xargs grep -l "OAuthClient" | grep -v "Factory\|Service\|Test" | head -5 | xargs cat
echo ""
echo "=== Search for PublicClientApplication or WithAuthority in FwLiteShared ==="
rg -rn "PublicClientApplication\|WithAuthority\|BuildApplication\|IPublicClientApplication" --type cs backend/FwLite/ -C 5 | head -80
echo ""
echo "=== Search for ValidateIssuer or authority in appsettings for the local lexbox ==="
fd -e json "appsettings" backend/ | xargs grep -l "localhost\|3050\|3000\|authority\|Authority" 2>/dev/null | head -5
echo ""
echo "=== Check if local lexbox server config has ASPNETCORE_FORWARDEDHEADERS ==="
fd -i "docker-compose" . | xargs grep -l "lexbox\|3000\|3050" 2>/dev/null | head -5
fd "docker-compose" . | head -10Length of output: 60547
Root CauseThe data flow is: The chain of events that causes the mismatch:
Fix OptionsOption A — Fix in the vite proxy (self-contained, no server changes needed)Intercept the OIDC discovery response and rewrite import { gunzipSync } from 'zlib';
// Inside the lexboxServer ProxyOptions configure callback, after existing code:
proxy.on('proxyRes', (proxyRes, req, res) => {
if (!req.url?.includes('openid-configuration')) return;
const chunks: Buffer[] = [];
const originalWrite = res.write.bind(res);
const originalEnd = res.end.bind(res);
// Intercept the streamed response
proxyRes.on('data', (chunk: Buffer) => chunks.push(chunk));
proxyRes.on('end', () => {
const body = Buffer.concat(chunks).toString('utf-8');
const rewritten = body.replaceAll(
/"issuer"\s*:\s*"http:\/\//g,
'"issuer":"https://'
);
const newBody = Buffer.from(rewritten, 'utf-8');
res.setHeader('Content-Length', newBody.length);
originalWrite(newBody);
originalEnd();
});
});
A simpler alternative using vite's built-in transformer hook (if supported by your vite version): // In vite.config.ts server.proxy options:
'/': {
...lexboxServer,
// Add a response rewriter for OIDC discovery only
rewrite: (path) => path,
}Option B — Fix in the local lexbox server (correct architectural fix)Enable environment:
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"This makes ASP.NET Core automatically enable forwarded headers, so the lexbox server generates Or in {
"ForwardedHeaders": {
"ForwardedHeaders": "XForwardedFor, XForwardedProto"
}
}Option C — Fix in
|
C# FwHeadless Unit Tests48 tests 48 ✅ 17s ⏱️ Results for commit 759a337. ♻️ This comment has been updated with latest results. |
|
@CodeRabbit - Syncing pictures using commit 3a729a7 produced the following exception: System.NotSupportedException: type JsonPatchChange`1 does not support NewEntity, because it inherits from EditChange, this means it must be called with a from an existing entity, not a newly generated one
at SIL.Harmony.Changes.EditChange`1.NewEntity(Commit commit, IChangeContext context) in backend/harmony/src/SIL.Harmony/Changes/EditChange.cs:line 8
at SIL.Harmony.Changes.Change`1.SIL.Harmony.Changes.IChange.NewEntity(Commit commit, IChangeContext context) in backend/harmony/src/SIL.Harmony/Changes/Change.cs:line 51
at SIL.Harmony.SnapshotWorker.ApplyCommitChanges(SortedSet`1 commits) in backend/harmony/src/SIL.Harmony/SnapshotWorker.cs:line 78
at SIL.Harmony.SnapshotWorker.UpdateSnapshots(SortedSet`1 commits) in backend/harmony/src/SIL.Harmony/SnapshotWorker.cs:line 54
at SIL.Harmony.DataModel.UpdateSnapshots(CrdtRepository repo, SortedSet`1 commitsToApply) in backend/harmony/src/SIL.Harmony/DataModel.cs:line 217
at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable`1 commits) in backend/harmony/src/SIL.Harmony/DataModel.cs:line 159
at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable`1 commits) in backend/harmony/src/SIL.Harmony/DataModel.cs:line 161
at SIL.Harmony.DataModel.SIL.Harmony.ISyncable.AddRangeFromSync(IEnumerable`1 commits) in backend/harmony/src/SIL.Harmony/DataModel.cs:line 161
at SIL.Harmony.SyncHelper.SyncWith(ISyncable localModel, ISyncable remoteModel, JsonSerializerOptions serializerOptions) in backend/harmony/src/SIL.Harmony/SyncHelper.cs:line 42
at SIL.Harmony.DataModel.SyncWith(ISyncable remoteModel) in backend/harmony/src/SIL.Harmony/DataModel.cs:line 377
at FwLiteShared.Sync.SyncService.ExecuteSync(Boolean skipNotifications) in backend/FwLite/FwLiteShared/Sync/SyncService.cs:line 89
at FwLiteShared.Projects.CombinedProjectsService.<>c.<<DownloadProject>b__16_1>d.MoveNext() in backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs:line 202
--- End of stack trace from previous location ---
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 175
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 191
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 194
at LcmCrdt.CrdtProjectsService.CreateProject(CreateProjectRequest request) in backend/FwLite/LcmCrdt/CrdtProjectsService.cs:line 194
at FwLiteShared.Projects.CombinedProjectsService.<>c__DisplayClass16_0.<<DownloadProject>b__0>d.MoveNext() in backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs:line 196
--- End of stack trace from previous location ---
at FwLiteShared.Projects.CombinedProjectsService.DownloadProject(ProjectModel project) in backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs:line 196If possible, analyse why NewEntity is being called on a |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== PictureSync.cs ==="
fd -e cs "PictureSync" | xargs cat
echo ""
echo "=== CreatePictureChange.cs ==="
fd -e cs "CreatePictureChange" | xargs cat
echo ""
echo "=== Search for where pictures are synced from FW to CRDT ==="
rg -n "CreatePicture\|AddPicture\|picture.*sync\|sync.*picture" --type cs -i backend/FwLite/ -C 3 | head -80
echo ""
echo "=== How other entity syncs look - e.g. EntrySync ==="
fd -e cs "EntrySync" backend/FwLite/ | xargs cat 2>/dev/null | head -100
echo ""
echo "=== How other entity syncs look - e.g. SenseSync ==="
fd -e cs "SenseSync" backend/FwLite/ | xargs cat 2>/dev/null | head -100Length of output: 10820 🏁 Script executed: #!/bin/bash
echo "=== Find CrdtMiniLcmApi picture-related methods ==="
fd -e cs "CrdtMiniLcmApi" backend/FwLite/ | xargs grep -n "Picture\|picture" | head -60
echo ""
echo "=== Find CreatePicture and UpdatePicture implementations in LcmCrdt ==="
rg -n "CreatePicture\|UpdatePicture\|DeletePicture\|MovePicture" --type cs backend/FwLite/LcmCrdt/ -C 5
echo ""
echo "=== Find how JsonPatchChange is created/used ==="
rg -n "JsonPatchChange\b" --type cs backend/FwLite/ -C 3
echo ""
echo "=== CrdtFwdataProjectSyncService - picture related sync code ==="
rg -n "Picture\|picture" --type cs backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs -C 3
echo ""
echo "=== Check UpdatePicture in LcmCrdt and what change type it creates ==="
rg -n "async.*UpdatePicture\|Task.*UpdatePicture" --type cs backend/FwLite/ -C 10Length of output: 14639
Root cause
// Overload 1 — takes an UpdateObjectInput, creates JsonPatchChange<Picture>
public async Task<Picture> UpdatePicture(Guid entryId, Guid senseId, Guid pictureId, UpdateObjectInput<Picture> update)
{
var patchChange = new JsonPatchChange<Picture>(pictureId, jsonPatch); // ← EditChange, no NewEntity support
await AddChange(patchChange);
...
}
// Overload 2 — takes before/after Pictures, calls PictureSync.Sync (single-picture version)
public async Task<Picture> UpdatePicture(Guid entryId, Guid senseId, Picture before, Picture after, ...)
{
await PictureSync.Sync(entryId, senseId, before, after, api ?? this); // ← ONLY calls UpdatePicture, NEVER CreatePicture
...
}The single-picture The sync code on the FW headless server is taking a path that calls the "before/after Picture" Where to lookThe FW headless sync must have new code (in this PR) in either // CORRECT — uses DiffOrderable, calls CreatePicture for new pictures
await PictureSync.Sync(entryId, senseId, beforeSense.Pictures, afterSense.Pictures, api);…it's going through something like: // WRONG — single-picture overload, always emits JsonPatchChange, never CreatePicture
foreach (var (before, after) in matchedPictures)
await api.UpdatePicture(entryId, senseId, before, after);
// or
await PictureSync.Sync(entryId, senseId, beforePicture, afterPicture, api);Suggested fixOption A — In // In SenseSync.Sync, alongside the ExampleSentenceSync call:
changes += await PictureSync.Sync(entryId,
beforeSense.Id,
beforeSense.Pictures,
afterSense.Pictures,
api);Option B — If the FW sync has a custom loop in The key invariant to enforce: 🧠 Learnings used |
Compiles, but some logic (most notably MediaUri handling) is incomplete
Next up will be writing tests to make sure we can properly two-way sync a picture from FW Classic, i.e. that the MediaUri handling is correct.
|
Rebased on top of current |
|
Huh. Look commit 2ce7ec8 and, specifically, at the DB migration. Why did it add the HomographNumber property? I might have had a local DB that's not up-to-date. I might have to delete that migration and create a new one. |
|
Force-pushed to get rid of commit 2ce7ec8 so the migration won't add a duplicate HomographNumber column. |
MiniLcmApiWriteNormalizationWrapper is new on develop branch since I started this PR, so I didn't have the Picture CRUD API calls for it.
|
@CodeRabbit - Look at the latest commit. When I sync a project that has pictures in the .fwdata file, the CRDT version of the project comes out with |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Latest commit info ==="
git log --oneline -5
echo ""
echo "=== SenseSync.cs ==="
fd -e cs "SenseSync" backend/FwLite/ | xargs cat
echo ""
echo "=== PictureSync.cs ==="
fd -e cs "PictureSync" backend/FwLite/ | xargs cat
echo ""
echo "=== CrdtFwdataProjectSyncService.cs - picture-related lines ==="
rg -n "Picture\|picture" --type cs backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs -C 3
echo ""
echo "=== FwDataMiniLcmApi.cs - GetPictures / picture mapping ==="
rg -n "GetPictures\|GetSensePictures\|Pictures\|FromLcmPicture" --type cs backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs -C 3
echo ""
echo "=== CrdtMiniLcmApi.cs - GetPictures / picture ==="
rg -n "GetPictures\|Pictures\|Picture" --type cs backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs -C 3 | head -80Length of output: 6711 🏁 Script executed: #!/bin/bash
echo "=== FwDataMiniLcmApi - Sense mapping (ToMiniLcmSense / LcmToSense) ==="
rg -n "ToMiniLcmSense\|LcmToSense\|FromLcmSense\|Sense\b.*=>.*new Sense\|new Sense\b" \
--type cs backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs -C 8
echo ""
echo "=== FwDataMiniLcmApi - all lines mentioning Sense OR Picture (case insensitive) ==="
grep -in "picture\|\.Pictures\b\|GetPictures" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs | head -40
echo ""
echo "=== CrdtMiniLcmApi - how Sense is returned / Pictures loaded ==="
grep -n "Picture\|\.Pictures\b\|GetPictures\|IncludePictures\|Include.*Picture" \
backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs | head -40
echo ""
echo "=== CrdtFwdataProjectSyncService.cs - Sense sync block (search for SenseSync) ==="
grep -n "SenseSync\|GetSenses\|GetEntries\|beforeSense\|afterSense\|Sense\b" \
backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs | head -60
echo ""
echo "=== Check if IMiniLcmApi interface has GetPictures ==="
rg -n "GetPictures\|Pictures\b" --type cs backend/FwLite/MiniLcm/IMiniLcmApi.cs -C 2
echo ""
echo "=== Check Sense model - does it include Pictures ==="
rg -n "Pictures\b" --type cs backend/FwLite/MiniLcm/Models/Sense.cs -C 3Length of output: 7429 🏁 Script executed: #!/bin/bash
echo "=== FwDataMiniLcmApi - find the sense→MiniLcm mapping (lines around FromLcmSense/ToSense) ==="
grep -n "Sense\|sense\b" backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs | \
grep -iv "ExampleSentence\|HasSense\|#" | head -60
echo ""
echo "=== FwDataMiniLcmApi - lines 780-850 (around FromLcmPicture / sense mapping) ==="
sed -n '780,860p' backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs
echo ""
echo "=== FwDataMiniLcmApi - search for ILexSense or PicturesOS usage ==="
grep -n "PicturesOS\|ILexSense\|lexSense\.\|new Sense\b\|return new Sense" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs | head -40
echo ""
echo "=== CrdtFwdataProjectSyncService.cs - full content ==="
cat backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.cs
echo ""
echo "=== CrdtMiniLcmApi - how Sense is loaded (with Pictures EF include) ==="
grep -n "Include\|Sense\b\|sense\b" backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs | \
grep -i "include\|query\|dbset\|context\|from\|where\|join" | head -30Length of output: 21784
Root Cause
// FwDataMiniLcmApi.cs - FromLexSense (lines 780–791)
var s = new Sense
{
Id = sense.Guid,
EntryId = sense.Entry.Guid,
Gloss = FromLcmMultiString(sense.Gloss),
Definition = FromLcmMultiString(sense.Definition),
PartOfSpeech = pos is null ? null : FromLcmPartOfSpeech(pos),
PartOfSpeechId = pos?.Guid,
SemanticDomains = [.. sense.SemanticDomainsRC.Select(FromLcmSemanticDomain)],
ExampleSentences = [.. sense.ExamplesOS.Select(sentence => FromLexExampleSentence(sense.Guid, sentence))]
// ← Pictures missing here!
};This means every
The FixAdd one line to var s = new Sense
{
Id = sense.Guid,
EntryId = sense.Entry.Guid,
Gloss = FromLcmMultiString(sense.Gloss),
Definition = FromLcmMultiString(sense.Definition),
PartOfSpeech = pos is null ? null : FromLcmPartOfSpeech(pos),
PartOfSpeechId = pos?.Guid,
SemanticDomains = [.. sense.SemanticDomainsRC.Select(FromLcmSemanticDomain)],
ExampleSentences = [.. sense.ExamplesOS.Select(sentence => FromLexExampleSentence(sense.Guid, sentence))],
Pictures = [.. sense.PicturesOS.Select(pic => FromLcmPicture(sense.Guid, pic))] // ← add this
};This mirrors the exact pattern already used for |
|
@CodeRabbit - I added the necessary line to FromLexSense. Now when syncing pictures, there's the following error: Analyze the cause of the error and recommend a fix. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== LcmCrdtDbContext - Picture and ExampleSentence configuration ==="
grep -n "Picture\|ExampleSentence\|HasKey\|composite\|OrderedWith\|IOrderableCrdt\|IObjectBase" \
backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs | head -80
echo ""
echo "=== Full LcmCrdtDbContext OnModelCreating ==="
cat backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs
echo ""
echo "=== Picture model definition ==="
fd -e cs "Picture" backend/FwLite/MiniLcm/ | xargs cat 2>/dev/null
fd -e cs "Picture" backend/FwLite/LcmCrdt/ | xargs cat 2>/dev/null
echo ""
echo "=== ExampleSentence model definition (for comparison) ==="
fd -e cs "ExampleSentence" backend/FwLite/MiniLcm/Models/ | xargs cat 2>/dev/null
fd -e cs "ExampleSentence" backend/FwLite/LcmCrdt/Objects/ | xargs cat 2>/dev/null
echo ""
echo "=== Harmony IOrderableCrdt / IObjectBase ==="
fd -e cs "IObjectBase\|IOrderable\|ObjectBase" backend/harmony/ | xargs grep -l "HasKey\|IObjectBase\|IOrderable" 2>/dev/null | head -5 | xargs cat 2>/dev/null | head -80Length of output: 45402 🏁 Script executed: Length of output: 91 Here are the results from shell you requested: |
|
@CodeRabbit - Try again. Your last attempt at analyzing the error message ran into an internal error and went nowhere. Focus on the error message "Entity type 'Picture' is defined with a 2-part composite key, but 1 values were passed to the 'Find' method." Why does EF Core think that Picture has a 2-part composite key? |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== LcmCrdtDbContext - OnModelCreating and ConfigureConventions ==="
cat backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs
echo ""
echo "=== Picture model in MiniLcm ==="
fd -e cs "Picture" backend/FwLite/MiniLcm/Models/ | xargs cat 2>/dev/null
echo ""
echo "=== Picture object in LcmCrdt ==="
fd -e cs "Picture" backend/FwLite/LcmCrdt/Objects/ | xargs cat 2>/dev/null
echo ""
echo "=== ExampleSentence model in MiniLcm (comparison) ==="
fd -e cs "ExampleSentence" backend/FwLite/MiniLcm/Models/ | xargs cat 2>/dev/null
echo ""
echo "=== IOrderableCrdt / IOrderable in Harmony or LcmCrdt ==="
rg -n "IOrderableCrdt\|IOrderable\b" --type cs backend/FwLite/ -C 3 | head -60
echo ""
echo "=== How Harmony configures composite keys for orderable entities ==="
rg -n "HasKey\|composite\|OrderedWith\|IOrderableCrdt\|\.Order\b" --type cs backend/harmony/src/ -C 3 | head -80Length of output: 9678
Root CauseIn senseModel.OwnsMany(s => s.Pictures, pic => { pic.ToJson(); });When you use Meanwhile, There's also a second conflict: The Fix
1. public class Picture : IObjectWithId<Picture>, IOrderable
{
public virtual Guid Id { get; set; }
public double Order { get; set; }
public virtual Guid SenseId { get; set; } // ← uncomment this
public virtual MediaUri MediaUri { get; set; }
public virtual RichMultiString Caption { get; set; } = [];
public DateTimeOffset? DeletedAt { get; set; }
public Guid[] GetReferences() => [SenseId]; // ← fix this
public void RemoveReference(Guid id, DateTimeOffset time)
{
if (id == SenseId) DeletedAt = time; // ← fix this
}
// ...
}2. // Add this alongside the other IQueryable properties:
public IQueryable<Picture> Pictures => Set<Picture>().AsNoTracking();
// In OnModelCreating — REMOVE this line:
// senseModel.OwnsMany(s => s.Pictures, pic => { pic.ToJson(); });
// In ConfigureConventions — REMOVE these lines:
// builder.Properties<Picture>()
// .HaveColumnType("jsonb")
// .HaveConversion<PictureDbConverter>();
// And REMOVE the PictureDbConverter class entirely.3. public Guid SenseId { get; init; }
public CreatePictureChange(Picture picture, Guid senseId) : base(...)
{
SenseId = senseId;
// ...
}
public override async ValueTask<Picture> NewEntity(Commit commit, IChangeContext context)
{
return new Picture
{
Id = EntityId,
SenseId = SenseId, // ← must be set
Order = Order,
Caption = Caption ?? new(),
MediaUri = MediaUri,
};
}4. Add a new EF migration after making these changes: dotnet ef migrations add AddPictureAsTopLevelEntityWhy |
|
@hahn-kev - Could you look at what Code Rabbit just said in #2300 (comment)? I'd like to hear your opinion. We had decided to make Pictures a JSON field off of Senses, but Code Rabbit seems to think that that won't work. I think it can, but perhaps we need to add the SenseId field into Pictures (which I had decided not to add since a JSON field on the sense itself doesn't need to store the GUID of the thing it's already attached to) in order to reconcile EF Core's view of the world with Harmony's. But that's only if Code Rabbit is right here. What's your take? Do I need to make Pictures a separate DB entity since we have a CRDT CRUD API for them? |
One missing line was responsible for the `Pictures = []` results we got.
Since Harmony calls `dbContext.Find(typeof(Picture), picId)`, EF Core needs Pictures to be its own table. So we can't make it a JSON column after all, and it will need SenseId as a foreign key.
|
@CodeRabbit - When a sync is run, pictures from FwData are not getting into CRDT, and the CRDT Sense object always has Pictures as an empty list. Furthermore, the empty Pictures list from CRDT is being synced into FwData, resulting in the pictures from FwData being deleted. Examine the sync code and determine the cause, then explain what changes would be needed so that pictures from FwData can get into CRDT. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
echo "=== Latest git log ==="
git log --oneline -5
echo ""
echo "=== LcmCrdtDbContext.cs ==="
cat backend/FwLite/LcmCrdt/LcmCrdtDbContext.cs
echo ""
echo "=== Picture model in MiniLcm ==="
fd -e cs "Picture.cs" backend/FwLite/MiniLcm/ | xargs cat 2>/dev/null
echo ""
echo "=== Picture object in LcmCrdt/Objects ==="
fd -e cs "Picture.cs" backend/FwLite/LcmCrdt/ | xargs cat 2>/dev/null
echo ""
echo "=== CreatePictureChange.cs ==="
fd -e cs "CreatePictureChange" backend/FwLite/ | xargs cat 2>/dev/null
echo ""
echo "=== CrdtMiniLcmApi.cs - GetSenses/GetEntries/Pictures ==="
grep -n "Picture\|GetSense\|GetEntry\|GetAll\|Include\|OwnsMany\|OwnsOne" \
backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs | head -80
echo ""
echo "=== SenseSync.cs ==="
fd -e cs "SenseSync" backend/FwLite/ | xargs cat 2>/dev/null
echo ""
echo "=== PictureSync.cs ==="
fd -e cs "PictureSync" backend/FwLite/ | xargs cat 2>/dev/null
echo ""
echo "=== FwDataMiniLcmApi.cs - FromLexSense / PicturesOS ==="
grep -n "PicturesOS\|FromLcmPicture\|Pictures\b" \
backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs | head -40Length of output: 19597 🏁 Script executed: #!/bin/bash
echo "=== CrdtMiniLcmApi.cs - DeletePicture, MovePicture, UpdatePicture full context ==="
sed -n '860,915p' backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
echo ""
echo "=== CrdtMiniLcmApi.cs - BulkCreateEntries / AddChanges for Pictures ==="
sed -n '490,520p' backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
echo ""
echo "=== CrdtConfig / registered change types for Picture ==="
fd -e cs "CrdtConfig\|LcmCrdtKernel\|AddCrdt\|RegisterChanges" backend/FwLite/LcmCrdt/ | head -5 | xargs cat 2>/dev/null
echo ""
echo "=== Search for all CRDT change registrations ==="
rg -n "RegisterChange\|AddChange.*type\|ISelfNamedType\|typeof.*Change\b" --type cs backend/FwLite/LcmCrdt/ | grep -v "//\|Test\|spec" | head -60
echo ""
echo "=== How Harmony handles OwnsMany with snapshots - look at CrdtRepository ProjectSnapshot ==="
fd -e cs "CrdtRepository" backend/harmony/ | xargs grep -n "ProjectSnapshot\|OwnsMany\|OwnedNavigation\|Find\b\|EntityEntry" 2>/dev/null | head -40
echo ""
echo "=== ProjectSnapshot in LcmCrdt ==="
fd -e cs "ProjectSnapshot" backend/FwLite/ | xargs cat 2>/dev/null | head -120
echo ""
echo "=== CrdtFwdataProjectSyncService.cs - how projectSnapshot is built / how Senses are read for sync ==="
cat backend/FwLite/FwLiteProjectSync/CrdtFwdataProjectSyncService.csLength of output: 18669 🏁 Script executed: #!/bin/bash
echo "=== Harmony CrdtRepository.cs - full file ==="
cat backend/harmony/src/SIL.Harmony/Db/CrdtRepository.cs
echo ""
echo "=== Harmony SnapshotWorker.cs - full file ==="
cat backend/harmony/src/SIL.Harmony/SnapshotWorker.cs
echo ""
echo "=== Search for how Harmony entity types are registered ==="
rg -n "IObjectBase\|AddCrdt\|UseCrdt\|object.*type\|entityType\|RegisterEntity" --type cs backend/FwLite/LcmCrdt/ -C 3 | head -60
echo ""
echo "=== LcmCrdt - LcmCrdtKernel / ServiceExtensions / CrdtSetup ==="
fd -e cs . backend/FwLite/LcmCrdt/ | xargs grep -l "UseCrdt\|AddCrdt\|CrdtConfig\|RegisterChange" 2>/dev/null | head -5 | xargs cat 2>/dev/null | head -120
echo ""
echo "=== CrdtConfig setup for LcmCrdt ==="
rg -n "CrdtConfig\|new CrdtConfig\|AddSingleton.*CrdtConfig\|Configure.*CrdtConfig" --type cs backend/FwLite/LcmCrdt/ -C 5 | head -60
echo ""
echo "=== Check if Picture is registered as an entity type in Harmony for LcmCrdt ==="
rg -n "Picture\b" --type cs backend/FwLite/LcmCrdt/LcmCrdtKernel.cs 2>/dev/null
echo ""
echo "=== LcmCrdtKernel.cs ==="
cat backend/FwLite/LcmCrdt/LcmCrdtKernel.cs 2>/dev/null || fd -e cs "LcmCrdtKernel\|Kernel" backend/FwLite/LcmCrdt/ | head -3 | xargs cat 2>/dev/nullLength of output: 26784 🏁 Script executed: #!/bin/bash
echo "=== LcmCrdt Sense model ==="
fd -e cs "Sense.cs" backend/FwLite/LcmCrdt/ | xargs cat 2>/dev/null
fd -e cs "Sense.cs" backend/FwLite/MiniLcm/ | xargs cat 2>/dev/null
echo ""
echo "=== Latest EF Core migration for LcmCrdt ==="
fd -e cs . backend/FwLite/LcmCrdt/Migrations/ | sort | tail -3 | xargs cat 2>/dev/null
echo ""
echo "=== Check for Pictures column in any migration ==="
rg -rn "Picture\|Pictures" backend/FwLite/LcmCrdt/Migrations/ | head -40
echo ""
echo "=== Sense table snapshot in EF migration - does Pictures column exist? ==="
rg -n "Pictures\|Senses\b" backend/FwLite/LcmCrdt/Migrations/ | head -30
echo ""
echo "=== MediaUri class definition ==="
fd -e cs "MediaUri" backend/FwLite/ | xargs cat 2>/dev/null
echo ""
echo "=== Check what PictureDbConverter does ==="
grep -n "PictureDbConverter\|Picture.*Converter" backend/FwLite/LcmCrdt/LcmCrdtDbContext.csLength of output: 67396
Root CauseThe Harmony's entity projection (which keeps the EF Core Senses table in sync with the ObjectSnapshot store) handles properties registered inside Fix 1 — Convert
|
| Issue | Cause | Fix |
|---|---|---|
Pictures = [] after sync |
OwnsMany...ToJson() outside Harmony's ObjectTypeListBuilder not projected correctly |
Move to HasConversion JSON scalar inside Add<Sense> builder |
DeletePicture / MovePicture broken |
Use DeleteChange<Picture> / SetOrderChange<Picture> with non-standalone entity |
Replace with EditChange<Sense>-based changes |
UpdatePicture broken |
Uses JsonPatchChange<Picture> same reason |
Replace with UpdateSensePictureChange |
Picture.Copy() loses ordering |
Order omitted |
Add Order = Order to Copy() |
Also means SenseId on Picture objects is completely redundant
WIP for #2232 and #2233.
MiniLcm API for picture support now contains the skeleton of what it needs to do, but one major feature is still lacking: correct MediaUri handling (which is the heart of the feature). It's not nearly as direct a translation from FW Classic to FW Lite, so I've implemented the easy stuff first. MediaUri handling will come next.