Skip to content

[claude] Centralize FW Lite user state updates on login - #6

Open
myieye wants to merge 4 commits into
developfrom
claude/user-state-handling-refactor-3f161f
Open

[claude] Centralize FW Lite user state updates on login#6
myieye wants to merge 4 commits into
developfrom
claude/user-state-handling-refactor-3f161f

Conversation

@myieye

@myieye myieye commented Jul 29, 2026

Copy link
Copy Markdown
Owner

[Claude, autonomous]

Staging PR — never merge; promoted to sillsdev when polished (see FORK.md).

Supersedes sillsdev#2490 per hahn-kev's review: instead of scattered UpdateLastUser writes (listing, every sync, project open), login is now the write point. Resolves sillsdev#2491.

  • New ProjectServerInfoService listens for auth changes and stamps the signed-in user + roles (from the server project list) onto the local projects originating from that server — covers login from anywhere, including the sync dialog.
  • The listing path and post-upload path route through the same service; project open self-heals the user from the local MSAL cache (crash between login and stamping, project file copied from another machine).
  • Sync no longer writes user state; comment read-status attribution reads the MSAL cache directly.
  • GetLexboxProjects returns null on failure instead of caching an empty list, and cache entries are tied to the identity they were fetched under.
  • A project missing from the server list keeps its stored role (admins download projects they aren't members of; a server-side delete shouldn't strand unpushed edits as readonly) — unless the user changed, then the old user's role isn't trusted.
  • New ProjectDataChangedEvent pushes fresh ProjectData to the open viewer, so comment ownership/role update live after a mid-session login.

Carries over from sillsdev#2490: origin-scoped stamping (same GUID on two servers can't clobber), OAuthClient.GetCachedUser + the accounts helper, and the review-comment fixes (person-name test usernames).

