[project page] on demand history loading, show fw lite history - #2546
[project page] on demand history loading, show fw lite history#2546hahn-kev wants to merge 16 commits into
Conversation
Add planning map and tickets for making Mercurial history load on demand and showing FieldWorks Lite (CRDT) history alongside it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
Collapsed section + "Show history" button gates the fetch; keep the unbounded whole-log fetch for v1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
New GraphQL field Project.harmonyCommits returning id/dateTime/authorName from CrdtCommits, newest-N hard cap, gated on hasHarmonyCommits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
Two separate labeled sections (hg graph + FwLite flat list), each with its own on-demand toggle; FwLite shown only when hasHarmonyCommits. No merge. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
Fold all three decisions into SPEC.md (Project.harmonyCommits field, independent per-section on-demand toggles, HarmonyLogView, test surface). Map complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
Gate the hg changeset fetch behind a "Show history" toggle so it no longer loads on every project-page visit, and add a parallel on-demand "FieldWorks Lite history" list backed by a new Project.harmonyCommits GraphQL field over CrdtCommits (commit metadata exposed as a JSON scalar). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBzpvvRMLcGAnZhSk7ghx
…time counter in UI and full time in tooltip, fix up issues with json scalar, and HybridDateTime in gql client
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a GraphQL resolver for capped, ordered Harmony commits. Updates the schema, backend relationships, and tests. Adds paused frontend history stores with separate collapsible Mercurial and Harmony sections, localization, and a ChangesProject history
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 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 ↗︎
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.scratch/lexbox-history-on-demand/SPEC.md:
- Around line 17-19: Update the documented GraphQL contract to use the
implemented harmonyCommits payload, including hybridDateTime and metadata
instead of direct dateTime and authorName fields. In
.scratch/lexbox-history-on-demand/SPEC.md lines 17-19 and 35-86, revise the
field/DTO definition plus resolver, schema, and materialization guidance; make
the corresponding API and resolver updates in
.scratch/lexbox-history-on-demand/issues/02-fwlite-history-server-api.md lines
34-63, document the actual fields received by the history component in
issues/03-combined-history-layout.md lines 30-58, and remove the
implementation-ready claim in issues/04-implementation-spec.md lines 27-32.
In `@backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs`:
- Around line 48-52: Update the query using CrdtCommits before ToArrayAsync to
project only Id, HybridDateTime, ProjectId, ClientId, and Metadata via Select,
while preserving the existing ordering, limit, and cancellation behavior.
In `@backend/LexBoxApi/GraphQL/CustomTypes/ProjectGqlConfiguration.cs`:
- Line 18: Bind ProjectGqlConfiguration’s HarmonyCommits field to
HarmonyCommitResolver.GetHarmonyCommits instead of the EF navigation, passing
the limit from the HarmonyCommits fragment while preserving the resolver’s
project-type guard and 1–200 clamp. Update
.scratch/lexbox-history-on-demand/map.md to document the current ServerCommit
shape, regenerate frontend/schema.graphql, and add the schema-level coverage in
backend/Testing/GraphQL/HarmonyCommitResolverTests.cs.
In `@frontend/src/lib/components/HarmonyLogView.svelte`:
- Around line 34-60: Update the conditional rendering in HarmonyLogView so the
commits each-block is selected when commits exist instead of being guarded by a
constant false condition. Preserve the existing loading and no_history fallback
for empty commits or loading states.
In `@frontend/src/routes/`(authenticated)/project/[project_code]/+page.ts:
- Around line 56-62: Update the harmonyCommits orderBy configuration so
hybridDateTime.dateTime, hybridDateTime.counter, and id use descending order,
ensuring the capped history result and displayed list remain newest first.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 59a68b7d-3a4b-41f1-b7a7-06212263754c
📒 Files selected for processing (20)
.scratch/lexbox-history-on-demand/SPEC.md.scratch/lexbox-history-on-demand/issues/01-hg-on-demand-ux.md.scratch/lexbox-history-on-demand/issues/02-fwlite-history-server-api.md.scratch/lexbox-history-on-demand/issues/03-combined-history-layout.md.scratch/lexbox-history-on-demand/issues/04-implementation-spec.md.scratch/lexbox-history-on-demand/map.mdbackend/Directory.Packages.propsbackend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.csbackend/LexBoxApi/GraphQL/CustomTypes/ProjectGqlConfiguration.csbackend/LexCore/Entities/Project.csbackend/LexCore/LexCore.csprojbackend/LexData/Entities/CommitEntityConfiguration.csbackend/Testing/GraphQL/HarmonyCommitResolverTests.csfrontend/gql-codegen.tsfrontend/schema.graphqlfrontend/src/lib/components/HarmonyLogView.sveltefrontend/src/lib/gql/gql-client.tsfrontend/src/lib/i18n/locales/en.jsonfrontend/src/routes/(authenticated)/project/[project_code]/+page.sveltefrontend/src/routes/(authenticated)/project/[project_code]/+page.ts
| var rows = await dbContext.CrdtCommits(project.Id) | ||
| .OrderByDescending(c => c.HybridDateTime.DateTime) | ||
| .ThenByDescending(c => c.HybridDateTime.Counter) | ||
| .Take(limit) | ||
| .ToArrayAsync(cancellationToken); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate files ---'
git ls-files 'backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs' \
'backend/**/*.cs' | rg 'HarmonyCommit\.cs|ServerCommit|CrdtCommits'
printf '%s\n' '--- HarmonyCommit outline ---'
ast-grep outline backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs
printf '%s\n' '--- HarmonyCommit source ---'
cat -n backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs
printf '%s\n' '--- ServerCommit declarations and ChangeEntities references ---'
rg -n -C 5 'class ServerCommit|record ServerCommit|ChangeEntities|CrdtCommits\(' backend -g '*.cs'Repository: sillsdev/languageforge-lexbox
Length of output: 50386
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- focused ServerCommit references ---'
rg -n -C 3 --glob '!backend/LexData/Migrations/**' \
'ServerCommit|ChangeEntities|CrdtCommits\(' backend -g '*.cs' | head -n 500
printf '%s\n' '--- package and project references ---'
rg -n -C 3 'Harmony|LexData|TargetFramework|PackageReference' \
backend/LexBoxApi backend/LexData -g '*.csproj' -g 'Directory.*props' -g 'Directory.*targets'
printf '%s\n' '--- EF model configuration ---'
rg -n -C 8 --glob '!backend/LexData/Migrations/**' \
'OwnsMany|ToJson|ChangeEntities|ServerCommit' backend -g '*.cs' | head -n 400
printf '%s\n' '--- all constructor-like ServerCommit creation sites ---'
rg -n -C 5 --glob '!backend/LexData/Migrations/**' \
'new[[:space:]]+ServerCommit|ServerCommit[[:space:]]*\(' backend -g '*.cs' | head -n 300Repository: sillsdev/languageforge-lexbox
Length of output: 50386
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- project package references ---'
find backend -type f \( -name '*.csproj' -o -name 'Directory.*props' -o -name 'Directory.*targets' \) -print0 |
xargs -0 rg -n -C 3 'Harmony|TargetFramework|PackageReference'
printf '%s\n' '--- current EF model snapshot for ServerCommit ---'
sed -n '1067,1098p' backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs
sed -n '1362,1385p' backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs
printf '%s\n' '--- current query and GraphQL fields ---'
sed -n '10,56p' backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs
printf '%s\n' '--- local constructors and projections involving ServerCommit ---'
rg -n -C 5 --glob '!backend/LexData/Migrations/**' \
'new[[:space:]]+ServerCommit|Select\(.*ServerCommit|ServerCommit[[:space:]]*\(' \
backend -g '*.cs' | head -n 250
printf '%s\n' '--- static query-shape verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
path = Path("backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs")
text = path.read_text()
query = text[text.index("var rows ="):text.index("return rows;")]
fields = set(re.findall(r'descriptor\.Field\(h => h\.([A-Za-z0-9_]+)', text))
fields.update(re.findall(r'descriptor\.Field\(h => h\.([A-Za-z0-9_]+)', text))
print("query_has_select:", bool(re.search(r'\.Select\s*\(', query)))
print("query_materializes_server_commit_array:", "ToArrayAsync" in query)
print("graphql_fields:", sorted(fields))
print("change_entities_referenced_by_graphql_fields:", "ChangeEntities" in fields)
print("change_entities_referenced_by_query:", "ChangeEntities" in query)
snapshot = Path("backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs").read_text()
block = snapshot[snapshot.index('modelBuilder.Entity("SIL.Harmony.Core.ServerCommit"'):
snapshot.index('modelBuilder.Entity("SIL.Harmony.Core.ServerCommit"', snapshot.index('modelBuilder.Entity("SIL.Harmony.Core.ServerCommit"') + 1)]
print("snapshot_maps_change_entities_as_jsonb:",
'b.Property<string>("ChangeEntities")' in block and '.HasColumnType("jsonb")' in block)
PYRepository: sillsdev/languageforge-lexbox
Length of output: 50385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- Harmony reference props ---'
cat backend/Harmony.Core.References.props
printf '%s\n' '--- package versions ---'
sed -n '108,116p' backend/Directory.Packages.props
printf '%s\n' '--- ServerCommit model properties ---'
sed -n '1067,1095p' backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs
printf '%s\n' '--- GraphQL query ---'
sed -n '35,55p' backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs
printf '%s\n' '--- static query-shape verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
path = Path("backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs")
text = path.read_text()
query = text[text.index("var rows ="):text.index("return rows;")]
fields = re.findall(r'descriptor\.Field\(h => h\.([A-Za-z0-9_]+)', text)
snapshot = Path("backend/LexData/Migrations/LexBoxDbContextModelSnapshot.cs").read_text()
start = snapshot.index('modelBuilder.Entity("SIL.Harmony.Core.ServerCommit"')
end = snapshot.index(
'modelBuilder.Entity("SIL.Harmony.Core.ServerCommit"', start + 1
)
block = snapshot[start:end]
print("query_has_select:", bool(re.search(r'\.Select\s*\(', query)))
print("query_materializes_server_commit_array:", "ToArrayAsync" in query)
print("graphql_fields:", fields)
print("change_entities_exposed_by_graphql:", "ChangeEntities" in fields)
print("change_entities_referenced_by_query:", "ChangeEntities" in query)
print("snapshot_maps_change_entities_as_jsonb:",
'b.Property<string>("ChangeEntities")' in block
and '.HasColumnType("jsonb")' in block)
PY
printf '%s\n' '--- local ServerCommit constructor evidence ---'
rg -n --glob '!backend/LexData/Migrations/**' \
'new[[:space:]]+ServerCommit|ServerCommit[[:space:]]*\(' backend -g '*.cs' || trueRepository: sillsdev/languageforge-lexbox
Length of output: 5084
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- ServerCommit construction and field assignments in tests ---'
sed -n '1,90p' backend/Testing/LexCore/CrdtServerCommitTests.cs
sed -n '1,80p' backend/Testing/LexCore/Services/CrdtCommitServiceTests.cs
sed -n '1,85p' backend/Testing/GraphQL/HarmonyCommitResolverTests.cs
printf '%s\n' '--- all relevant ServerCommit property assignments ---'
rg -n -C 4 --glob '!backend/LexData/Migrations/**' \
'new ServerCommit|HybridDateTime[[:space:]]*=|ProjectId[[:space:]]*=|ClientId[[:space:]]*=|Metadata[[:space:]]*=' \
backend/Testing backend/LexBoxApi backend/LexData -g '*.cs' | head -n 350
printf '%s\n' '--- projection field coverage verifier ---'
python3 - <<'PY'
from pathlib import Path
import re
text = Path("backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs").read_text()
graphql_fields = set(re.findall(r'descriptor\.Field\(h => h\.([A-Za-z0-9_]+)', text))
proposed = {"Id", "ProjectId", "ClientId", "HybridDateTime", "Metadata"}
print("graphql_fields:", sorted(graphql_fields))
print("proposed_fields:", sorted(proposed))
print("all_graphql_fields_projected:", graphql_fields <= proposed)
print("unprojected_graphql_fields:", sorted(graphql_fields - proposed))
print("heavy_field_projected:", "ChangeEntities" in proposed)
PYRepository: sillsdev/languageforge-lexbox
Length of output: 40432
Project only the history fields before materialization.
ToArrayAsync materializes every mapped ServerCommit property, including the ChangeEntities jsonb payload. Add a Select for only Id, HybridDateTime, ProjectId, ClientId, and Metadata before materialization.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/LexBoxApi/GraphQL/CustomTypes/HarmonyCommit.cs` around lines 48 - 52,
Update the query using CrdtCommits before ToArrayAsync to project only Id,
HybridDateTime, ProjectId, ClientId, and Metadata via Select, while preserving
the existing ordering, limit, and cancellation behavior.
There was a problem hiding this comment.
@hahn-kev we talked about this, but I can't remember exactly what you said. The code comment says:
"heavy ChangeEntities jsonb blob stays in the DB"
but I think you/we said that we basically just have to pull the whole commit out, because it's a single JSON blob. Either way, a test documenting this might be beneficial. The seeded commit in your tests has no changes on it.
There was a problem hiding this comment.
we have to pull all the metadata on a commit out since it's a single blob, and the ChangeEntities are a single blob. Since we don't bind ChangeEntites in hotchocolate it won't query them.
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
| {#if false} | ||
| {#each commits as commit (commit.id)} | ||
| <tr> | ||
| <td title={$date(commit.hybridDateTime.dateTime, {dateStyle: 'full', timeStyle: 'long'})}> | ||
| {$date(commit.hybridDateTime.dateTime)} | ||
| {#if commit.hybridDateTime.counter > 0} | ||
| <span class="text-xs text-secondary">+{commit.hybridDateTime.counter}</span> | ||
| {/if} | ||
| </td> | ||
| <td>{authorName(commit.metadata)}</td> | ||
| </tr> | ||
| {/each} | ||
| {:else} | ||
| <tr> | ||
| <td colspan="100"> | ||
| <div class="text p-2 text-secondary flex gap-2 items-center"> | ||
| {#if loading} | ||
| <Loader loading /> | ||
| {$t('project_page.harmony.loading')} | ||
| {:else} | ||
| <Icon icon="i-mdi-creation-outline" size="text-2xl" /> | ||
| {$t('project_page.harmony.no_history')} | ||
| {/if} | ||
| </div> | ||
| </td> | ||
| </tr> | ||
| {/if} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Render the commit rows when commits exist.
Line 34 disables the only commit-rendering branch. The component always shows the loading or empty state after a successful history fetch.
Proposed fix
- {`#if` false}
+ {`#if` commits.length > 0}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {#if false} | |
| {#each commits as commit (commit.id)} | |
| <tr> | |
| <td title={$date(commit.hybridDateTime.dateTime, {dateStyle: 'full', timeStyle: 'long'})}> | |
| {$date(commit.hybridDateTime.dateTime)} | |
| {#if commit.hybridDateTime.counter > 0} | |
| <span class="text-xs text-secondary">+{commit.hybridDateTime.counter}</span> | |
| {/if} | |
| </td> | |
| <td>{authorName(commit.metadata)}</td> | |
| </tr> | |
| {/each} | |
| {:else} | |
| <tr> | |
| <td colspan="100"> | |
| <div class="text p-2 text-secondary flex gap-2 items-center"> | |
| {#if loading} | |
| <Loader loading /> | |
| {$t('project_page.harmony.loading')} | |
| {:else} | |
| <Icon icon="i-mdi-creation-outline" size="text-2xl" /> | |
| {$t('project_page.harmony.no_history')} | |
| {/if} | |
| </div> | |
| </td> | |
| </tr> | |
| {/if} | |
| {`#if` commits.length > 0} | |
| {`#each` commits as commit (commit.id)} | |
| <tr> | |
| <td title={$date(commit.hybridDateTime.dateTime, {dateStyle: 'full', timeStyle: 'long'})}> | |
| {$date(commit.hybridDateTime.dateTime)} | |
| {`#if` commit.hybridDateTime.counter > 0} | |
| <span class="text-xs text-secondary">+{commit.hybridDateTime.counter}</span> | |
| {/if} | |
| </td> | |
| <td>{authorName(commit.metadata)}</td> | |
| </tr> | |
| {/each} | |
| {:else} | |
| <tr> | |
| <td colspan="100"> | |
| <div class="text p-2 text-secondary flex gap-2 items-center"> | |
| {`#if` loading} | |
| <Loader loading /> | |
| {$t('project_page.harmony.loading')} | |
| {:else} | |
| <Icon icon="i-mdi-creation-outline" size="text-2xl" /> | |
| {$t('project_page.harmony.no_history')} | |
| {/if} | |
| </div> | |
| </td> | |
| </tr> | |
| {/if} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/lib/components/HarmonyLogView.svelte` around lines 34 - 60,
Update the conditional rendering in HarmonyLogView so the commits each-block is
selected when commits exist instead of being guarded by a constant false
condition. Preserve the existing loading and no_history fallback for empty
commits or loading states.
| harmonyCommits(orderBy: [ { | ||
| hybridDateTime: { | ||
| dateTime: ASC | ||
| counter: ASC | ||
| } | ||
| id: ASC | ||
| }]) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep the history order newest first.
The settled contract specifies descending history order. This query requests ascending hybridDateTime and id values, so the displayed list starts with the oldest commit in the capped result set.
Proposed fix
- dateTime: ASC
- counter: ASC
+ dateTime: DESC
+ counter: DESC
}
- id: ASC
+ id: DESC📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| harmonyCommits(orderBy: [ { | |
| hybridDateTime: { | |
| dateTime: ASC | |
| counter: ASC | |
| } | |
| id: ASC | |
| }]) { | |
| harmonyCommits(orderBy: [ { | |
| hybridDateTime: { | |
| dateTime: DESC | |
| counter: DESC | |
| } | |
| id: DESC | |
| }]) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/routes/`(authenticated)/project/[project_code]/+page.ts around
lines 56 - 62, Update the harmonyCommits orderBy configuration so
hybridDateTime.dateTime, hybridDateTime.counter, and id use descending order,
ensuring the capped history result and displayed list remain newest first.
History is collapsed by default, so the reset-project spec never saw the hg tip link; also undo the accidental {#if false} that hid Harmony commits.
Co-authored-by: Cursor <cursoragent@cursor.com>
| var rows = await dbContext.CrdtCommits(project.Id) | ||
| .OrderByDescending(c => c.HybridDateTime.DateTime) | ||
| .ThenByDescending(c => c.HybridDateTime.Counter) | ||
| .Take(limit) | ||
| .ToArrayAsync(cancellationToken); |
There was a problem hiding this comment.
@hahn-kev we talked about this, but I can't remember exactly what you said. The code comment says:
"heavy ChangeEntities jsonb blob stays in the DB"
but I think you/we said that we basically just have to pull the whole commit out, because it's a single JSON blob. Either way, a test documenting this might be beneficial. The seeded commit in your tests has no changes on it.
|
I want to add the client version to the harmony history view, it should be in metadata |

replaces #2538
Before loading:


After:
sadly we don't have a message to show in history, I suggest that when authoring a commit we use our fancy new activity code to summarize the changes and put that in commit metadata, then we could show that here.
AI Summary
Load project history on demand and add FieldWorks Lite history
The project page currently fetches the entire Mercurial changelog on every visit, which is slow for large repos. This makes history load on demand instead, and adds a second, independent FieldWorks Lite (CRDT) history list when the project has one.
What changed
Frontend
changesetsquery no longer fires on page load — it starts paused and resumes only when the section is revealed.project.hasHarmonyCommits, with its own independent "Show FieldWorks Lite history" toggle so opening the cheap CRDT list never triggers the expensive hg fetch (and vice versa).HarmonyLogViewcomponent; reveal/resume driven by an$effectso it survives load reruns.isEmptynow derives fromproject.lastCommitrather than the (now-deferred) changeset fetch.Backend
Project.harmonyCommits(limit: Int! = 50): [HarmonyCommit!]!, resolved over the PostgresCrdtCommitstable viaIDbContextFactory. Orders byHybridDateTime(date, then counter) and caps in SQL; limit clamped to[1, 200].CommitMetadatais returned as a JSONAnyscalar (camelCase) rather than typed fields — the client readsmetadata.authorNametoday, and any future metadata field is available with no schema change. The heavyChangeEntitiesblob is left in the DB.Type(Unknown/FLEx), matchinghasHarmonyCommits.Test plan
schema.graphqlregenerated (additive only).pnpm run checkpasses; GraphQL codegen regenerated.HarmonyCommitResolverTests(RequiresDb, runs in CI): SQL ordering + counter tiebreak, limit cap, limit clamp, null-author passthrough, and the type-guard short-circuit.Considered and rejected
CommitMetadataGraphQL object — GraphQL has no map type forextraMetadata, and it would couple the schema to the Harmony package type. The JSON scalar is simpler and future-proof.[UsePaging]— this is a deliberately bounded "last N commits" list, not an infinite-scroll collection.Notes / follow-ups
harmonyCommitsis a per-project resolver (no DataLoader), same profile as the existingchangesetsfield — fine for the single-project page; would need batching if ever selected on a project list.isEmptynow relies onlastCommit, dropping an old workaround for a project-reset staleness bug — worth confirming that bug is resolved.