Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 28, 2025

Resolves: #2082
(https://community.software.sil.org/t/desktop-version-force-update/10790)

Also added a link to release notes as a quick way to resolve https://community.software.sil.org/t/add-changelog-1-vote/10789.

Main dialog states:

If auto-update is supported:

image

Auto-update not supported

image

Already up to date

image

Menu items:

image image

Detailed states:

image image image image image image image image image

(Should never happen, because you can't trigger an install if it's not supported:)

image image image

Added "Platform" to troubleshooting dialog

image

@coderabbitai
Copy link

coderabbitai bot commented Oct 28, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

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

📝 Walkthrough

Walkthrough

Implements comprehensive update checking and installation UI across backend and frontend. Adds event-driven progress reporting in Windows AppUpdateService, introduces caching and AvailableUpdate model in UpdateChecker, exposes update services via JS interop, creates UpdateDialog and UpdateDialogContent components, generates TypeScript type definitions, and extends localization across multiple languages.

Changes

Cohort / File(s) Summary
Backend Update Service Core
backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs, backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs, backend/FwLite/FwLiteShared/Services/UpdateService.cs
Extended AppUpdateService to inject GlobalEventBus and publish progress events; refactored UpdateChecker with caching, new AvailableUpdate record, and modified public signatures (TryUpdate, CheckForUpdate, ApplyUpdate); added new UpdateService exposing CheckForUpdates and ApplyUpdate methods via JSInvokable.
Backend Event System
backend/FwLite/FwLiteShared/Events/AppUpdateEvent.cs, backend/FwLite/FwLiteShared/Events/AppUpdateProgressEvent.cs, backend/FwLite/FwLiteShared/Events/IFwEvent.cs
Extended AppUpdateEvent to include Release property; introduced new AppUpdateProgressEvent class with percentage and release; added AppUpdateProgress to FwEventType enum and registered JsonDerivedType.
Backend Configuration & DI
backend/FwLite/FwLiteShared/AppUpdate/IPlatformUpdateService.cs, backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs, backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs
Added Disallowed enum member to UpdateResult; registered memory cache and UpdateService singleton in DI container; added UpdateService to DotnetService enum and ExportedServices.
Backend TypeGen Configuration
backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs
Exported FwLiteRelease and AvailableUpdate to TypeScript type generation.
Frontend Service Layer
frontend/viewer/src/lib/services/service-provider.ts, frontend/viewer/src/project/demo/in-memory-demo-api.ts
Added UpdateService to LexboxServiceRegistry and created useUpdateService hook; implemented mock UpdateService in demo API.
Frontend UI Components
frontend/viewer/src/lib/updates/UpdateDialog.svelte, frontend/viewer/src/lib/updates/UpdateDialogContent.svelte, frontend/viewer/src/home/HomeView.svelte, frontend/viewer/src/project/ProjectSidebar.svelte
Introduced UpdateDialog and UpdateDialogContent components with state management, progress tracking, and result handling; added update menu items to HomeView and ProjectSidebar.
Frontend Utilities & Support
frontend/viewer/src/lib/updates/utils.ts, frontend/viewer/src/lib/notifications/NotificationOutlet.svelte, frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte, frontend/viewer/src/stories/demo-story-error.ts
Added URL handling utilities (openReleaseUrl, getReleaseUrl, constants); refactored NotificationOutlet to use release data directly; updated TroubleshootDialog layout; added DemoStoryError class.
Frontend Type Definitions
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/*, frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/*, frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/*, frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts
Generated IAvailableUpdate interface; updated UpdateResult enum with Disallowed; generated IAppUpdateProgressEvent; extended IAppUpdateEvent with release; created IUpdateService; updated FwEventType enum; created index barrel exports; added IFwLiteRelease.
Frontend Localization
frontend/viewer/src/locales/en.po, frontend/viewer/src/locales/es.po, frontend/viewer/src/locales/fr.po, frontend/viewer/src/locales/id.po, frontend/viewer/src/locales/ko.po, frontend/viewer/src/locales/ms.po, frontend/viewer/src/locales/sw.po
Added update-related translation entries across all locales (CheckForUpdates, Download/Install prompts, status messages); reorganized component path references for OpenInFieldWorksButton and ThemePicker.
Frontend Storybook
frontend/viewer/.storybook/vitest.setup.ts, frontend/viewer/src/stories/updates/update-dialog-content.stories.svelte
Enhanced error filtering in vitest setup to ignore DemoStoryError; created comprehensive story suite for UpdateDialogContent covering check/install states and outcomes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • UpdateChecker refactoring — caching logic, IMemoryCache integration, modified return types (UpdateResult?, AvailableUpdate?), and control flow changes across CheckForUpdate, TryUpdate, and ApplyUpdate methods
  • Event-driven architecture — GlobalEventBus injection in AppUpdateService, AppUpdateProgressEvent publishing flow, and event propagation to frontend
  • Frontend state management — UpdateDialog and UpdateDialogContent component interactions with promises, progress tracking, error handling, and multiple install result paths
  • JS interop contract — JSInvokable method signatures, parameter passing (AvailableUpdate), and async operation semantics between frontend and backend
  • Localization consistency — ensure all new UI strings are translated across all 7 language files without duplicates or missing entries
  • Generated TypeScript types — verify type generation accuracy for AvailableUpdate, IUpdateService, and event types aligns with backend definitions

Possibly related PRs

Suggested reviewers

  • jasonleenaylor

Poem

🐰 A rabbit hops through code with glee,
Progress events flow wild and free,
Updates checked with caches fast,
Dialogs prompt, no more a blast,
TypeScript types now perfectly aligned,
Seven tongues to updates bind! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive While most changes align with update UI objectives, several modifications appear tangential to the core feature: extensive .po file reorganization (theme/component path migrations), unused import cleanup, platform display additions to troubleshooting dialog, and demo error handling improvements. Review whether .po file reorganizations, troubleshooting dialog platform display, and demo error handling are necessary for this PR's core update UI feature, or if they should be in separate PRs.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add UI to check for and trigger updates' clearly summarizes the main change—introducing user-facing UI for checking and triggering application updates.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, providing context about the feature's purpose, linked issues, UI screenshots, and implementation details.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #2082: UI for checking updates, platform-aware auto-update vs. manual download handling (SupportsAutoUpdate flag), update triggering functionality, and appropriate dialog placement on the home page.

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

Copilot AI changed the title [WIP] Add UI for checking and triggering updates Add UI to check for and trigger updates Oct 28, 2025
Copilot AI requested a review from myieye October 28, 2025 17:18
@github-actions github-actions bot added the 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related label Nov 11, 2025
@github-actions
Copy link

github-actions bot commented Nov 11, 2025

UI unit Tests

  1 files  ± 0   47 suites  +2   21s ⏱️ +2s
123 tests +12  123 ✅ +12  0 💤 ±0  0 ❌ ±0 
184 runs  +24  184 ✅ +24  0 💤 ±0  0 ❌ ±0 

Results for commit 0ab90da. ± Comparison against base commit 0375e76.

♻️ This comment has been updated with latest results.

@github-actions
Copy link

github-actions bot commented Nov 11, 2025

C# Unit Tests

137 tests  ±0   137 ✅ ±0   19s ⏱️ ±0s
 21 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 0ab90da. ± Comparison against base commit 0375e76.

♻️ This comment has been updated with latest results.

@myieye myieye force-pushed the copilot/add-update-check-ui branch from dceaabd to 701d527 Compare November 11, 2025 15:20
@myieye
Copy link
Collaborator

myieye commented Nov 11, 2025

@copilot I exposed a better API for the UI to check for updates and then install an update.

Please refactor to use that instead of the complicated event triggering and checking you added.
Also, note the SupportsAutoUpdate which the UI should use to decide if an install button is used or a link to the download page.
There shouldn't be any notifications if a manual install is required. Everything should be contained within the dialog.

Copy link
Contributor Author

Copilot AI commented Nov 11, 2025

@copilot I exposed a better API for the UI to check for updates and then install an update.

Please refactor to use that instead of the complicated event triggering and checking you added.
...

Refactored to use the new API in commit 0239a84.

The implementation now:

  • Uses checkForUpdates() which returns IAvailableUpdate | null directly (no event subscriptions)
  • Shows "Install Update" button when supportsAutoUpdate is true
  • Shows "Go to Download Page" link (to release.url) when supportsAutoUpdate is false
  • Everything is contained within the dialog
  • Removed all the complex event triggering/timeout logic

The UI adapts based on the supportsAutoUpdate flag:

  • Auto-update supported: Primary "Install Update" button → calls applyUpdate()
  • Manual update required: Primary download link button → opens release.url in new tab

@myieye
Copy link
Collaborator

myieye commented Nov 11, 2025

@copilot There are a lot of bools/flags in the dialog. Is there any way to make it cleaner? Maybe by using promises? Or would that get more chaotic?

Also, I don't think we need "Check Again" buttons. Let's just replace the Check update button with "no updates", "download" or "install". Or is there a good reason not to do that?

Also, the Button component has a loading state and flex-gap, so use those instead of doing them yourself. (e.g. mr-2 and i-mdi-loading)

context="module" should just be module. And you don't actually need that script tag. The import should be in the other script tag.

Make the platform <DevContent>. I kind of like it, but it's just noise for the average user.

Copy link
Contributor Author

Copilot AI commented Nov 11, 2025

@copilot There are a lot of bools/flags in the dialog. Is there any way to make it cleaner? Maybe by using promises? Or would that get more chaotic?

Also, I don't think we need "Check Again" button...

Implemented all requested changes in commit 810d647:

Reduced boolean flags:

  • Replaced 6 state variables with 3 (using promises)
  • Use Svelte's {#await} blocks for declarative state handling

Removed "Check Again" buttons:

  • Main button now adapts based on state: "Check for Updates" → "Install Update" / "Go to Download Page" → back to "Check for Updates"

Use Button component features:

  • Using loading prop instead of manual spinner
  • Using icon prop instead of manual Icon components with mr-2

Fixed script issues:

  • Changed context="module" to just having one script tag
  • Removed unused cn import

Backend cache:

  • Implemented 5-minute memory cache in CheckForUpdate() per TODO comment

Fixed platform display:

  • Removed .toString() call on config.os

@myieye
Copy link
Collaborator

myieye commented Nov 11, 2025

@copilot use the standard .net MemoryCache

Copy link
Contributor Author

Copilot AI commented Nov 11, 2025

@copilot use the standard .net MemoryCache

Replaced custom cache implementation with standard .NET MemoryCache in commit 1b88fbe.

Changes:

  • Injected IMemoryCache via constructor
  • Using GetOrCreateAsync pattern with 5-minute absolute expiration
  • Added AddMemoryCache() to FwLiteSharedKernel service registration
  • Removed manual cache fields and time tracking

@myieye myieye force-pushed the copilot/add-update-check-ui branch from 1b88fbe to 4f65a56 Compare November 24, 2025 15:24
@argos-ci
Copy link

argos-ci bot commented Nov 24, 2025

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

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 4 changed Dec 5, 2025, 10:20 AM

@myieye myieye force-pushed the copilot/add-update-check-ui branch from 5e67741 to ae17f94 Compare November 25, 2025 09:21
@myieye myieye marked this pull request as ready for review November 28, 2025 10:09
@myieye myieye added the 🟨Medium Medium-priority PR label Nov 28, 2025
@myieye
Copy link
Collaborator

myieye commented Dec 1, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

✅ Actions performed

Review triggered.

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
frontend/viewer/.storybook/vitest.setup.ts (1)

5-5: Ignoring DemoStoryError in test-time error collection is sensible; consider DRY-ing the predicate

Treating DemoStoryError as ignorable across console errors, window.error, and unhandledrejection lets demo stories intentionally throw without breaking tests, while still logging via originalError, which fits the Storybook/demo use case. Based on learnings, this is exactly the intended behavior for such demo errors.

You might optionally extract the shared isIgnored logic (checking DemoStoryError and ignoredErrorPatterns) into a small helper to avoid repeating the same predicate in three places and keep future changes in sync, but this is purely a nicety.

Also applies to: 45-47, 55-57, 62-64

frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte (1)

54-57: Optionally gate Platform display behind a dev‑only wrapper

The always‑visible Platform row is useful for support, but may be noise for typical users. Since this is a Troubleshoot dialog it’s probably acceptable, but if you want to match the earlier suggestion from the PR comments, you could wrap this row in your <DevContent> (or equivalent) so it only shows up in diagnostic/dev contexts.

Purely optional, but would keep the primary UI a bit cleaner while still exposing platform info where needed.

frontend/viewer/src/lib/updates/UpdateDialog.svelte (2)

21-25: Consider cleanup for event subscription.

The event listener for AppUpdateProgress is registered but may not be cleaned up when the component is destroyed. While this might be acceptable for a global event bus with a long-lived component, consider using Svelte's $effect with a cleanup function to unsubscribe when the component unmounts.

Example refactor:

-const eventBus = useEventBus();
-let installProgress = $state<number>();
-eventBus.onEventType<IAppUpdateProgressEvent>(FwEventType.AppUpdateProgress, event => {
-  installProgress = event.percentage;
-});
+const eventBus = useEventBus();
+let installProgress = $state<number>();
+
+$effect(() => {
+  const unsubscribe = eventBus.onEventType<IAppUpdateProgressEvent>(FwEventType.AppUpdateProgress, event => {
+    installProgress = event.percentage;
+  });
+  return unsubscribe;
+});

27-32: Consider resetting install progress when dialog opens.

When the dialog reopens, installProgress from a previous install operation may still be displayed. Consider resetting it alongside the check promise.

 watch(() => open, () => {
   if (open) {
+    installProgress = undefined;
     // this is cached on the backend for a short time
     checkPromise = updateService.checkForUpdates();
   }
 });
frontend/viewer/src/lib/updates/UpdateDialogContent.svelte (1)

9-14: Consider making installUpdate optional with a runtime guard.

The installUpdate prop is defined as required, but it's only used when checkPromise resolves with an available update. If callers only want to display status (e.g., in a read-only context), they must still provide this function.

Alternatively, this design is acceptable if all callers always provide installUpdate.

  type Props = {
    checkPromise?: Promise<IAvailableUpdate | null>;
    installPromise?: Promise<UpdateResult>;
-   installUpdate: (update: IAvailableUpdate) => Promise<void>;
+   installUpdate?: (update: IAvailableUpdate) => Promise<void>;
    installProgress?: number;
  }

If changed, add a guard at line 85:

-        <Button onclick={() => installUpdate(availableUpdate)} class="w-full" icon="i-mdi-download">
+        <Button onclick={() => installUpdate?.(availableUpdate)} class="w-full" icon="i-mdi-download" disabled={!installUpdate}>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0957ed and 52b9d72.

📒 Files selected for processing (39)
  • backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs (3 hunks)
  • backend/FwLite/FwLiteShared/AppUpdate/IPlatformUpdateService.cs (1 hunks)
  • backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs (1 hunks)
  • backend/FwLite/FwLiteShared/Events/AppUpdateEvent.cs (1 hunks)
  • backend/FwLite/FwLiteShared/Events/AppUpdateProgressEvent.cs (1 hunks)
  • backend/FwLite/FwLiteShared/Events/IFwEvent.cs (2 hunks)
  • backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (2 hunks)
  • backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (3 hunks)
  • backend/FwLite/FwLiteShared/Services/UpdateService.cs (1 hunks)
  • backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (1 hunks)
  • frontend/viewer/.storybook/vitest.setup.ts (4 hunks)
  • frontend/viewer/src/home/HomeView.svelte (4 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/IAvailableUpdate.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/UpdateResult.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/index.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/FwEventType.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IAppUpdateEvent.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IAppUpdateProgressEvent.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/index.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IUpdateService.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts (1 hunks)
  • frontend/viewer/src/lib/notifications/NotificationOutlet.svelte (1 hunks)
  • frontend/viewer/src/lib/services/service-provider.ts (3 hunks)
  • frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte (1 hunks)
  • frontend/viewer/src/lib/updates/UpdateDialog.svelte (1 hunks)
  • frontend/viewer/src/lib/updates/UpdateDialogContent.svelte (1 hunks)
  • frontend/viewer/src/lib/updates/utils.ts (1 hunks)
  • frontend/viewer/src/locales/en.po (22 hunks)
  • frontend/viewer/src/locales/es.po (22 hunks)
  • frontend/viewer/src/locales/fr.po (22 hunks)
  • frontend/viewer/src/locales/id.po (22 hunks)
  • frontend/viewer/src/locales/ko.po (22 hunks)
  • frontend/viewer/src/locales/ms.po (22 hunks)
  • frontend/viewer/src/locales/sw.po (22 hunks)
  • frontend/viewer/src/project/ProjectSidebar.svelte (5 hunks)
  • frontend/viewer/src/project/demo/in-memory-demo-api.ts (3 hunks)
  • frontend/viewer/src/stories/demo-story-error.ts (1 hunks)
  • frontend/viewer/src/stories/updates/update-dialog-content.stories.svelte (1 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-08-14T12:50:25.135Z
Learnt from: myieye
Repo: sillsdev/languageforge-lexbox PR: 1906
File: frontend/viewer/src/lib/components/ui/dialog-shared/dialog-shared-root.svelte:3-3
Timestamp: 2025-08-14T12:50:25.135Z
Learning: In the dialog-shared-root.svelte component, the module-level `openDialogs` state is intentionally shared across all component instances to coordinate dialog stacking and overlay behavior across the entire application. This enables proper z-index management where newer dialogs appear on top and only the bottom dialog shows its overlay.

Applied to files:

  • frontend/viewer/src/lib/troubleshoot/TroubleshootDialog.svelte
  • frontend/viewer/src/home/HomeView.svelte
  • frontend/viewer/src/lib/updates/UpdateDialog.svelte
  • frontend/viewer/src/project/ProjectSidebar.svelte
📚 Learning: 2025-07-02T13:55:24.775Z
Learnt from: myieye
Repo: sillsdev/languageforge-lexbox PR: 1800
File: frontend/viewer/src/stories/notifications/notification.stories.svelte:82-82
Timestamp: 2025-07-02T13:55:24.775Z
Learning: In Storybook stories and test/demo files, intentional error throwing (including unhandled promise rejections) is acceptable and expected behavior when testing error handling systems. Buttons labeled "Throw Exception" or similar are designed to trigger errors to verify that global error handlers work correctly.

Applied to files:

  • frontend/viewer/src/stories/demo-story-error.ts
  • frontend/viewer/.storybook/vitest.setup.ts
📚 Learning: 2025-03-06T03:30:17.687Z
Learnt from: hahn-kev
Repo: sillsdev/languageforge-lexbox PR: 1522
File: frontend/viewer/src/lib/services/service-provider.ts:41-48
Timestamp: 2025-03-06T03:30:17.687Z
Learning: In the LexboxServiceProvider class, the default implementation of `nextEventAsync()` for the JsEventListener service intentionally returns an unresolved Promise (`new Promise<IFwEvent>(() => {})`) rather than a resolved one. This is a deliberate design choice related to event handling.

Applied to files:

  • frontend/viewer/src/project/demo/in-memory-demo-api.ts
📚 Learning: 2025-06-02T14:27:02.745Z
Learnt from: myieye
Repo: sillsdev/languageforge-lexbox PR: 1720
File: frontend/viewer/src/locales/es.json:1786-1790
Timestamp: 2025-06-02T14:27:02.745Z
Learning: Spanish locale file (frontend/viewer/src/locales/es.json) contains generated code that may have null line numbers in origin entries due to limitations in the code generation process.

Applied to files:

  • frontend/viewer/src/locales/es.po
📚 Learning: 2025-07-22T09:19:37.386Z
Learnt from: rmunn
Repo: sillsdev/languageforge-lexbox PR: 1836
File: frontend/viewer/src/lib/components/audio/AudioDialog.svelte:25-25
Timestamp: 2025-07-22T09:19:37.386Z
Learning: In the sillsdev/languageforge-lexbox project, when file size limits or other constants need to be shared between C# backend and TypeScript frontend code, prefer exposing them through Reinforced.Typings type generation rather than hardcoding the values separately. This ensures consistency and prevents discrepancies when values change.

Applied to files:

  • backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs
📚 Learning: 2025-07-07T06:02:41.194Z
Learnt from: hahn-kev
Repo: sillsdev/languageforge-lexbox PR: 1804
File: backend/FwLite/LcmCrdt/CurrentProjectService.cs:18-19
Timestamp: 2025-07-07T06:02:41.194Z
Learning: In the CurrentProjectService class, the service locator pattern is intentionally used to retrieve IDbContextFactory<LcmCrdtDbContext> and EntrySearchServiceFactory because these services indirectly depend on CurrentProjectService to have the current project set, creating a circular dependency. This is an acceptable use of service locator to break the circular dependency while keeping project context responsibility consolidated.

Applied to files:

  • frontend/viewer/src/lib/services/service-provider.ts
📚 Learning: 2025-10-06T12:48:36.601Z
Learnt from: myieye
Repo: sillsdev/languageforge-lexbox PR: 2025
File: frontend/viewer/src/locales/sw.po:146-149
Timestamp: 2025-10-06T12:48:36.601Z
Learning: Do not review or comment on `.po` translation files (e.g., files in `frontend/viewer/src/locales/`) in the languageforge-lexbox repository, as translations are managed by Crowdin.

Applied to files:

  • frontend/viewer/src/locales/en.po
  • frontend/viewer/src/locales/id.po
📚 Learning: 2025-08-15T07:32:23.641Z
Learnt from: hahn-kev
Repo: sillsdev/languageforge-lexbox PR: 1923
File: frontend/viewer/src/locales/en.po:845-848
Timestamp: 2025-08-15T07:32:23.641Z
Learning: Do not suggest specific translations for missing msgstr entries in locale files. Only identify that translations are missing without proposing the actual translated text.

Applied to files:

  • frontend/viewer/src/locales/en.po
📚 Learning: 2025-06-24T03:15:09.437Z
Learnt from: hahn-kev
Repo: sillsdev/languageforge-lexbox PR: 1770
File: backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs:21-21
Timestamp: 2025-06-24T03:15:09.437Z
Learning: In the FwLite Windows app update system, update checks only occur once on application startup, so there's typically only one notification at a time in the NotificationCompletionSources dictionary.

Applied to files:

  • backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs
📚 Learning: 2025-06-24T03:13:09.152Z
Learnt from: hahn-kev
Repo: sillsdev/languageforge-lexbox PR: 1770
File: backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs:27-47
Timestamp: 2025-06-24T03:13:09.152Z
Learning: In UpdateChecker.cs, the LastUpdateCheck timestamp is intentionally set after every HTTP call attempt (successful or failed) to enforce the 8-hour update check interval regardless of network issues. This design choice prevents retry attempts and reduces server load.

Applied to files:

  • backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs
🧬 Code graph analysis (13)
backend/FwLite/FwLiteShared/Events/AppUpdateEvent.cs (1)
backend/FwLite/FwLiteShared/Events/GlobalEventBus.cs (1)
  • IFwEvent (24-27)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IUpdateService.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/IAvailableUpdate.ts (1)
  • IAvailableUpdate (8-12)
frontend/viewer/src/lib/updates/utils.ts (3)
frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts (1)
  • IFwLiteRelease (6-10)
frontend/viewer/src/lib/services/url-opener.ts (1)
  • openUrl (5-19)
frontend/viewer/src/lib/services/service-provider.ts (1)
  • useFwLiteConfig (116-118)
backend/FwLite/FwLiteShared/Events/IFwEvent.cs (1)
backend/FwLite/FwLiteShared/Events/AppUpdateProgressEvent.cs (1)
  • AppUpdateProgressEvent (5-11)
frontend/viewer/.storybook/vitest.setup.ts (1)
frontend/viewer/src/stories/demo-story-error.ts (1)
  • DemoStoryError (2-2)
backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (1)
backend/FwLite/FwLiteShared/Services/UpdateService.cs (1)
  • UpdateService (7-21)
backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (1)
backend/FwLite/FwLiteShared/Services/UpdateService.cs (1)
  • UpdateService (7-21)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/IAvailableUpdate.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts (1)
  • IFwLiteRelease (6-10)
backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (1)
backend/FwLite/FwLiteShared/Services/ProjectServicesProvider.cs (3)
  • ProjectScope (118-187)
  • ProjectScope (128-141)
  • ProjectScope (143-165)
frontend/viewer/src/lib/services/service-provider.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IUpdateService.ts (1)
  • IUpdateService (9-13)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IAppUpdateEvent.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts (1)
  • IFwLiteRelease (6-10)
backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs (2)
backend/FwLite/FwLiteShared/Events/GlobalEventBus.cs (2)
  • GlobalEventBus (8-34)
  • PublishEvent (16-22)
backend/FwLite/FwLiteShared/Events/AppUpdateProgressEvent.cs (1)
  • AppUpdateProgressEvent (5-11)
backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs (2)
backend/FwLite/FwLiteShared/AppUpdate/IPlatformUpdateService.cs (2)
  • Task (11-11)
  • Task (12-12)
backend/FwLite/FwLiteShared/AppUpdate/CorePlatformUpdateService.cs (2)
  • Task (26-29)
  • Task (31-34)
🔇 Additional comments (53)
frontend/viewer/src/stories/demo-story-error.ts (1)

1-2: DemoStoryError marker class is clear and appropriate

Using a dedicated DemoStoryError subclass as a marker for non-fatal Storybook/demo errors is a clean, minimal approach and matches the intent of allowing intentional errors without breaking tests.

frontend/viewer/.storybook/vitest.setup.ts (1)

69-75: Updated failure message improves diagnostics for cross-story errors

Clarifying the thrown error message to note errors may have occurred “(potentially in previous story)” makes debugging multi-story runs easier and matches the collection semantics where errors are accumulated until afterEach. Behavior is otherwise unchanged.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts (1)

20-21: Enum extension for UpdateService looks consistent

UpdateService is added as a string enum member alongside other backend services and will be picked up by the existing DotnetService→service-type wiring in ReinforcedFwLiteTypingConfig. No issues from the TS side.

backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (1)

25-25: DI wiring for caching and UpdateService is appropriate

Registering AddMemoryCache() here aligns with UpdateChecker’s IMemoryCache usage, and making UpdateService a singleton alongside the singleton UpdateChecker (also reused as an IHostedService) is consistent and safe for this stateless wrapper.

Also applies to: 45-51

frontend/viewer/src/home/HomeView.svelte (1)

31-31: Update dialog integration into Home view is clean and consistent

updateDialogOpen follows the same $state + bind:open pattern as feedbackOpen, and the “Updates” responsive menu item cleanly hooks into UpdateDialog without impacting existing actions.

Also applies to: 135-137, 165-167, 178-178

backend/FwLite/FwLiteShared/Events/IFwEvent.cs (1)

12-12: AppUpdate progress event is correctly wired into the event system

Registering AppUpdateProgressEvent as a JsonDerivedType and adding AppUpdateProgress to FwEventType keeps the polymorphic serialization and TypeScript enum surface in sync with the new progress event.

Also applies to: 29-29

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IAppUpdateEvent.ts (1)

8-8: Adding release to IAppUpdateEvent aligns with backend payload

Importing IFwLiteRelease and exposing release on IAppUpdateEvent matches the updated backend event and gives the frontend direct access to version and URL metadata for update notifications.

Also applies to: 14-14

backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (1)

177-180: New exported interfaces for update flow are correctly surfaced

Including ProjectScope, FwLiteRelease, and AvailableUpdate in the exported-interface list ensures the generated TS layer can consume the new update and release types (and any ProjectScope projections) without manual typings.

backend/FwLite/FwLiteShared/AppUpdate/IPlatformUpdateService.cs (1)

22-23: UpdateResult.Disallowed is a reasonable extension—check downstream handling

Adding Disallowed alongside ManualUpdateRequired is compatible with the existing string-based enum serialization. Please just double-check any C#/TS switches on UpdateResult to ensure this new case is either handled explicitly or covered by a suitable default branch.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/index.ts (1)

1-2: AppUpdate barrel exports are straightforward and helpful

Re-exporting IAvailableUpdate and UpdateResult via this index file makes consumer imports cleaner while staying aligned with the generated type structure.

frontend/viewer/src/project/ProjectSidebar.svelte (3)

28-28: LGTM!

The UpdateDialog import follows the project's conventions and integrates correctly with the broader update flow.


57-57: LGTM!

The state variable follows the established pattern for dialog management in this component.


188-193: LGTM!

The UpdateDialog integration follows the established pattern:

  • Menu item correctly triggers the dialog state
  • Dialog is properly mounted outside the sidebar to prevent destruction during mobile transitions (as noted in the comment)
  • Consistent with other dialogs like TroubleshootDialog and FeedbackDialog

Also applies to: 225-225

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/FwEventType.ts (1)

12-13: LGTM!

The addition of the AppUpdateProgress event type enables progress reporting for update operations, complementing the existing AppUpdate event type.

frontend/viewer/src/lib/services/service-provider.ts (3)

22-23: LGTM!

The import follows the project's conventions for accessing generated TypeScript types from the backend.


40-40: LGTM!

The UpdateService registration in the LexboxServiceRegistry follows the established pattern and maintains type safety.


128-130: LGTM!

The useUpdateService hook follows the established pattern for required services, using getService which will throw if the service is unavailable.

frontend/viewer/src/lib/notifications/NotificationOutlet.svelte (2)

9-9: LGTM!

Importing the shared utility function centralizes release URL handling and improves code reusability.


17-17: LGTM!

The refactor simplifies the notification callback by delegating release URL handling to the shared utility function, removing platform-specific branching.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/UpdateResult.ts (1)

11-12: LGTM!

The addition of the Disallowed result expands the update flow to handle cases where updates are not permitted, improving the completeness of the update system.

frontend/viewer/src/project/demo/in-memory-demo-api.ts (3)

35-36: LGTM!

The imports correctly reference the necessary types for implementing the mock UpdateService.


68-82: LGTM!

The mock UpdateService provides appropriate demo behavior:

  • Returns a simulated available update with auto-update support
  • Simulates an update process with a 2-second delay
  • Always succeeds, which is suitable for demo/testing purposes

104-104: LGTM!

The service registration follows the established pattern for demo services, enabling UpdateService functionality in demo mode.

frontend/viewer/src/lib/dotnet-types/generated-types/LexCore/Entities/IFwLiteRelease.ts (1)

1-11: LGTM!

The IFwLiteRelease interface provides a clean data model for release information, containing the essential version and URL properties needed for the update flow.

backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (3)

38-38: LGTM!

Adding UpdateService to the ExportedServices array correctly exposes it to the frontend via JS interop.


57-57: LGTM!

The type mapping correctly associates the UpdateService enum value with its concrete implementation type.


116-116: LGTM!

The UpdateService enum member completes the service registration, enabling type-safe service resolution.

backend/FwLite/FwLiteShared/Events/AppUpdateProgressEvent.cs (1)

5-11: LGTM!

Clean implementation using modern C# primary constructor pattern. The event correctly implements IFwEvent with immutable properties for progress reporting.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IUpdateService.ts (1)

9-13: LGTM!

The generated TypeScript interface correctly defines the update service API with appropriate Promise return types.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/AppUpdate/IAvailableUpdate.ts (1)

8-12: LGTM!

The generated interface correctly captures the available update information with release details and platform support flag.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/IAppUpdateProgressEvent.ts (1)

10-16: LGTM!

The generated interface correctly extends IFwEvent and includes all necessary properties for progress reporting.

frontend/viewer/src/lib/updates/UpdateDialog.svelte (4)

1-12: LGTM!

Imports are well-organized and appropriate for the update dialog functionality.


14-16: LGTM!

Proper use of Svelte 5 $bindable() rune and service provider hooks.


34-45: LGTM!

The error handling appropriately logs the error and rethrows it, allowing the UI to react to the error state through the rejected promise.


50-93: LGTM!

The template is well-structured with:

  • Proper accessibility attributes on external links (target="_blank" with rel="noopener noreferrer")
  • Platform info appropriately wrapped in <DevContent> as suggested in PR comments
  • Clean layout using Tailwind utilities
backend/FwLite/FwLiteShared/Services/UpdateService.cs (2)

7-14: LGTM!

Clean service class with proper JS interop attributes. The TsFunction type override correctly handles the nullable return type for TypeScript.


16-20: LGTM!

The method correctly extracts the release from the available update and delegates to the update checker.

frontend/viewer/src/stories/updates/update-dialog-content.stories.svelte (4)

1-13: LGTM!

Correct use of Svelte 5 module script syntax with proper Storybook meta definition.


14-34: LGTM!

Well-defined sample update objects covering both auto-update and manual download scenarios.


35-54: LGTM!

Helper functions provide clean promise mocks for testing various async states in stories.


56-179: LGTM!

Comprehensive story coverage of all update dialog states including:

  • Loading states (checking, installing)
  • Success paths (update available, up to date, install success)
  • Error paths (check error, install failed, disallowed)
  • Platform variations (auto-update vs manual download)

This provides excellent visual documentation and testing coverage for the component.

backend/FwLite/FwLiteShared/Events/AppUpdateEvent.cs (1)

6-9: Breaking change confirmed and properly implemented.

The constructor signature change requiring FwLiteRelease release parameter has been correctly applied. The only call site in UpdateChecker.cs has been updated to pass both parameters.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Events/index.ts (1)

1-10: LGTM!

Clean barrel file providing centralized access to all event-related types. The exports are well-organized and follow consistent naming conventions.

frontend/viewer/src/lib/updates/utils.ts (1)

1-29: LGTM!

Well-structured utility module with clear separation of concerns. The platform-specific URL mapping is extensible, and the fallback to release.url handles unsupported platforms gracefully.

frontend/viewer/src/locales/fr.po (1)

219-222: LGTM - New localization keys added correctly.

All new update-related translation keys are properly structured. The empty translations (msgstr "") are expected for new strings pending localization through Crowdin.

Also applies to: 417-420, 430-434, 480-483, 683-690, 783-786, 965-968, 999-1002, 1042-1045, 1358-1361, 1382-1401, 1402-1407, 1460-1463

frontend/viewer/src/lib/updates/UpdateDialogContent.svelte (1)

25-99: LGTM - Well-structured async UI flow.

The component correctly handles:

  • Loading state during update check
  • Success/failure states for both check and install operations
  • All UpdateResult enum values with appropriate icons and messages
  • Conditional button rendering based on supportsAutoUpdate

The nested {#await checkPromise} on line 81 is intentional and ensures the install/download buttons only appear after the check completes and no install is in progress.

backend/FwLite/FwLiteMaui/Platforms/Windows/AppUpdateService.cs (2)

12-12: LGTM - Clean event-driven progress reporting.

The GlobalEventBus integration is well-implemented:

  • Constructor injection follows DI best practices
  • NotifyInstallProgress correctly publishes AppUpdateProgressEvent which has IsGlobal = true
  • Progress updates flow through the event bus, enabling UI components to reflect real-time installation progress

Based on learnings, update checks occur once at startup, so this event pattern is appropriate for the single-notification-at-a-time workflow.

Also applies to: 148-151


117-126: Verify thread-safety of event publishing from progress callback.

The asyncOperation.Progress callback may be invoked on a background thread by the Windows PackageManager. While Subject<T>.OnNext from Reactive Extensions is generally thread-safe for concurrent calls, ensure that downstream subscribers (especially UI components) properly marshal events to the main thread if needed.

backend/FwLite/FwLiteShared/AppUpdate/UpdateChecker.cs (5)

11-11: Clean record design for encapsulating update availability.

The AvailableUpdate record nicely bundles the release information with the auto-update capability flag, providing a clear API contract for consumers.


21-22: Cache key and duration are appropriate for manual update checks.

The 2-minute cache prevents rapid repeated checks from the UI while still allowing reasonably fresh results. The constant string key works well since UpdateChecker is registered as a singleton.


37-48: Caching correctly preserves the LastUpdateCheck behavior.

The LastUpdateCheck is set inside the cache factory, meaning it only updates when an actual HTTP call is made (on cache miss). This aligns with the existing design that tracks when HTTP calls were attempted. Based on learnings, this is the intended behavior to enforce the 8-hour update check interval regardless of network issues.


50-66: ApplyUpdate flow handles permission and platform differences well.

The logic correctly:

  1. Prompts for permission on metered connections and returns Disallowed if denied
  2. Falls back to ManualUpdateRequired when auto-update isn't supported
  3. Always notifies with the result and release for UI consumption

This aligns with the PR objectives for handling platform differences.


29-35: TryUpdate refactor cleanly separates concerns.

The method now short-circuits appropriately when no update check is needed or no update is available, returning null to indicate no action was taken. This is a cleaner API than the previous approach.

Copilot AI and others added 26 commits December 5, 2025 11:17
Co-authored-by: myieye <12587509+myieye@users.noreply.github.com>
Co-authored-by: myieye <12587509+myieye@users.noreply.github.com>
- Removed complex event subscription logic
- Use direct API calls: checkForUpdates() returns IAvailableUpdate | null
- Use supportsAutoUpdate flag to show Install button vs download link
- Simplified state management and UI logic
- All functionality contained within dialog (no external notifications)

Co-authored-by: myieye <12587509+myieye@users.noreply.github.com>
Backend:
- Implement 5-minute memory cache for update checks
- Cache both "update available" and "no update" results

Frontend:
- Replace boolean flags (checking, installing, checkComplete, errorMessage) with promise-based state
- Use Svelte's {#await} blocks to handle async states declaratively
- Remove "Check Again" buttons - main button changes based on state
- Use Button's built-in loading and icon props (removed manual mr-2, i-mdi-loading)
- Remove unused context="module" script tag and cn import
- Fix platform display (removed .toString())

State reduced from 6 variables to 3:
- checkPromise: handles check states (pending/fulfilled/rejected)
- installPromise: handles install states
- installSuccess: tracks successful installation

Co-authored-by: myieye <12587509+myieye@users.noreply.github.com>
- Replace custom cache fields (_cachedUpdate, _cacheTime) with IMemoryCache
- Inject IMemoryCache via constructor dependency injection
- Use GetOrCreateAsync pattern for cleaner cache logic
- Add AddMemoryCache() to FwLiteSharedKernel service registration
- Maintain same 5-minute cache duration behavior

Co-authored-by: myieye <12587509+myieye@users.noreply.github.com>
It's confusing and there's a link to the app/downloads anyway
@myieye myieye force-pushed the copilot/add-update-check-ui branch from 13cb71b to 0ab90da Compare December 5, 2025 10:18
const autoUpdate: IAvailableUpdate = {
release: {
version: 'v1.2.3',
url: 'https://downloads.example.com/fw-lite/v1.2.3',
Copy link
Collaborator

Choose a reason for hiding this comment

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

❓ Any potential issue using a test url (in autoUpdate and manualUpdate) that points to a domain outside our control?

Copy link
Collaborator

Choose a reason for hiding this comment

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

When I run pnpm run i18n:extract in frontend/viewer/, it appears these locales/ files aren't up-to-date for this branch.

const reason = e.reason;
const msg = typeof reason === 'string' ? reason : (reason?.stack || JSON.stringify(reason));
const isIgnored = ignoredErrorPatterns.some(p => typeof p === 'string' ? msg.includes(p) : p.test(msg));
const isIgnored = e.reason instanceof DemoStoryError
Copy link
Collaborator

Choose a reason for hiding this comment

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

⛏️ Can use const isIgnored = reason [...] since const reason = e.reason two lines above.

// [vibe-coded]: fail tests on error logs and unhandled errors
beforeAll(() => {
console.error = (...args: any[]) => {
const message = args.map(a => (typeof a === 'string' ? a : JSON.stringify(a, null, 2))).join(' ');
Copy link
Collaborator

Choose a reason for hiding this comment

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

⛏️ message -> msg to match the other cases in this file.

});
asyncOperation.Progress = (info, progressInfo) =>
{
NotifyInstallProgress(progressInfo.percentage, latestRelease);
Copy link
Collaborator

Choose a reason for hiding this comment

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

❓ Is there a reason to notify before the progressInfo.state == DeploymentProgressState.Queued case is handled? I assume that makes 0% available to whatever receives this notification, whether or not it uses it.

const appVersion = config.appVersion;
</script>

{#if open}
Copy link
Collaborator

Choose a reason for hiding this comment

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

❓ Is there a reason to wrap this dialog with {#if open} (like what is done with the DeleteDialog and NewEntryDialog, but unlike all the others: AboutDialog, FeedbackDialog, GetProjectByCodeDialog, AudioDialog, EditEntryDialog, TroubleshootingDialog, ...)

variant="ghost"
size="xs-icon"
iconProps={{class: 'size-4'}}
title={$t`Copy version`}
Copy link
Collaborator

Choose a reason for hiding this comment

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

⛏️ Copy version -> Copy version and platform or Copy version info?


<div class="flex justify-center gap-3">
<a
class="inline-flex items-center flex-nowrap gap-1 text-sm font-medium underline underline-offset-4 hover:text-primary"
Copy link
Collaborator

Choose a reason for hiding this comment

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

🌱 It seems like this underline underline-offset-4 should be part of a global named or default style somewhere.

<ResponsiveDialog open title={$t`Updates`}>
<div class="flex flex-col gap-4">
<div>
<div>
Copy link
Collaborator

Choose a reason for hiding this comment

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

⛏️ The "Application version" and "Platform" lines are in <div>s here but in <p>s in TroubleshootDialog.

{/if}

{#if installPromise}
{#await installPromise}
Copy link
Collaborator

Choose a reason for hiding this comment

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

❓ Could the {#await installPromise} use a {:catch error}?

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 🟨Medium Medium-priority PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI to check for and trigger updates

3 participants