ProjectData.LastUser* and Role were written from scattered places (project
listing, every sync, PR sillsdev#2490 would have added project open), each covering
a different subset of cases and none covering login from the sync dialog.
Per review feedback on sillsdev#2490, make login the write point: a new
ProjectServerInfoService listens for auth changes and stamps the signed-in
user and their roles onto the local projects originating from that server.
The listing path and post-upload path route through the same service, and
project open self-heals the user from the local MSAL cache (covers a crash
between login and stamping, or a project file copied from another machine).

Also:
- SyncService no longer writes user state; comment read-status attribution
  reads the MSAL cache directly, so it's right even when a login raced the
  stamping.
- GetLexboxProjects returns null on failure (instead of caching an empty
  list for 5 minutes) and ties cache entries to the identity they were
  fetched under.
- A project missing from the server list keeps its stored role (admins can
  download projects they aren't members of), unless the signed-in user
  changed, in which case the previous user's role isn't trusted.
- A new ProjectDataChangedEvent pushes fresh ProjectData to the open
  viewer, so comment ownership and role update live after a mid-session
  login instead of only at reopen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98026bcd-8926-4bc8-a356-1ec753c16f65

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
📝 Walkthrough

Walkthrough

Changes

The PR centralizes cached authentication reads, adds identity-aware project caching and server metadata refresh, updates project role synchronization, introduces project-data-changed events, and applies those events to the viewer’s project context.

Project synchronization

Layer / File(s) Summary
Cached authentication access
backend/FwLite/FwLiteShared/Auth/OAuthClient.cs, backend/FwLite/FwLiteShared.Tests/Auth/*
Cached MSAL account access is centralized and tested for account identity and empty-account behavior.
Project roles and identity-aware project caching
backend/FwLite/FwLiteShared/Projects/*, backend/FwLite/LcmCrdt/CrdtProjectsService.cs, backend/FwLite/LcmCrdt.Tests/Project/*
Role conversions, per-user project-list caching, nullable fetch results, and persisted user/role update behavior are implemented and tested.
Server metadata refresh and role resolution
backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs, backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs, backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs, backend/FwLite/FwLiteShared.Tests/Projects/*
ProjectServerInfoService refreshes server-owned projects, resolves roles, updates local metadata, publishes events, and is registered as a hosted singleton service.
Project opening and sync integration
backend/FwLite/FwLiteShared/Services/ProjectServicesProvider.cs, backend/FwLite/FwLiteShared/Sync/SyncService.cs
Project opening reconciles cached users, while synchronization refreshes server metadata and uses cached user identity for comment read-status updates.
Project-data event delivery
backend/FwLite/FwLiteShared/Events/*, frontend/viewer/src/lib/services/event-bus.ts, frontend/viewer/src/DotnetProjectView.svelte, frontend/viewer/src/project/project-context.svelte.ts, frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/*
Project-data-changed events are serialized, typed, routed through the viewer, buffered during setup, and applied to project context state.

Suggested reviewers: hahn-kev

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@deepsource-io

deepsource-io Bot commented Jul 29, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in e221be6...a3350ad on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Jul 29, 2026 6:58p.m. Review ↗
Docker Jul 29, 2026 6:58p.m. Review ↗
JavaScript Jul 29, 2026 6:58p.m. Review ↗
Shell Jul 29, 2026 6:58p.m. Review ↗
SQL Jul 29, 2026 6:58p.m. Review ↗
Secrets Jul 29, 2026 6:58p.m. Review ↗
PowerShell Jul 29, 2026 6:58p.m. Review ↗
CSS Jul 29, 2026 6:58p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

Comment on lines +194 to +196
function isProjectDataChangedEvent(event: IFwEvent): event is IProjectDataChangedEvent {
return event.type === FwEventType.ProjectDataChanged;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable


It is considered a best practice to avoid 'polluting' the global scope with variables that are intended to be local to the script. Global variables created from a script can produce name collisions with global variables created from another script, which will usually lead to runtime errors or unexpected behavior. It is mostly useful for browser scripts.

// Strict mock only stubs GetAccountsAsync, so these also prove GetCachedUser never reaches for a token.
// Note: MSAL's Username carries our Name claim, i.e. the person's name, not an email or login.
[Fact]
public async Task GetCachedUser_ReturnsAccountIdentity()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method with return type `Task` does not follow the naming convention


The consensus in .NET is to have names of methods dealing with asynchronous operations suffixed with Async. One such example is Stream.ReadAsync from System.IO. Doing so improves readability and provides crucial information at a glance.

}

[Fact]
public async Task GetCachedUser_ReturnsNull_WhenNoAccounts()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Method with return type `Task` does not follow the naming convention


The consensus in .NET is to have names of methods dealing with asynchronous operations suffixed with Async. One such example is Stream.ReadAsync from System.IO. Doing so improves readability and provides crucial information at a glance.

if (localProject?.Data is null) continue;
await crdtProjectsService.UpdateProjectServerInfo(localProject, lexboxUser?.Name, lexboxUser?.Id, ToRole(serverProject.Role));
}
return new(server, projectModels, lexboxProjects?.CanDownloadByCode ?? false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nullable boolean expression can be simplified


While the null coalescing operator may come in handy when evaluating the bool? types, it is recommended that you stick to the traditional equality operators and explicit boolean values — an approach that is more readable and easier to comprehend.

myieye and others added 2 commits July 29, 2026 20:26
Avoids the nullable-bool evaluation DeepSource flagged (CS-R1118) and
reads better than threading null through the model mapping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Devin caught a hole: ResolveOriginUser swapped LastUserId to the freshly
resolved user but kept the stored role, and once the id is swapped the
login path's trust-neither rule can never fire for that project. A project
file copied from another machine would keep the other user's permissions
indefinitely. Apply the same rule at open time and fetch the real role in
the background, which reaches the open viewer via ProjectDataChangedEvent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@myieye

myieye commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@backend/FwLite/FwLiteShared/Events/JsEventListener.cs`:
- Around line 17-20: Ensure the JsEventListener path does not lose
ProjectDataChangedEvent updates when the bounded queue reaches
MaxJsEventQueueSize. Make delivery lossless or coalesce pending project updates,
or have the viewer’s onProjectDataChanged flow rehydrate the latest project data
when attaching, while preserving unrelated Sync/EntriesChanged events.

In `@backend/FwLite/FwLiteShared/Projects/LexboxProjectService.cs`:
- Around line 57-75: Update GetLexboxProjects and its consumer flow to preserve
the user identity captured before FetchLexboxProjects across the await. Before
ApplyServerInfo applies roles, verify the returned project list still belongs to
the current cached user; otherwise reject it or propagate the captured UserId so
roles cannot be applied to a different account, while preserving valid cache
behavior.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 76958881-a2b3-4578-b161-b73871d5483e

📥 Commits

Reviewing files that changed from the base of the PR and between e221be6 and 6c19717.

📒 Files selected for processing (22)
  • backend/FwLite/FwLiteShared.Tests/Auth/OAuthClientIsSignedInTests.cs
  • backend/FwLite/FwLiteShared.Tests/Projects/ProjectServerInfoServiceTests.cs
  • backend/FwLite/FwLiteShared/Auth/OAuthClient.cs
  • backend/FwLite/FwLiteShared/Events/IFwEvent.cs
  • backend/FwLite/FwLiteShared/Events/JsEventListener.cs
  • backend/FwLite/FwLiteShared/Events/ProjectDataChangedEvent.cs
  • backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs
  • backend/FwLite/FwLiteShared/Projects/CombinedProjectsService.cs
  • backend/FwLite/FwLiteShared/Projects/LexboxProjectService.cs
  • backend/FwLite/FwLiteShared/Projects/ProjectRoleExtensions.cs
  • backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs
  • backend/FwLite/FwLiteShared/Services/ProjectServicesProvider.cs
  • backend/FwLite/FwLiteShared/Sync/SyncService.cs
  • backend/FwLite/LcmCrdt.Tests/Project/CurrentProjectServiceTests.cs
  • backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs
  • backend/FwLite/LcmCrdt/CrdtProjectsService.cs
  • frontend/viewer/src/DotnetProjectView.svelte
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/FwEventType.ts
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IProjectDataChangedEvent.ts
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/index.ts
  • frontend/viewer/src/lib/services/event-bus.ts
  • frontend/viewer/src/project/project-context.svelte.ts
💤 Files with no reviewable changes (1)
  • backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs

Comment on lines +17 to +20
//just a guess, this may need to be adjusted if we start losing events. Sized to absorb a login
//re-stamping user state on many projects at once (one ProjectDataChangedEvent each) without
//dropping unrelated events like Sync/EntriesChanged.
private const int MaxJsEventQueueSize = 64;

@coderabbitai coderabbitai Bot Jul 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and related symbols first.
ast-grep outline backend/FwLite/FwLiteShared/Events/JsEventListener.cs --view expanded || true

echo
echo "---- relevant references ----"
rg -n "MaxJsEventQueueSize|TryWrite|ProjectDataChangedEvent|Sync|EntriesChanged|Channel<|BoundedChannelFullMode|JsEventListener" backend/FwLite/FwLiteShared -S

Repository: myieye/languageforge-lexbox

Length of output: 27243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- JsEventListener.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/JsEventListener.cs | sed -n '1,220p'

echo
echo "---- ProjectEventBus.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/ProjectEventBus.cs | sed -n '1,220p'

echo
echo "---- IFwEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/IFwEvent.cs | sed -n '1,220p'

Repository: myieye/languageforge-lexbox

Length of output: 7036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- ProjectDataChangedEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/ProjectDataChangedEvent.cs | sed -n '1,120p'

echo
echo "---- EntriesChangedEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/EntriesChangedEvent.cs | sed -n '1,120p'

echo
echo "---- SyncEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/SyncEvent.cs | sed -n '1,120p'

echo
echo "---- any JS consumer of JsEventListener ----"
rg -n "JsEventListener|onJsEvent|ProjectDataChangedEvent|EntriesChangedEvent|SyncEvent|DotNetObjectReference<JsEventListener>|InvokeMethodAsync|JSInvokable" backend/FwLite -S

Repository: myieye/languageforge-lexbox

Length of output: 16429


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- JsEventListener.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/JsEventListener.cs | sed -n '1,220p'

Repository: myieye/languageforge-lexbox

Length of output: 3556


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- GlobalEventBus.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/GlobalEventBus.cs | sed -n '1,260p'

echo
echo "---- JS/TS consumers of JsEventListener and NextEventAsync ----"
rg -n "NextEventAsync|LastEvent\\(|JsEventListener|DotNetObjectReference<JsEventListener>|getLastEvent|ProjectDataChanged" backend/FwLite/FwLiteWeb backend/FwLite/FwLiteMaui backend/FwLite -g '!**/bin/**' -g '!**/obj/**' -S

Repository: myieye/languageforge-lexbox

Length of output: 3719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- ProjectEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/ProjectEvent.cs | sed -n '1,220p'

echo
echo "---- direct publications of ProjectDataChangedEvent / ProjectEvent ----"
rg -n "new ProjectDataChangedEvent|new ProjectEvent\\(|PublishEvent\\(project, new ProjectDataChangedEvent|FwEventType\\.ProjectDataChanged|LastEvent\\(FwEventType" . -S

echo
echo "---- any frontend callers of JsEventListener methods ----"
rg -n "NextEventAsync\\(|LastEvent\\(" . -S --glob '!**/bin/**' --glob '!**/obj/**'

Repository: myieye/languageforge-lexbox

Length of output: 2143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- ProjectEvent.cs ----"
cat -n backend/FwLite/FwLiteShared/Events/ProjectEvent.cs | sed -n '1,220p'

echo
echo "---- direct publications of ProjectDataChangedEvent / ProjectEvent ----"
rg -n "new ProjectDataChangedEvent|new ProjectEvent\\(|PublishEvent\\(project, new ProjectDataChangedEvent|FwEventType\\.ProjectDataChanged|LastEvent\\(FwEventType" . -S --glob '!**/bin/**' --glob '!**/obj/**'

echo
echo "---- any frontend callers of JsEventListener methods ----"
rg -n "NextEventAsync\\(|LastEvent\\(" . -S --glob '!**/bin/**' --glob '!**/obj/**'

Repository: myieye/languageforge-lexbox

Length of output: 2143


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- frontend/viewer/src/lib/services/event-bus.ts (relevant slice) ----"
cat -n frontend/viewer/src/lib/services/event-bus.ts | sed -n '1,280p'

Repository: myieye/languageforge-lexbox

Length of output: 8330


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- frontend usages of onProjectDataChanged / getLastEvent / includeLast ----"
rg -n "onProjectDataChanged\\(|getLastEvent\\(|includeLast|onSync\\(|onEntriesChanged\\(|onEntryUpdated\\(|onEntryDeleted\\(" frontend -S --glob '!**/bin/**' --glob '!**/obj/**'

Repository: myieye/languageforge-lexbox

Length of output: 2070


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- frontend/viewer/src/DotnetProjectView.svelte ----"
cat -n frontend/viewer/src/DotnetProjectView.svelte | sed -n '1,220p'

echo
echo "---- project server info update path ----"
cat -n backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs | sed -n '70,120p'

Repository: myieye/languageforge-lexbox

Length of output: 8108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- frontend/viewer/src/DotnetProjectView.svelte ----"
cat -n frontend/viewer/src/DotnetProjectView.svelte | sed -n '1,220p'

echo
echo "---- backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs ----"
cat -n backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs | sed -n '70,120p'

Repository: myieye/languageforge-lexbox

Length of output: 8141


Avoid dropping ProjectDataChangedEvent updates. TryWrite still drops events when the bounded queue fills, and the viewer’s onProjectDataChanged path consumes only the live stream, so a login burst can leave the open project with stale user/role data. Make this path lossless/coalesced, or rehydrate the latest project data when the listener attaches.

🤖 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/FwLite/FwLiteShared/Events/JsEventListener.cs` around lines 17 - 20,
Ensure the JsEventListener path does not lose ProjectDataChangedEvent updates
when the bounded queue reaches MaxJsEventQueueSize. Make delivery lossless or
coalesce pending project updates, or have the viewer’s onProjectDataChanged flow
rehydrate the latest project data when attaching, while preserving unrelated
Sync/EntriesChanged events.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[Claude, autonomous]

Fixed in a3350ad: the channel now uses BoundedChannelFullMode.DropOldest, so the newest event always lands — state-typed events (sync status, project data) are superseded by later ones, meaning the final state gets through even under pressure. Full losslessness isn't needed for correctness here: the persisted ProjectData is the source of truth and the open project re-reads it at open.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- event listener ----"
cat -n backend/FwLite/FwLiteShared/Events/JsEventListener.cs | sed -n '12,95p'

echo
echo "---- event buses and project-data publisher ----"
for file in \
  backend/FwLite/FwLiteShared/Events/GlobalEventBus.cs \
  backend/FwLite/FwLiteShared/Events/ProjectEventBus.cs \
  backend/FwLite/FwLiteShared/Events/ProjectEvent.cs \
  backend/FwLite/FwLiteShared/Projects/ProjectServerInfoService.cs
do
  if [ -f "$file" ]; then
    echo "---- $file ----"
    rg -n -C 5 'ProjectDataChanged|Publish|GetLastEvent|OnGlobalEvent|OnProjectEvent' "$file" || true
  fi
done

echo
echo "---- viewer subscriptions and hydration ----"
for file in \
  frontend/viewer/src/lib/services/event-bus.ts \
  frontend/viewer/src/DotnetProjectView.svelte \
  frontend/viewer/src/project/project-context.svelte.ts
do
  if [ -f "$file" ]; then
    echo "---- $file ----"
    rg -n -C 6 'onProjectDataChanged|getLastEvent|LastEvent|projectData|updateProjectData|serviceLoaded' "$file" || true
  fi
done

Length of output: 13281


@myieye, thanks — DropOldest is an improvement because it avoids rejecting the newly written event when the queue is full. However, the finding remains valid for an already-open viewer.

A ProjectDataChangedEvent can still be evicted by any subsequent 64 events before NextEventAsync() reads it. The persisted-data reread only fixes state on a later project open; it does not repair an open viewer whose only update path is the live event stream:

  • ProjectEventBus.onProjectDataChanged() (Lines 154-160 in frontend/viewer/src/lib/services/event-bus.ts) has no latest-event replay.
  • DotnetProjectView.svelte applies updates after load only upon receiving that live event (Lines 38-40).

Please retain or reintroduce a per-project rehydration/latest-state mechanism—for example, cache/replay the latest ProjectDataChangedEvent per project when subscribing, or coalesce pending project-data events by project—so unrelated bursts cannot leave a currently open project with stale user/role data.

Comment thread backend/FwLite/FwLiteShared/Projects/LexboxProjectService.cs Outdated
…overflow

Two CodeRabbit findings:
- GetLexboxProjects could return a list fetched under user A after B logged
  in mid-fetch, letting ApplyServerInfo stamp B with A's roles. The result
  now carries the identity it was fetched under and ApplyServerInfo treats
  a mismatch as no fresh knowledge.
- The bounded JS event channel dropped the newest event when full, so the
  last ProjectDataChangedEvent of a burst could be lost and leave the open
  project stale. DropOldest guarantees the final state gets through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FW Lite shows the wrong current user when a project's GUID exists on more than one logged-in server

1 participant