Skip to content

Add main publication to new entries (IsMain)#2341

Draft
myieye wants to merge 28 commits into
developfrom
claude/refactor-defaultasof-to-ismain-HJfTr
Draft

Add main publication to new entries (IsMain)#2341
myieye wants to merge 28 commits into
developfrom
claude/refactor-defaultasof-to-ismain-HJfTr

Conversation

@myieye

@myieye myieye commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2179.

FieldWorks marks one publication ("Main Dictionary") as protected and auto-adds it to every new entry, so downstream workflows (Webonary export, etc.) pick up new entries by default. FwLite didn't. This adds an IsMain flag on Publication (read from FLEx's protected IsProtected on import, not written back) and lets new-entry creation add the main publication.

Whether a new entry gets the main publication is decided per call via CreateEntryOptions (the C# presets WithMainPublication / AsIs, mirrored by a small TS helper). The viewer chooses based on whether the user can see the "Publish in" field: if it's shown the user controls publications (AsIs), otherwise we auto-add the main (WithMainPublication). Bulk import and sync always use AsIs, so they preserve the source's publications.

The single-main invariant (can't create a second main, can't turn the main flag off) lives in the shared MiniLcmApiValidationWrapper, so it applies to both the CRDT and FwData backends in one place; CrdtMiniLcmApi keeps only the CRDT-specific SetMainPublicationChange convergence. Because two CRDT replicas can each create a main while offline, a merged-in duplicate is demoted to a non-main publication (not deleted), converging to one main without throwing or losing data.

One deliberately odd bit: the JS-invokable CreateEntry takes options as a required argument, not a nullable/optional one. Optional [JSInvokable] params are a lie — Blazor enforces argument count, so an omitted arg throws at runtime even though it type-checks. Making it required moves that failure to compile time.

Includes an EF Core migration for the IsMain column (dotnet-ef bumped to 10.0.8 to match the project's EF Core version). Also rides along two unrelated hygiene tweaks (an AGENTS.md test-guidance clarification and an i18n-completeness agent scope trim), called out so they're not a surprise.

Test plan

  • CI (unit): single-main invariant — create/promote a second main throws, the main flag can't be turned off, and an offline merge of two mains converges to one (the duplicate surviving as non-main).
  • CI (unit): auto-add is per-call — WithMainPublication adds the main, AsIs doesn't, no double-add; on both the CRDT and FwData APIs.
  • CI (unit): PublicationSync promotes an existing publication to main, and a brand-new main is created (not promoted) so a dry-run sync doesn't throw.
  • CI (snapshot): regenerated Sena3 sync and serialization snapshots pass.
  • Manual: create an entry with no explicit publication and confirm it's published in the main publication; after sync, confirm it appears under Main Dictionary in FLEx.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: af442c37-a959-4d5d-a73d-d23d9e3be903

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/refactor-defaultasof-to-ismain-HJfTr

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

UI unit Tests

  1 files  ±0   63 suites  +1   30s ⏱️ +2s
188 tests +2  188 ✅ +2  0 💤 ±0  0 ❌ ±0 
260 runs  +2  260 ✅ +2  0 💤 ±0  0 ❌ ±0 

Results for commit f4b932f. ± Comparison against base commit 2d6b20e.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

C# FwHeadless Unit Tests

48 tests   48 ✅  17s ⏱️
 5 suites   0 💤
 1 files     0 ❌

Results for commit f4b932f.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

C# Unit Tests

165 tests   165 ✅  20s ⏱️
 23 suites    0 💤
  1 files      0 ❌

Results for commit f4b932f.

♻️ This comment has been updated with latest results.

@argos-ci

argos-ci Bot commented Jun 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Jun 19, 2026, 1:06 PM


var publications = await Api.GetPublications().ToArrayAsync();
publications.Should().ContainSingle(p => p.IsMain).Which.Id.Should().Be(firstMainId);
publications.Should().NotContain(p => p.Id == secondMainId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is deleting the right move? what if we just kept it around but made sure it's not main? There's going to be entries that reference this publication (because it was main for this user) so deleting it will remove the publication from those entries.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. I'll change it to keep the new publication and simply not mark it as "main"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should consider what BulkCreateEntries does. Right now it will bulk create entries without a publication. But maybe we just need to add CreateEntryOptions to the bulk api.

@myieye myieye Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yikes. Actually, we're falling back to CreateEntryOptions.Everything, which does auto-add the main publication, which is actually really bad. When we're importing entries from FieldWorks we shouldn't touch anything. If there are entries not in the main publication, then that's intentional.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed Everything to AsIs and it no longer auto-adds the main publication. It does exactly was it says: it creates the entry as-is.

@hahn-kev hahn-kev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me. I tested it locally against a FW project and it worked great. I don't have any existing projects locally and downloading new ones is broken by morph types right now so I can't test that.

@myieye myieye force-pushed the claude/refactor-defaultasof-to-ismain-HJfTr branch from 5666b93 to 347d2c8 Compare June 17, 2026 15:49
claude and others added 19 commits June 19, 2026 11:28
…sMain

Replace the timestamp-based default publication tracking with a simple
boolean flag. This makes finding the main/default publication a direct
flag check rather than requiring date comparisons.

Key behaviors:
- API methods throw if turning IsMain from ON to OFF
- API methods throw if setting IsMain ON for more than one publication
- CRDT SetDefaultPublicationChange is a no-op if a main publication
  already exists (0→1 transition only), ensuring convergence
- FwData bridge maps IsMain from/to FW's IsProtected property
- IsMain is stripped when syncing back to FwData (read-only there)
- CreateEntry auto-adds the default publication when enabled

https://claude.ai/code/session_01YJVmNMUDdWKgGUv3HicCCo
The Publication model uses IsMain, so all related code should use
"main" terminology consistently instead of mixing "default" and "main".

Renames across backend and frontend:
- SetDefaultPublicationChange → SetMainPublicationChange
- GetDefaultPublication → GetMainPublication
- AutoAddDefaultPublication → AutoAddMainPublication
- defaultPublication → mainPublication
- resolveDefaultPublication → resolveMainPublication
- UI text: "default publication" → "main publication"

https://claude.ai/code/session_01YJVmNMUDdWKgGUv3HicCCo
The IsMain column (added in the publication refactor) had no migration. The local dotnet-ef tool was pinned to 9.0.16 while the project targets EF Core 10.0.8, so the snapshot was stamped 9.0.16; bump the tool to 10.0.8 and scaffold the migration + snapshot with matching tooling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard CreatePublication/UpdatePublication on both API implementations so a
second main publication can't be created and the main flag can't be turned
off. For CRDT merges (where two replicas can each create a main offline),
CreatePublicationChange returns the duplicate pre-deleted so Harmony filters
it out, converging to one main without throwing. GetMainPublication uses
SingleOrDefault to surface >1 corruption rather than mask it.

AutoFaker now forces Publication.IsMain false: main is a controlled
singleton, never randomly generated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The publication rename added "The main publication is always included." in
NewEntryDialog without re-extracting the catalogs, failing the i18n check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The demo API's publications omitted the now-required isMain, failing
svelte-check; the publications unit test used `as any`, failing eslint.
Set isMain on the demo publications (Main Dictionary is the main one) and
type the test with a small IPublication factory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The workaround forced IsMain=false on both sides before the CRDT->FwData
publication sync. It was masking AutoFaker generating publications with random
IsMain=true, which let a test create a CRDT main different from FwData's
protected main. With faked publications now always IsMain=false, the two sides
always agree on the main, so no IsMain write-back is ever needed — and FwData's
IsMain is read-only anyway (UpdatePublicationProxy.IsMain is a no-op, mapped
from IsProtected). Sync publications symmetrically like every other entity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CreateEntry auto-adds the main publication by default (a UI affordance), so in
FwData — which always has a protected Main Dictionary — entries the query tests
expect to have an empty PublishIn instead carry the main. Disable auto-add where
those tests assert exact PublishIn, matching the sync path (EntrySync), which
also never auto-adds. Regenerate the Sena-3 snapshot for the new IsMain field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the main publication is new to a collection, PublicationSync's Add already
creates it with IsMain set, so the follow-up collection-level UpdatePublication
was redundant — and it threw NotFound during a dry-run sync, where the Add was a
no-op so the publication doesn't actually exist. Only promote a publication that
already existed but wasn't yet the main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Approve the developer-approval snapshots for the new IsMain field and
SetMainPublicationChange: the change/snapshot regression data, the EF model and
change-model snapshots, and the live Sena-3 db + fw-headless snapshot (one-time
SetMainPublicationChange promoting the protected Main Dictionary to main).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…motion

Flip CreateEntryOptions.AutoAddMainPublication to default off so bulk import
and sync preserve the source's publications; interactive entry creation opts
in via the new CreateEntryOptions.WithMainPublication (passed at the JS-invokable,
hub, and route entry points). Rename CreateEntryOptions.Everything to AsIs.

On offline merge, a duplicate main publication is now demoted to a non-main
publication instead of being deleted, so the publication survives. Gate the
NewEntryDialog publish-in note on field visibility. Adds PublicationSync tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t scope

AGENTS.md: note that MiniLcm.Tests / LcmCrdt.Tests / FwDataMiniLcmBridge.Tests
run without infrastructure (FwData-backed ones moderately slow, so filter).
i18n-completeness: drop the translator-context (#.) comment check, owned by the
crowdin-merge workflow. Restore trailing newline in dotnet-tools.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reateEntry options

Move the single-main guards out of the CRDT and FwData APIs into the shared
MiniLcmApiValidationWrapper (new PublicationUpdateValidator + a TryGetPropertyChange
helper), so the invariant lives in one place; CrdtMiniLcmApi keeps only the
SetMainPublicationChange convergence.

Make the JS-invokable CreateEntry options a required argument (optional [JSInvokable]
params throw at runtime on arg-count mismatch, despite type-checking) and drive AsIs
vs WithMainPublication from the frontend by publish-in field visibility, via a shared
TS helper mirroring the C# presets. Simplify the publishIn handling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the UpdatePublication(before, after) override to MiniLcmApiValidationWrapper so
the publication update path validates its model like every other entity does — it was
the one gap in the publication wrapper overrides.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ESLint's @typescript-eslint/naming-convention rejects PascalCase const variables; the
shared options helper must be camelCase. svelte-check didn't catch it (it's a lint rule).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The IsMain refactor edited sena-3_snapshot.2026-04-09.verified.txt in
place. Dated snapshots are immutable historical fixtures (AssertSena3Snapshots
iterates them to prove old formats still deserialize), and every prior format
change added a new dated file rather than mutating an existing one. Restore
2026-04-09 to its pre-IsMain content and capture the IsMain round-trip output
as a new 2026-06-18 snapshot, which LatestSena3SnapshotRoundTrips now targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…inputs

Closes #2366.

The newest dated sena-3 snapshot used to do two fighting jobs: an
immutable historical deserialize fixture (AssertSena3Snapshots) and the
mutable round-trip Verify target (LatestSena3SnapshotRoundTrips, picked
via OrderDescending().First()). A format change tempted editing a dated
file in place, which silently rotted the back-compat guard.

Split them: the dated files are now deserialize-only inputs that never
change, and an un-dated sena-3_snapshot.latest.verified.txt is the
round-trip target that legitimately tracks the current format. The IsMain
snapshot added earlier on this branch becomes that latest file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uded" string

An earlier iteration of NewEntryDialog showed this note inline; it later
moved to the publishInNote snippet and the string is no longer used, but
the catalogs still carried it. Re-running extraction prunes it from all
locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
myieye and others added 7 commits June 19, 2026 11:45
The IsMain migration was generated with EF tools 10, but bumping .config/dotnet-tools.json is unrelated to this PR. Restore develop's 9.0.16.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use the named helper in EntrySync instead of constructing the options inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ions

The hub and REST route hardcoded WithMainPublication; now they accept an optional options arg, defaulting to the same behaviour (auto-add the main publication).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Skip allocating a filtered JsonPatchDocument on the common update that doesn't touch IsMain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DiffToUpdate now carries the false->true promotion like any other field (the CRDT side turns it into a SetMainPublicationChange), so the collection-level promotion special-case is removed. A main is never demoted, so there's no 1->0 handling to add.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single-main guards are enforced in the backend-agnostic validation wrapper, so move them to PublicationsTestsBase. FwData maps IsMain from the read-only IsProtected, so the FwData test seeds a main at the LCM layer. The promotion and merge-convergence tests stay CRDT-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@myieye myieye force-pushed the claude/refactor-defaultasof-to-ismain-HJfTr branch from 5388d3c to 1bb7c5a Compare June 19, 2026 09:46
myieye and others added 2 commits June 19, 2026 14:36
Consolidate the get-or-create main-publication helper onto MiniLcmTestBase
(shared by the publication and create-entry conformance tests, reusing the
project's existing main rather than minting a second). Make the REST route's
CreateEntryOptions defaults explicit via ToOptions(defaults), extract
CreatePublicationChange.MainAlreadyExists, and make DoesNotChangePropertyTo
generic over the forbidden value's type.

Drop the dry-run PublicationSyncTests: the collection-level promotion it
guarded was removed when IsMain moved into standard per-publication diffing,
so the failure mode it covered is structurally gone.

Fix entry-api-helper to inline the create-entry options inside page.evaluate
— module imports are closure variables that don't cross the Playwright
serialization boundary, so referencing the shared helper there throws.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hand-minimized latest.verified.txt left a trailing comma after LexemeForm, which strict deserialization (CanDeserializeLatestRegressionData) rejects and RegressionDataUpToDate flagged. Drop it; data is otherwise unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add default/main publication to new entries

3 participants