diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index cdd6e9a734..0000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,60 +0,0 @@ -# GitHub Copilot Instructions for languageforge-lexbox - -## Project Overview - -This is a monorepo containing: -- **LexBox** - A web application for managing linguistic data (backend in C#/.NET, frontend in Svelte) -- **FwLite** - A lightweight FieldWorks application (MAUI desktop app) -- **FwHeadless** - A headless service for FieldWorks data processing - -## Tech Stack - -- **Backend**: .NET 9, C#, Entity Framework Core, GraphQL (Hot Chocolate) -- **Frontend**: SvelteKit, TypeScript -- **Database**: PostgreSQL -- **Infrastructure**: Docker, Kubernetes, Skaffold, Tilt - -## Development Commands - -```bash -# Backend -dotnet build backend/LexBoxApi/LexBoxApi.csproj -dotnet test - -# FwLite (Windows) -dotnet build backend/FwLite/FwLiteMaui/FwLiteMaui.csproj --framework net9.0-windows10.0.19041.0 - -# Frontend -cd frontend && pnpm dev -``` - -## Project Structure - -```text -languageforge-lexbox/ -├── backend/ -│ ├── LexBoxApi/ # Main API (ASP.NET Core + GraphQL) -│ ├── LexCore/ # Core domain models -│ ├── LexData/ # Data access layer (EF Core) -│ ├── FwLite/ # FwLite MAUI app -│ ├── FwHeadless/ # Headless FW service -│ └── Testing/ # Test projects -├── frontend/ # SvelteKit web app -└── deployment/ # K8s/Docker configs -``` - -## Important Rules - -- ✅ Use GitHub Issues for task tracking -- ✅ Use `gh` CLI for GitHub issues/PRs, not browser tools -- ✅ Use **Mermaid diagrams** for flowcharts and architecture (not ASCII art) -- ❌ Do NOT use ASCII art for diagrams (use Mermaid instead) - ---- - -**For detailed workflows and advanced features, see [AGENTS.md](../AGENTS.md)** - -**For critical code areas:** -- [FwLite/CRDT Guide](../backend/FwLite/AGENTS.md) - Data sync, model changes (HIGH RISK) -- [FwHeadless Guide](../backend/FwHeadless/AGENTS.md) - Mercurial sync, FwData processing -- [CI/CD Guide](./AGENTS.md) - Workflows, deployments, K8s diff --git a/AGENTS.md b/AGENTS.md index a9f1ec5e7e..e075266427 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,28 +7,14 @@ This is a monorepo containing: - **FwLite** - A lightweight FieldWorks application (MAUI desktop app) - **FwHeadless** - A headless service for FieldWorks data processing -## Tech Stack +### Tech Stack - **Backend**: .NET 9, C#, Entity Framework Core, GraphQL (Hot Chocolate) - **Frontend**: SvelteKit, TypeScript - **Database**: PostgreSQL - **Infrastructure**: Docker, Kubernetes, Skaffold, Tilt -## Development Commands - -```bash -# Backend -dotnet build backend/LexBoxApi/LexBoxApi.csproj -dotnet test - -# FwLite (Windows) -dotnet build backend/FwLite/FwLiteMaui/FwLiteMaui.csproj --framework net9.0-windows10.0.19041.0 - -# Frontend -cd frontend && pnpm dev -``` - -## Project Structure +### Structure ```text languageforge-lexbox/ @@ -39,68 +25,67 @@ languageforge-lexbox/ │ ├── FwLite/ # FwLite MAUI app │ ├── FwHeadless/ # Headless FW service │ └── Testing/ # Test projects -├── frontend/ # SvelteKit web app +├── frontend/ # Lexbox SvelteKit web app +├── frontend/viewer/ # FieldWorks Lite frontend Svelte code └── deployment/ # K8s/Docker configs ``` -**IMPORTANT: Testing Policy** -- ❌ **Do NOT run integration tests** (`dotnet test`) unless the user explicitly asks -- Integration tests require full test infrastructure (database, services) and take significant time -- Only run unit tests locally when verifying critical business logic -- User must explicitly request test runs before executing them - -### Checking GitHub Issues and PRs - -When asked to check GitHub issues or PRs, use the `gh` CLI instead of browser tools: - -```bash -# List open issues -gh issue list --limit 30 - -# List open PRs -gh pr list --limit 30 - -# View specific issue or PR -gh issue view -gh pr view -``` - -- If the user asks about "the" PR, but does not explicitly name a PR or branch, assume they mean the PR associated with the current branch. - -Provide an in-conversation summary highlighting: -- Urgent/critical issues (regressions, bugs, broken builds) -- Common themes or patterns -- Items needing immediate attention - -**Why CLI over browser**: Faster, less tokens, easier to scan and discuss. - ### Important Files Key documentation for this project: - `README.md` - Project overview and setup - `AGENTS.md` - You are here! Agent instructions -- `.github/copilot-instructions.md` - GitHub Copilot auto-loaded instructions - `.github/AGENTS.md` - **CI/CD and deployment guide** (workflows, K8s, Docker) - `docs/DEVELOPER-win.md` - Windows development setup - `docs/DEVELOPER-linux.md` - Linux development setup - `docs/DEVELOPER-osx.md` - macOS development setup - `backend/README.md` - Backend architecture -- `backend/FwLite/AGENTS.md` - **FwLite/CRDT critical code guide** (data loss risks!) -- `backend/FwHeadless/AGENTS.md` - **FwHeadless sync guide** +- `backend/AGENTS.md` - General backend guidelines +- `backend/LexBoxApi/AGENTS.md` - API & GraphQL specific rules +- `backend/FwLite/AGENTS.md` - **FwLite/CRDT** (Critical code! Data loss risks!) +- `backend/FwHeadless/AGENTS.md` - **FwHeadless guide** (Critical code! Data loss risks! Mercurial sync, FwData processing) +- `frontend/AGENTS.md` - General frontend/SvelteKit rules +- `frontend/viewer/AGENTS.md` - **FwLite Viewer** (Specific frontend rules) - `deployment/README.md` - Deployment and infrastructure +## Guidelines + +### Testing + +- ❌ **Do NOT run dotnet INTEGRATION tests** unless the user explicitly asks. They require full test infrastructure (database, services) which usually isn't available. +- ✅ **DO run unit tests locally** and filter to the tests that are relevant to the changes you are making. Use IDE testing tools over the cli. + ### Questions? - Check existing issues: `gh issue list --limit 30` - Look at recent commits: `git log --oneline -20` - Read the docs in `docs/` directory - Create a GitHub issue if unsure +- Ask the user to clarify + +### Pre-Flight Check + +Before implementing any change that will touch many files or is in a 🔴 **Critical** area (FwLite sync, FwHeadless) do a "Pre-Flight Check" and list every component in the chain that will be touched (e.g., MiniLcm -> LcmCrdt -> FwDataBridge -> SyncHelper). ### Important Rules -- ✅ Use GitHub Issues for task tracking +- ✅ **ALWAYS read local `AGENTS.md` files** in the directories you are working in (and their parents) before starting. +- ✅ **ALWAYS review relevant code paths** before asking clarification questions. +- ✅ New instructions in AGENTS.md files should be SUCCINCT. - ✅ Use `gh` CLI for GitHub issues/PRs, not browser tools +- ✅ When pulling PR comments with `gh` use `api`. It's the only thing that returns review comments. +- ✅ If the user asks about "the" PR, but does not explicitly name a PR or branch, assume they mean the PR associated with the current branch. - ✅ Use **Mermaid diagrams** for flowcharts and architecture (not ASCII art) +- ✅ Prefer IDE diagnostics (compiler/lint errors) over CLI tools for identifying issues. Fixing these diagnostics is part of completing any instruction. - ✅ Do NOT run integration tests unless user explicitly requests -- ❌ Do NOT use ASCII art for diagrams (use Mermaid instead) +- ✅ When handling a user prompt ALWAYS ask for clarification if there are details to clarify, important decisions that must be made first or the plan sounds unwise - ❌ Do NOT git commit or git push without explicit user approval + +### 🛡️ VIGILANCE + +- ❌ **NEVER "fix" a failure** by removing assertions, commenting out code, or changing data to match a broken implementation. +- ✅ **ALWAYS fix the root cause** when a test or check fails. +- ✅ **ALWAYS double-check** that your "fix" hasn't made a check or test meaningless (e.g., asserting `expect(true).toBe(true)`). +- ✅ **Assert that E2E test user actions** e.g. (scroll, click, etc.) actually have the expected effect before proceeding further. + +If you are struggling, explain the difficulty to the user instead of cheating. **Integrity is non-negotiable.** diff --git a/backend/FwLite/AGENTS.md b/backend/FwLite/AGENTS.md index 30b10390d9..224c1ea2ff 100644 --- a/backend/FwLite/AGENTS.md +++ b/backend/FwLite/AGENTS.md @@ -30,6 +30,17 @@ dotnet test FwLiteOnly.slnf dotnet build FwLiteMaui/FwLiteMaui.csproj --framework net9.0-windows10.0.19041.0 ``` +## Generated Types (TypeScript) + +The frontend viewer uses TypeScript types and API interfaces generated from .NET using **Reinforced.Typings**. These are automatically updated when you build the **FwLiteShared** project (or any project that depends on it like `FwLiteMaui` or `FwLiteWeb`). + +```bash +# To manually update generated types: +dotnet build backend/FwLite/FwLiteShared/FwLiteShared.csproj +``` + +The configuration for this lives in `FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs` and `FwLiteShared/Reinforced.Typings.settings.xml`. + ## Project Structure | Directory | Priority | Purpose | diff --git a/backend/FwLite/FwDataMiniLcmBridge.Tests/MiniLcmTests/EntryIndexTests.cs b/backend/FwLite/FwDataMiniLcmBridge.Tests/MiniLcmTests/EntryIndexTests.cs new file mode 100644 index 0000000000..dbfea0dd20 --- /dev/null +++ b/backend/FwLite/FwDataMiniLcmBridge.Tests/MiniLcmTests/EntryIndexTests.cs @@ -0,0 +1,12 @@ +using FwDataMiniLcmBridge.Tests.Fixtures; + +namespace FwDataMiniLcmBridge.Tests.MiniLcmTests; + +[Collection(ProjectLoaderFixture.Name)] +public class EntryIndexTests(ProjectLoaderFixture fixture) : EntryIndexTestsBase +{ + protected override Task NewApi() + { + return Task.FromResult(fixture.NewProjectApi("entry-index-test", "en", "en")); + } +} diff --git a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs index c52fba6a0f..7d99e43470 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/Api/FwDataMiniLcmApi.cs @@ -916,23 +916,27 @@ public IAsyncEnumerable GetEntries( Func? predicate, QueryOptions? options = null, string? query = null) { options ??= QueryOptions.Default; - var entries = GetLexEntries(predicate, options); - - entries = ApplySorting(options, entries, query); + var entries = GetFilteredAndSortedEntries(predicate, options, options.Order, query); entries = options.ApplyPaging(entries); return entries.ToAsyncEnumerable().Select(FromLexEntry); } - private IEnumerable ApplySorting(QueryOptions options, IEnumerable entries, string? query) + private IEnumerable GetFilteredAndSortedEntries(Func? predicate, FilterQueryOptions? filterOptions, SortOptions order, string? query) + { + var entries = GetLexEntries(predicate, filterOptions); + return ApplySorting(order, entries, query); + } + + private IEnumerable ApplySorting(SortOptions order, IEnumerable entries, string? query) { - var sortWs = GetWritingSystemHandle(options.Order.WritingSystem, WritingSystemType.Vernacular); - if (options.Order.Field == SortField.SearchRelevance) + var sortWs = GetWritingSystemHandle(order.WritingSystem, WritingSystemType.Vernacular); + if (order.Field == SortField.SearchRelevance) { - return entries.ApplyRoughBestMatchOrder(options.Order, sortWs, query); + return entries.ApplyRoughBestMatchOrder(order, sortWs, query); } - return options.ApplyOrder(entries, e => e.LexEntryHeadword(sortWs)); + return order.ApplyOrder(entries, e => e.LexEntryHeadword(sortWs)); } public IAsyncEnumerable SearchEntries(string query, QueryOptions? options = null) @@ -955,6 +959,24 @@ public IAsyncEnumerable SearchEntries(string query, QueryOptions? options return Task.FromResult(lexEntry is null ? null : FromLexEntry(lexEntry)); } + public Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null) + { + var predicate = EntrySearchPredicate(query); + var entries = GetFilteredAndSortedEntries(predicate, options, options?.Order ?? SortOptions.Default, query); + + var rowIndex = 0; + foreach (var entry in entries) + { + if (entry.Guid == entryId) + { + return Task.FromResult(rowIndex); + } + rowIndex++; + } + + return Task.FromResult(-1); + } + public async Task CreateEntry(Entry entry, CreateEntryOptions? options = null) { options ??= CreateEntryOptions.Everything; diff --git a/backend/FwLite/FwDataMiniLcmBridge/LexEntryFilterMapProvider.cs b/backend/FwLite/FwDataMiniLcmBridge/LexEntryFilterMapProvider.cs index 019208f154..8abd0d754a 100644 --- a/backend/FwLite/FwDataMiniLcmBridge/LexEntryFilterMapProvider.cs +++ b/backend/FwLite/FwDataMiniLcmBridge/LexEntryFilterMapProvider.cs @@ -32,6 +32,7 @@ public class LexEntryFilterMapProvider : EntryFilterMapProvider public override Expression> EntryCitationForm => (entry, ws) => entry.PickText(entry.CitationForm, ws); public override Expression> EntryLiteralMeaning => (entry, ws) => entry.PickText(entry.LiteralMeaning, ws); + public override Expression> EntryMorphType => e => LcmHelpers.FromLcmMorphType(e.PrimaryMorphType); public override Expression> EntryComplexFormTypes => e => EmptyToNull(e.ComplexFormEntryRefs.SelectMany(r => r.ComplexEntryTypesRS)); public override Func? EntryComplexFormTypesConverter => EntryFilter.NormalizeEmptyToNull; } diff --git a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs index 3daf61bddd..48a6967d98 100644 --- a/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs +++ b/backend/FwLite/FwLiteShared/Services/MiniLcmJsInvokable.cs @@ -95,6 +95,12 @@ public Task CountEntries(string? query, FilterQueryOptions? options) return Task.Run(() => _wrappedApi.CountEntries(query, options)); } + [JSInvokable] + public Task GetEntryIndex(Guid id, string? query, IndexQueryOptions? options) + { + return Task.Run(() => _wrappedApi.GetEntryIndex(id, query, options)); + } + [JSInvokable] public Task GetEntries(QueryOptions? options = null) { diff --git a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs index 960ef578fe..a76647607c 100644 --- a/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs +++ b/backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs @@ -119,6 +119,7 @@ private static void ConfigureMiniLcmTypes(ConfigurationBuilder builder) builder.ExportAsInterfaces([ typeof(QueryOptions), typeof(FilterQueryOptions), + typeof(IndexQueryOptions), typeof(SortOptions), typeof(ExemplarOptions), typeof(EntryFilter), diff --git a/backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs b/backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs index 7f6fd036af..272bd07908 100644 --- a/backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs +++ b/backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs @@ -99,6 +99,7 @@ await projectProvider.OpenProject(project, context.HttpContext.RequestServices) api.MapGet("/entries", MiniLcm.GetEntries); api.MapGet("/entries/{search}", MiniLcm.SearchEntries); api.MapGet("/entry/{id:Guid}", MiniLcm.GetEntry); + api.MapGet("/entry/{id:Guid}/index", MiniLcm.GetEntryIndex); api.MapGet("/parts-of-speech", MiniLcm.GetPartsOfSpeech); api.MapGet("/semantic-domains", MiniLcm.GetSemanticDomains); api.MapGet("/publications", MiniLcm.GetPublications); @@ -137,6 +138,15 @@ public static IAsyncEnumerable SearchEntries([FromServices] MiniLcmHolder return api.GetEntry(id); } + public static Task GetEntryIndex( + Guid id, + [AsParameters] MiniLcmQueryOptions options, + [FromServices] MiniLcmHolder holder) + { + var api = holder.MiniLcmApi; + return api.GetEntryIndex(id, null, options.ToIndexQueryOptions()); + } + public static IAsyncEnumerable GetPartsOfSpeech([FromServices] MiniLcmHolder holder) { var api = holder.MiniLcmApi; @@ -185,6 +195,19 @@ public QueryOptions ToQueryOptions() string.IsNullOrEmpty(GridifyFilter) ? null : new EntryFilter {GridifyFilter = GridifyFilter}); } + public IndexQueryOptions ToIndexQueryOptions() + { + ExemplarOptions? exemplarOptions = string.IsNullOrEmpty(ExemplarValue) || ExemplarWritingSystem is null + ? null + : new(ExemplarValue, ExemplarWritingSystem); + var sortField = SortField ?? SortOptions.Default.Field; + return new IndexQueryOptions(new SortOptions(sortField, + SortWritingSystem ?? SortOptions.Default.WritingSystem, + Ascending ?? SortOptions.Default.Ascending), + exemplarOptions, + string.IsNullOrEmpty(GridifyFilter) ? null : new EntryFilter {GridifyFilter = GridifyFilter}); + } + public SortField? SortField { get; set; } = SortOptions.Default.Field; [DefaultValue(SortOptions.DefaultWritingSystem)] diff --git a/backend/FwLite/LcmCrdt.Tests/MiniLcmTests/EntryIndexTests.cs b/backend/FwLite/LcmCrdt.Tests/MiniLcmTests/EntryIndexTests.cs new file mode 100644 index 0000000000..d7badf3995 --- /dev/null +++ b/backend/FwLite/LcmCrdt.Tests/MiniLcmTests/EntryIndexTests.cs @@ -0,0 +1,26 @@ +using Xunit.Abstractions; + +namespace LcmCrdt.Tests.MiniLcmTests; + +public class EntryIndexTests(ITestOutputHelper output) : EntryIndexTestsBase +{ + private readonly MiniLcmApiFixture _fixture = new(); + + public override async Task InitializeAsync() + { + _fixture.LogTo(output); + await _fixture.InitializeAsync(); + await base.InitializeAsync(); + } + + protected override Task NewApi() + { + return Task.FromResult(_fixture.Api); + } + + public override async Task DisposeAsync() + { + await base.DisposeAsync(); + await _fixture.DisposeAsync(); + } +} diff --git a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs index cfeea8fc98..2c80826f03 100644 --- a/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs +++ b/backend/FwLite/LcmCrdt/CrdtMiniLcmApi.cs @@ -405,6 +405,12 @@ public async IAsyncEnumerable SearchEntries(string? query, QueryOptions? return await repo.GetEntry(id); } + public async Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null) + { + await using var repo = await repoFactory.CreateRepoAsync(); + return await repo.GetEntryIndex(entryId, query, options); + } + public async Task BulkCreateEntries(IAsyncEnumerable entries) { await using var repo = await repoFactory.CreateRepoAsync(); diff --git a/backend/FwLite/LcmCrdt/Data/CustomSqliteFunctionInterceptor.cs b/backend/FwLite/LcmCrdt/Data/CustomSqliteFunctionInterceptor.cs index 0f9528f914..661cb588e3 100644 --- a/backend/FwLite/LcmCrdt/Data/CustomSqliteFunctionInterceptor.cs +++ b/backend/FwLite/LcmCrdt/Data/CustomSqliteFunctionInterceptor.cs @@ -1,20 +1,60 @@ using System.Data.Common; using System.Globalization; -using System.Runtime.CompilerServices; using System.Text; +using LinqToDB.Interceptors; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore.Diagnostics; -using MiniLcm.Culture; namespace LcmCrdt.Data; -public class CustomSqliteFunctionInterceptor : IDbConnectionInterceptor +public class CustomSqliteFunctionInterceptor : IDbConnectionInterceptor, IConnectionInterceptor { public const string ContainsFunction = "contains"; public void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) { - var sqliteConnection = (SqliteConnection)connection; + ConnectionOpened(connection); + } + + public Task ConnectionOpenedAsync(DbConnection connection, + ConnectionEndEventData eventData, + CancellationToken cancellationToken = default) + { + ConnectionOpened(connection); + return Task.CompletedTask; + } + + public void ConnectionOpening(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection) + { + // We register the function after connection opens, not before + } + + public Task ConnectionOpeningAsync(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public void ConnectionOpened(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection) + { + ConnectionOpened(connection); + } + + public Task ConnectionOpenedAsync(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection, CancellationToken cancellationToken) + { + ConnectionOpened(connection); + return Task.CompletedTask; + } + + private void ConnectionOpened(DbConnection connection) + { + if (connection is SqliteConnection sqliteConnection) + { + RegisterContainsFunction(sqliteConnection); + } + } + + public static void RegisterContainsFunction(SqliteConnection sqliteConnection) + { //creates a new function that can be used in queries sqliteConnection.CreateFunction(ContainsFunction, //in sqlite strings are byte arrays, so we can avoid allocating strings by using spans @@ -50,12 +90,4 @@ private static bool ContainsDiacritic(in ReadOnlySpan value) } return hasAccent; } - - public Task ConnectionOpenedAsync(DbConnection connection, - ConnectionEndEventData eventData, - CancellationToken cancellationToken = new CancellationToken()) - { - ConnectionOpened(connection, eventData); - return Task.CompletedTask; - } } diff --git a/backend/FwLite/LcmCrdt/Data/MiniLcmRepository.cs b/backend/FwLite/LcmCrdt/Data/MiniLcmRepository.cs index 20233e1038..1e5184396e 100644 --- a/backend/FwLite/LcmCrdt/Data/MiniLcmRepository.cs +++ b/backend/FwLite/LcmCrdt/Data/MiniLcmRepository.cs @@ -4,6 +4,7 @@ using LcmCrdt.FullTextSearch; using LcmCrdt.Utils; using LinqToDB; +using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -61,7 +62,7 @@ public void Dispose() } - public IQueryable Entries => dbContext.Entries; + public IQueryable Entries => dbContext.Entries.ToLinqToDB(); public IQueryable ComplexFormComponents => dbContext.ComplexFormComponents; public IQueryable ComplexFormTypes => dbContext.ComplexFormTypes; public IQueryable Senses => dbContext.Senses; @@ -128,12 +129,8 @@ public async IAsyncEnumerable GetEntries( string? query = null, QueryOptions? options = null) { - options = await EnsureWritingSystemIsPopulated(options ??= QueryOptions.Default); - - var queryable = Entries; - (queryable, var sortingHandled) = await FilterEntries(queryable, query, options, options.Order); - if (!sortingHandled) - queryable = await ApplySorting(queryable, options, query); + IQueryable queryable; + (queryable, options) = await FilterAndSortEntries(query, options ?? QueryOptions.Default); queryable = queryable .LoadWith(e => e.Senses) @@ -155,6 +152,20 @@ public async IAsyncEnumerable GetEntries( } } + private async Task<(IQueryable queryable, QueryOptions options)> FilterAndSortEntries( + string? query, + QueryOptions options) + { + options = await EnsureWritingSystemIsPopulated(options); + + var queryable = Entries; + var (filteredQuery, sortingHandled) = await FilterEntries(queryable, query, options, options.Order); + if (!sortingHandled) + filteredQuery = await ApplySorting(filteredQuery, options, query); + + return (filteredQuery, options); + } + private async Task EnsureWritingSystemIsPopulated(QueryOptions queryOptions) { if (queryOptions.Order.WritingSystem != default) return queryOptions; @@ -208,22 +219,20 @@ private async Task EnsureWritingSystemIsPopulated(QueryOptions que return (queryable, sortingHandled); } - private async ValueTask> ApplySorting(IQueryable queryable, QueryOptions options, string? query = null) + private ValueTask> ApplySorting(IQueryable queryable, QueryOptions options, string? query = null) { if (options.Order.WritingSystem == default) throw new ArgumentException("Sorting writing system must be specified", nameof(options)); var wsId = options.Order.WritingSystem; - switch (options.Order.Field) + IQueryable result = options.Order.Field switch { - case SortField.SearchRelevance: - return queryable.ApplyRoughBestMatchOrder(options.Order, query); - case SortField.Headword: - var ordered = options.ApplyOrder(queryable, e => e.Headword(wsId).CollateUnicode(wsId)); - return ordered.ThenBy(e => e.Id); - default: - throw new ArgumentOutOfRangeException(nameof(options), "sort field unknown " + options.Order.Field); - } + SortField.SearchRelevance => queryable.ApplyRoughBestMatchOrder(options.Order, query), + SortField.Headword => + options.ApplyOrder(queryable, e => e.Headword(wsId).CollateUnicode(wsId)).ThenBy(e => e.Id), + _ => throw new ArgumentOutOfRangeException(nameof(options), "sort field unknown " + options.Order.Field) + }; + return new ValueTask>(result); } public async Task GetEntry(Guid id) @@ -262,6 +271,25 @@ private async ValueTask> ApplySorting(IQueryable querya return exampleSentence; } + public async Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null) + { + var queryOptions = new QueryOptions( + options?.Order ?? QueryOptions.Default.Order, + options?.Exemplar, + QueryOptions.QueryAll, + 0, + options?.Filter + ); + + IQueryable queryable; + (queryable, queryOptions) = await FilterAndSortEntries(query, queryOptions); + + // SQLite's ROW_NUMBER() cannot easily inherit the existing ORDER BY from the query. (AI tried a billion things) + // This is pretty efficient since we only select IDs, not full entities. + var sortedIds = await queryable.Select(e => e.Id).ToListAsyncEF(); + return sortedIds.IndexOf(entryId); + } + public async Task GetPublication(Guid publicationId) { var publication = await AsyncExtensions.SingleOrDefaultAsync(Publications diff --git a/backend/FwLite/LcmCrdt/Data/SetupCollationInterceptor.cs b/backend/FwLite/LcmCrdt/Data/SetupCollationInterceptor.cs index fc732bdd50..de1a1c856e 100644 --- a/backend/FwLite/LcmCrdt/Data/SetupCollationInterceptor.cs +++ b/backend/FwLite/LcmCrdt/Data/SetupCollationInterceptor.cs @@ -2,18 +2,21 @@ using System.Data.Common; using System.Globalization; using System.Text; +using LinqToDB.Interceptors; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using MiniLcm.Culture; +using SIL.Harmony; namespace LcmCrdt.Data; -public class SetupCollationInterceptor(IMemoryCache cache, IMiniLcmCultureProvider cultureProvider) : IDbConnectionInterceptor, ISaveChangesInterceptor +public class SetupCollationInterceptor(IMemoryCache cache, IMiniLcmCultureProvider cultureProvider, IOptions crdtConfig) : IDbConnectionInterceptor, ISaveChangesInterceptor, IConnectionInterceptor { private static string? WsTableName = null; - private WritingSystem[] GetWritingSystems(LcmCrdtDbContext dbContext, DbConnection connection) + private WritingSystem[] GetWritingSystems(DbConnection connection, LcmCrdtDbContext? dbContext = null) { return cache.GetOrCreate(CacheKey(connection), entry => @@ -21,13 +24,31 @@ private WritingSystem[] GetWritingSystems(LcmCrdtDbContext dbContext, DbConnecti entry.SlidingExpiration = TimeSpan.FromMinutes(30); try { - WsTableName ??= dbContext.Model.FindRuntimeEntityType(typeof(WritingSystem))?.GetTableName() ?? "WritingSystem"; - if (!HasTable(dbContext, WsTableName)) + var localContext = dbContext; + if (localContext is null) { - return []; + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite(connection); + localContext = new LcmCrdtDbContext(optionsBuilder.Options, crdtConfig); } - return dbContext.WritingSystems.ToArray(); + try + { + WsTableName ??= localContext.Model.FindRuntimeEntityType(typeof(WritingSystem))?.GetTableName() ?? "WritingSystem"; + if (!HasTable(localContext, WsTableName)) + { + return []; + } + + return localContext.WritingSystems.ToArray(); + } + finally + { + if (dbContext is null) + { + localContext.Dispose(); + } + } } catch (SqliteException) { @@ -53,14 +74,9 @@ private void InvalidateWritingSystemsCache(DbConnection connection) cache.Remove(CacheKey(connection)); } - public void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) + private void SetupCommonCollations(SqliteConnection sqliteConnection, WritingSystem[]? writingSystems = null) { - var context = (LcmCrdtDbContext?)eventData.Context; - if (context is null) throw new InvalidOperationException("context is null"); - var sqliteConnection = (SqliteConnection)connection; - SetupCollations(sqliteConnection, GetWritingSystems(context, connection)); - - //setup general use collation + // Setup general use collation sqliteConnection.CreateCollation(SqlSortingExtensions.CollateUnicodeNoCase, CultureInfo.CurrentCulture.CompareInfo, (compareInfo, x, y) => @@ -71,6 +87,20 @@ public void ConnectionOpened(DbConnection connection, ConnectionEndEventData eve // When case-insensitively equal, sort lowercase before uppercase return compareInfo.Compare(x, y, CompareOptions.None); }); + + // Setup writing system specific collations if available + if (writingSystems is not null) + { + SetupCollations(sqliteConnection, writingSystems); + } + } + + public void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData) + { + var context = (LcmCrdtDbContext?)eventData.Context; + if (context is null) throw new InvalidOperationException("context is null"); + var sqliteConnection = (SqliteConnection)connection; + SetupCommonCollations(sqliteConnection, GetWritingSystems(connection, context)); } public Task ConnectionOpenedAsync(DbConnection connection, @@ -81,6 +111,33 @@ public Task ConnectionOpenedAsync(DbConnection connection, return Task.CompletedTask; } + // LinqToDB interface + public void ConnectionOpening(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection) + { + // Setup happens after connection opens + } + + public Task ConnectionOpeningAsync(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public void ConnectionOpened(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection) + { + if (connection is not SqliteConnection sqliteConnection) return; + + // Only setup basic collation - writing system collations come from EF Core path + // Note: Collations persist on the connection, so if EF already opened this connection, + // this is redundant but harmless. SQLite allows re-registering collations. + SetupCommonCollations(sqliteConnection, GetWritingSystems(connection)); + } + + public Task ConnectionOpenedAsync(LinqToDB.Interceptors.ConnectionEventData eventData, DbConnection connection, CancellationToken cancellationToken) + { + ConnectionOpened(eventData, connection); + return Task.CompletedTask; + } + public InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) { UpdateCollationsOnSave(eventData.Context); diff --git a/backend/FwLite/LcmCrdt/EntryFilterMapProvider.cs b/backend/FwLite/LcmCrdt/EntryFilterMapProvider.cs index 5aa3a06b3f..a62d909c9c 100644 --- a/backend/FwLite/LcmCrdt/EntryFilterMapProvider.cs +++ b/backend/FwLite/LcmCrdt/EntryFilterMapProvider.cs @@ -27,6 +27,7 @@ public class EntryFilterMapProvider : EntryFilterMapProvider public override Expression> EntryLexemeForm => (entry, ws) => Json.Value(entry.LexemeForm, ms => ms[ws])!; public override Expression> EntryCitationForm => (entry, ws) => Json.Value(entry.CitationForm, ms => ms[ws])!; public override Expression> EntryLiteralMeaning => (entry, ws) => Json.Value(entry.LiteralMeaning, ms => ms[ws])!.GetPlainText(); + public override Expression> EntryMorphType => e => e.MorphType; public override Expression> EntryComplexFormTypes => e => e.ComplexFormTypes; public override Func? EntryComplexFormTypesConverter => EntryFilter.NormalizeEmptyToEmptyList; } diff --git a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs index 043489a6ec..b5ddfb578b 100644 --- a/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs +++ b/backend/FwLite/LcmCrdt/LcmCrdtKernel.cs @@ -121,12 +121,22 @@ public static void ConfigureDbOptions(IServiceProvider provider, DbContextOption new DataParameter { Value = id.Code, DataType = DataType.Text }); optionsBuilder.AddMappingSchema(mappingSchema); optionsBuilder.AddCustomOptions(options => options.UseSQLiteMicrosoft()); + + // Register read-relevant interceptors for LinqToDB + var sqliteFunctionInterceptor = new CustomSqliteFunctionInterceptor(); + var collationInterceptor = provider.GetRequiredService(); + optionsBuilder.AddInterceptor(sqliteFunctionInterceptor); + optionsBuilder.AddInterceptor(collationInterceptor); + var loggerFactory = provider.GetService(); if (loggerFactory is not null) optionsBuilder.AddCustomOptions(dataOptions => dataOptions.UseLoggerFactory(loggerFactory)); }); + // Register interceptors for EF Core builder.AddInterceptors(new CustomSqliteFunctionInterceptor(), provider.GetRequiredService()); + + // UpdateEntrySearchTableInterceptor is write-only (updates FTS table), so only register with EF Core var updateSearchTableInterceptor = provider.GetService(); if (updateSearchTableInterceptor is not null) builder.AddInterceptors(updateSearchTableInterceptor); diff --git a/backend/FwLite/MiniLcm.Tests/EntryIndexTestsBase.cs b/backend/FwLite/MiniLcm.Tests/EntryIndexTestsBase.cs new file mode 100644 index 0000000000..e994947c65 --- /dev/null +++ b/backend/FwLite/MiniLcm.Tests/EntryIndexTestsBase.cs @@ -0,0 +1,169 @@ +using MiniLcm.Filtering; + +namespace MiniLcm.Tests; + +/// +/// Tests for GetEntryIndex API. +/// This API is critical for finding the position of an entry in a sorted/filtered list for virtual scrolling. +/// +public abstract class EntryIndexTestsBase : MiniLcmTestBase +{ + private readonly Guid appleId = Guid.NewGuid(); + private readonly Guid bananaId = Guid.NewGuid(); + private readonly Guid kiwiId = Guid.NewGuid(); + private readonly Guid orangeId = Guid.NewGuid(); + private readonly Guid peachId = Guid.NewGuid(); + + private const string Apple = "Apple"; + private const string Banana = "Banana"; + private const string Kiwi = "Kiwi"; + private const string Orange = "Orange"; + private const string Peach = "Peach"; + + public override async Task InitializeAsync() + { + await base.InitializeAsync(); + await Api.CreateEntry(new Entry { Id = appleId, LexemeForm = { { "en", Apple } } }); + await Api.CreateEntry(new Entry { Id = bananaId, LexemeForm = { { "en", Banana } } }); + await Api.CreateEntry(new Entry { Id = kiwiId, LexemeForm = { { "en", Kiwi } } }); + await Api.CreateEntry(new Entry { Id = orangeId, LexemeForm = { { "en", Orange } } }); + await Api.CreateEntry(new Entry { Id = peachId, LexemeForm = { { "en", Peach } } }); + } + + [Fact] + public async Task GetEntryIndex_FirstEntry_ReturnsZero() + { + var result = await Api.GetEntryIndex(appleId); + + result.Should().Be(0); + } + + [Fact] + public async Task GetEntryIndex_MiddleEntry_ReturnsCorrectIndex() + { + var result = await Api.GetEntryIndex(kiwiId); + + result.Should().Be(2); + } + + [Fact] + public async Task GetEntryIndex_LastEntry_ReturnsLastIndex() + { + var result = await Api.GetEntryIndex(peachId); + + result.Should().Be(4); + } + + [Fact] + public async Task GetEntryIndex_NonExistentEntry_ReturnsNegativeOne() + { + var nonExistentId = Guid.NewGuid(); + + var result = await Api.GetEntryIndex(nonExistentId); + + result.Should().Be(-1); + } + + [Fact] + public async Task GetEntryIndex_WithCustomSort_ReturnsCorrectIndex() + { + // Sort descending + var options = new IndexQueryOptions(new SortOptions(SortField.Headword, "en", false)); + + // Order: Peach, Orange, Kiwi, Banana, Apple + (await Api.GetEntryIndex(peachId, null, options)).Should().Be(0); + (await Api.GetEntryIndex(orangeId, null, options)).Should().Be(1); + (await Api.GetEntryIndex(kiwiId, null, options)).Should().Be(2); + (await Api.GetEntryIndex(bananaId, null, options)).Should().Be(3); + (await Api.GetEntryIndex(appleId, null, options)).Should().Be(4); + } + + [Fact] + public async Task GetEntryIndex_LargeList_ReturnsCorrectIndex() + { + var entryIds = new List(); + for (var i = 0; i < 1005; i++) + { + var id = Guid.NewGuid(); + entryIds.Add(id); + // Ensure deterministic sort (e.g., 0000 Entry, 0001 Entry, ...) + // We use a prefix to ensure these come before the standard entries (Apple, Banana, etc) + await Api.CreateEntry(new Entry { Id = id, LexemeForm = { { "en", $"{i:D4} Entry" } } }); + } + + (await Api.GetEntryIndex(entryIds[0])).Should().Be(0); + (await Api.GetEntryIndex(entryIds[500])).Should().Be(500); + (await Api.GetEntryIndex(entryIds[1000])).Should().Be(1000); + (await Api.GetEntryIndex(entryIds[1004])).Should().Be(1004); + } + + [Fact] + public async Task GetEntryIndex_WithShortQuery_ReturnsCorrectIndex() + { + // Query "Ki" is < 3 characters, should use simple search filter + var result = await Api.GetEntryIndex(kiwiId, "Ki"); + + // "Kiwi" should be the only match, index 0 + result.Should().Be(0); + } + + [Fact] + public async Task GetEntryIndex_WithLongQuery_ReturnsCorrectIndex() + { + // Query "Orange" is >= 3 characters, should use FTS + var result = await Api.GetEntryIndex(orangeId, "Orange"); + + // "Orange" should be found, index 0 + result.Should().Be(0); + } + + [Fact] + public async Task GetEntryIndex_WithGridifyFilter_ReturnsCorrectIndex() + { + // Use Gridify filter to find "banana" + var options = new IndexQueryOptions(Filter: new EntryFilter { GridifyFilter = "LexemeForm[en]=Banana" }); + + var result = await Api.GetEntryIndex(bananaId, null, options); + + result.Should().Be(0); + } + + [Fact] + public async Task GetEntryIndex_WithFilterAndSort_ReturnsCorrectIndex() + { + var options = new IndexQueryOptions(new SortOptions(SortField.SearchRelevance, "en", true)); + + (await Api.GetEntryIndex(appleId, "a", options)).Should().Be(0); + (await Api.GetEntryIndex(peachId, "a", options)).Should().Be(1); + (await Api.GetEntryIndex(bananaId, "a", options)).Should().Be(2); + (await Api.GetEntryIndex(orangeId, "a", options)).Should().Be(3); + + // "Kiwi" shouldn't be in the results at all + (await Api.GetEntryIndex(kiwiId, "a", options)).Should().Be(-1); + } + + [Fact] + public async Task GetEntryIndex_WithFTSFilterAndSort_ReturnsCorrectIndex() + { + // Add some more entries to make search more interesting + var applePieId = Guid.NewGuid(); + var pineappleId = Guid.NewGuid(); + await Api.CreateEntry(new Entry { Id = applePieId, LexemeForm = { { "en", "Apple Pie" } } }); + await Api.CreateEntry(new Entry { Id = pineappleId, LexemeForm = { { "en", "Pineapple" } } }); + + // Query "Apple" (>= 3 chars, uses SearchService/FTS) + // Matches: Apple (len 5), Apple Pie (len 9), Pineapple (len 9) + // Sorting: + // 1. Headword contains "Apple": All match. + // 2. Length: Apple (5), Apple Pie (9), Pineapple (9) + // 3. Alphabetical (for 9-len): Apple Pie, Pineapple + var options = new IndexQueryOptions(new SortOptions(SortField.SearchRelevance, "en", true)); + + (await Api.GetEntryIndex(appleId, "Apple", options)).Should().Be(0); + (await Api.GetEntryIndex(applePieId, "Apple", options)).Should().Be(1); + (await Api.GetEntryIndex(pineappleId, "Apple", options)).Should().Be(2); + + // "Banana" shouldn't be in the results + (await Api.GetEntryIndex(bananaId, "Apple", options)).Should().Be(-1); + } +} diff --git a/backend/FwLite/MiniLcm.Tests/QueryEntryTestsBase.cs b/backend/FwLite/MiniLcm.Tests/QueryEntryTestsBase.cs index 117ab99970..4069a93a7c 100644 --- a/backend/FwLite/MiniLcm.Tests/QueryEntryTestsBase.cs +++ b/backend/FwLite/MiniLcm.Tests/QueryEntryTestsBase.cs @@ -27,7 +27,8 @@ public override async Task InitializeAsync() await Api.CreateEntry(new Entry() { Id = appleId, - LexemeForm = { { "en", Apple } } + LexemeForm = { { "en", Apple } }, + MorphType = MorphType.Root, }); await Api.CreateEntry(new Entry() { @@ -226,6 +227,20 @@ public async Task CanFilterToMissingComplexFormTypesWithEmptyArray() results.Select(e => e.LexemeForm["en"]).Distinct().Should().BeEquivalentTo(Apple, Banana, Kiwi, Null_LexemeForm); } + [Fact] + public async Task CanFilterByMorphTypeSingleType() + { + var results = await Api.GetEntries(new(Filter: new() { GridifyFilter = "MorphType=Root" })).ToArrayAsync(); + results.Select(e => e.LexemeForm["en"]).Should().BeEquivalentTo(Apple); + } + + [Fact] + public async Task CanFilterByMorphTypeTwoTypes() + { + var results = await Api.GetEntries(new(Filter: new() { GridifyFilter = "MorphType=Root|MorphType=Stem" })).ToArrayAsync(); + results.Select(e => e.LexemeForm["en"]).Should().BeEquivalentTo(Apple, Peach, Banana, Kiwi, Null_LexemeForm); + } + [Fact] public async Task CanFilterLexemeForm() { diff --git a/backend/FwLite/MiniLcm/Filtering/EntryFilter.cs b/backend/FwLite/MiniLcm/Filtering/EntryFilter.cs index f1f332961b..a1688e3911 100644 --- a/backend/FwLite/MiniLcm/Filtering/EntryFilter.cs +++ b/backend/FwLite/MiniLcm/Filtering/EntryFilter.cs @@ -26,6 +26,7 @@ public static GridifyMapper NewMapper(EntryFilterMapProvider provider) mapper.AddMap(nameof(Entry.LexemeForm), provider.EntryLexemeForm!); mapper.AddMap(nameof(Entry.CitationForm), provider.EntryCitationForm!); mapper.AddMap(nameof(Entry.LiteralMeaning), provider.EntryLiteralMeaning!); + mapper.AddMap(nameof(Entry.MorphType), provider.EntryMorphType); mapper.AddMap(nameof(Entry.ComplexFormTypes), provider.EntryComplexFormTypes, provider.EntryComplexFormTypesConverter); return mapper; } diff --git a/backend/FwLite/MiniLcm/Filtering/EntryFilterMapProvider.cs b/backend/FwLite/MiniLcm/Filtering/EntryFilterMapProvider.cs index 00be759cde..58967af5f3 100644 --- a/backend/FwLite/MiniLcm/Filtering/EntryFilterMapProvider.cs +++ b/backend/FwLite/MiniLcm/Filtering/EntryFilterMapProvider.cs @@ -19,6 +19,7 @@ public abstract class EntryFilterMapProvider public abstract Expression> EntryLexemeForm { get; } public abstract Expression> EntryCitationForm { get; } public abstract Expression> EntryLiteralMeaning { get; } + public abstract Expression> EntryMorphType { get; } public abstract Expression> EntryComplexFormTypes { get; } public virtual Func? EntryComplexFormTypesConverter { get; } = null; } diff --git a/backend/FwLite/MiniLcm/IMiniLcmReadApi.cs b/backend/FwLite/MiniLcm/IMiniLcmReadApi.cs index 7e396956bb..db89753c37 100644 --- a/backend/FwLite/MiniLcm/IMiniLcmReadApi.cs +++ b/backend/FwLite/MiniLcm/IMiniLcmReadApi.cs @@ -27,6 +27,11 @@ public interface IMiniLcmReadApi Task GetPublication(Guid id); Task GetSemanticDomain(Guid id); Task GetExampleSentence(Guid entryId, Guid senseId, Guid id); + /// + /// Get the index of an entry within the sorted/filtered entry list. + /// Returns -1 if the entry is not found. + /// + Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null); Task GetFileStream(MediaUri mediaUri) { @@ -34,12 +39,26 @@ Task GetFileStream(MediaUri mediaUri) } } +public record IndexQueryOptions( + SortOptions? Order = null, + ExemplarOptions? Exemplar = null, + EntryFilter? Filter = null) : FilterQueryOptions(Exemplar, Filter) +{ + public static new IndexQueryOptions Default { get; } = new(); + public SortOptions Order { get; init; } = Order ?? SortOptions.Default; + + public override IndexQueryOptions Normalized(NormalizationForm form) + { + return new(Order, Exemplar?.Normalized(form), Filter?.Normalized(form)); + } +} + public record FilterQueryOptions( ExemplarOptions? Exemplar = null, EntryFilter? Filter = null) { public static FilterQueryOptions Default { get; } = new(); - public bool HasFilter => Filter is {GridifyFilter.Length: > 0 } || Exemplar is {Value.Length: > 0}; + public bool HasFilter => Filter is { GridifyFilter.Length: > 0 } || Exemplar is { Value.Length: > 0 }; public virtual FilterQueryOptions Normalized(NormalizationForm form) { return new(Exemplar?.Normalized(form), Filter?.Normalized(form)); @@ -81,26 +100,12 @@ public IQueryable ApplyPaging(IQueryable queryable) public IOrderedEnumerable ApplyOrder(IEnumerable enumerable, Func orderFunc) { - if (Order.Ascending) - { - return enumerable.OrderBy(orderFunc); - } - else - { - return enumerable.OrderByDescending(orderFunc); - } + return Order.ApplyOrder(enumerable, orderFunc); } public IOrderedQueryable ApplyOrder(IQueryable enumerable, Expression> orderFunc) { - if (Order.Ascending) - { - return enumerable.OrderBy(orderFunc); - } - else - { - return enumerable.OrderByDescending(orderFunc); - } + return Order.ApplyOrder(enumerable, orderFunc); } } @@ -108,6 +113,16 @@ public record SortOptions(SortField Field, WritingSystemId WritingSystem = defau { public const string DefaultWritingSystem = "default"; public static SortOptions Default { get; } = new(SortField.Headword, DefaultWritingSystem); + + public IOrderedEnumerable ApplyOrder(IEnumerable enumerable, Func orderFunc) + { + return Ascending ? enumerable.OrderBy(orderFunc) : enumerable.OrderByDescending(orderFunc); + } + + public IOrderedQueryable ApplyOrder(IQueryable enumerable, Expression> orderFunc) + { + return Ascending ? enumerable.OrderBy(orderFunc) : enumerable.OrderByDescending(orderFunc); + } } public record ExemplarOptions(string Value, WritingSystemId WritingSystem) diff --git a/backend/FwLite/MiniLcm/Normalization/MiniLcmApiStringNormalizationWrapper.cs b/backend/FwLite/MiniLcm/Normalization/MiniLcmApiStringNormalizationWrapper.cs index 1204dc2a11..ea756ef69d 100644 --- a/backend/FwLite/MiniLcm/Normalization/MiniLcmApiStringNormalizationWrapper.cs +++ b/backend/FwLite/MiniLcm/Normalization/MiniLcmApiStringNormalizationWrapper.cs @@ -39,6 +39,11 @@ public IAsyncEnumerable GetEntries(QueryOptions? options = null) return _api.GetEntries(options?.Normalized(Form)); } + public Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null) + { + return _api.GetEntryIndex(entryId, query?.Normalize(Form), options?.Normalized(Form)); + } + void IDisposable.Dispose() { } diff --git a/backend/LfClassicData/LfClassicMiniLcmApi.cs b/backend/LfClassicData/LfClassicMiniLcmApi.cs index 0974cc7923..1b961a15e9 100644 --- a/backend/LfClassicData/LfClassicMiniLcmApi.cs +++ b/backend/LfClassicData/LfClassicMiniLcmApi.cs @@ -424,4 +424,9 @@ private static SemanticDomain ToSemanticDomain(Entities.OptionListItem item) if (exampleSentence is null) return null; return ToExampleSentence(sense.Guid, exampleSentence); } + + public Task GetEntryIndex(Guid entryId, string? query = null, IndexQueryOptions? options = null) + { + throw new NotImplementedException(); + } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index b3fde81e76..8d773777de 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -30,9 +30,6 @@ catalogs: '@vitest/ui': specifier: ^4.0.15 version: 4.0.15 - autoprefixer: - specifier: ^10.4.22 - version: 10.4.22 eslint: specifier: ^9.39.1 version: 9.39.1 @@ -136,7 +133,7 @@ importers: version: 2.4.10 '@vitejs/plugin-basic-ssl': specifier: 'catalog:' - version: 2.1.0(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + version: 2.1.0(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) css-tree: specifier: ^3.1.0 version: 3.1.0 @@ -157,7 +154,7 @@ importers: version: 4.17.1 runed: specifier: 'catalog:' - version: 0.37.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(zod@4.1.13) + version: 0.37.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(zod@4.1.13) set-cookie-parser: specifier: ^2.7.2 version: 2.7.2 @@ -169,7 +166,7 @@ importers: version: 0.12.3(@babel/core@7.28.5)(svelte@5.45.3) sveltekit-search-params: specifier: ^3.0.0 - version: 3.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + version: 3.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -206,16 +203,16 @@ importers: version: 1.57.0 '@stylistic/eslint-plugin': specifier: 'catalog:' - version: 5.6.1(eslint@9.39.1(jiti@1.21.7)) + version: 5.6.1(eslint@9.39.1(jiti@2.6.1)) '@sveltejs/adapter-node': specifier: ^5.4.0 - version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))) + version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))) '@sveltejs/kit': specifier: 2.49.0 - version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: 'catalog:' - version: 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + version: 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) '@tailwindcss/typography': specifier: ^0.5.19 version: 0.5.19(tailwindcss@3.4.18(yaml@2.8.2)) @@ -230,7 +227,7 @@ importers: version: 4.4.5 '@typescript-eslint/parser': specifier: 'catalog:' - version: 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@urql/core': specifier: ^6.0.1 version: 6.0.1(graphql@16.12.0) @@ -251,13 +248,13 @@ importers: version: 4.12.24(postcss@8.5.6) eslint: specifier: 'catalog:' - version: 9.39.1(jiti@1.21.7) + version: 9.39.1(jiti@2.6.1) eslint-output: specifier: 'catalog:' - version: 4.0.2(eslint@9.39.1(jiti@1.21.7)) + version: 4.0.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-svelte: specifier: 'catalog:' - version: 3.13.0(eslint@9.39.1(jiti@1.21.7))(svelte@5.45.3) + version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.3) globals: specifier: ^16.5.0 version: 16.5.0 @@ -287,13 +284,13 @@ importers: version: 1.4.0(svelte@5.45.3) svelte-preprocess: specifier: 'catalog:' - version: 6.0.3(@babel/core@7.28.5)(postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2))(postcss@8.5.6)(svelte@5.45.3)(typescript@5.9.3) + version: 6.0.3(@babel/core@7.28.5)(postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.8.2))(postcss@8.5.6)(svelte@5.45.3)(typescript@5.9.3) svelte-turnstile: specifier: ^0.11.0 version: 0.11.0(svelte@5.45.3) sveltekit-superforms: specifier: ^2.28.1 - version: 2.28.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(@types/json-schema@7.0.15)(esbuild@0.25.12)(svelte@5.45.3)(typescript@5.9.3) + version: 2.28.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(@types/json-schema@7.0.15)(esbuild@0.25.12)(svelte@5.45.3)(typescript@5.9.3) tailwindcss: specifier: 'catalog:' version: 3.4.18(yaml@2.8.2) @@ -308,16 +305,16 @@ importers: version: 5.9.3 typescript-eslint: specifier: 'catalog:' - version: 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) vite: specifier: 'catalog:' - version: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) + version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) vite-plugin-graphql-codegen: specifier: ^3.7.0 - version: 3.7.0(@graphql-codegen/cli@6.1.0(@types/node@24.10.1)(graphql@16.12.0)(typescript@5.9.3))(graphql@16.12.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + version: 3.7.0(@graphql-codegen/cli@6.1.0(@types/node@24.10.1)(graphql@16.12.0)(typescript@5.9.3))(graphql@16.12.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@27.2.0)(yaml@2.8.2) + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@27.2.0)(yaml@2.8.2) zod: specifier: ^4.1.13 version: 4.1.13 @@ -395,7 +392,7 @@ importers: version: 1.41.3 runed: specifier: 'catalog:' - version: 0.37.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(zod@4.1.13) + version: 0.37.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(zod@4.1.13) svelte-dnd-action: specifier: ^0.9.67 version: 0.9.67(svelte@5.45.3) @@ -407,7 +404,7 @@ importers: version: 0.2.2(@lingui/cli@5.6.1(typescript@5.9.3))(@lingui/core@5.6.1(@lingui/babel-plugin-lingui-macro@5.6.1(typescript@5.9.3)))(svelte@5.45.3)(typescript@5.9.3) svelte-preprocess: specifier: 'catalog:' - version: 6.0.3(@babel/core@7.28.5)(postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.6)(yaml@2.8.2))(postcss@8.5.6)(svelte@5.45.3)(typescript@5.9.3) + version: 6.0.3(@babel/core@7.28.5)(postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(yaml@2.8.2))(postcss@8.5.6)(svelte@5.45.3)(typescript@5.9.3) svelte-routing: specifier: ^2.13.0 version: 2.13.0 @@ -429,7 +426,7 @@ importers: version: 6.3.4 '@chromatic-com/storybook': specifier: ^4.1.3 - version: 4.1.3(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))) + version: 4.1.3(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))) '@egoist/tailwindcss-icons': specifier: ^1.9.0 version: 1.9.0(tailwindcss@3.4.18(yaml@2.8.2)) @@ -444,7 +441,7 @@ importers: version: 5.6.1(typescript@5.9.3) '@lingui/vite-plugin': specifier: ^5.6.1 - version: 5.6.1(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 5.6.1(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@mdi/js': specifier: ^7.4.47 version: 7.4.47 @@ -453,25 +450,25 @@ importers: version: 1.57.0 '@storybook/addon-a11y': specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))) + version: 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))) '@storybook/addon-docs': specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 0.0.0-pr-32289-sha-5b7a0231(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@storybook/addon-svelte-csf': specifier: ^5.0.10 - version: 5.0.10(@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3))(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 5.0.10(@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3))(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@storybook/addon-vitest': specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vitest@4.0.15) + version: 0.0.0-pr-32289-sha-5b7a0231(@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vitest@4.0.15) '@storybook/svelte-vite': specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 0.0.0-pr-32289-sha-5b7a0231(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@stylistic/eslint-plugin': specifier: 'catalog:' - version: 5.6.1(eslint@9.39.1(jiti@2.6.1)) + version: 5.6.1(eslint@9.39.1(jiti@1.21.7)) '@sveltejs/vite-plugin-svelte': specifier: 'catalog:' - version: 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@tailwindcss/container-queries': specifier: ^0.1.1 version: 0.1.1(tailwindcss@3.4.18(yaml@2.8.2)) @@ -486,37 +483,37 @@ importers: version: 24.10.1 '@typescript-eslint/parser': specifier: 'catalog:' - version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) '@vitejs/plugin-basic-ssl': specifier: 'catalog:' - version: 2.1.0(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 2.1.0(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@vitest/browser': specifier: 'catalog:' - version: 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15) + version: 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15) '@vitest/browser-playwright': specifier: 'catalog:' - version: 4.0.15(playwright@1.57.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15) + version: 4.0.15(playwright@1.57.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15) '@vitest/ui': specifier: 'catalog:' version: 4.0.15(vitest@4.0.15) bits-ui: specifier: 2.14.4 - version: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) + version: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) clsx: specifier: ^2.1.1 version: 2.1.1 eslint: specifier: 'catalog:' - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@1.21.7) eslint-output: specifier: 'catalog:' - version: 4.0.2(eslint@9.39.1(jiti@2.6.1)) + version: 4.0.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-storybook: specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(eslint@9.39.1(jiti@2.6.1))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(typescript@5.9.3) + version: 0.0.0-pr-32289-sha-5b7a0231(eslint@9.39.1(jiti@1.21.7))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(typescript@5.9.3) eslint-plugin-svelte: specifier: 'catalog:' - version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.3) + version: 3.13.0(eslint@9.39.1(jiti@1.21.7))(svelte@5.45.3) mode-watcher: specifier: ^1.1.0 version: 1.1.0(svelte@5.45.3) @@ -528,7 +525,7 @@ importers: version: 1.57.0 storybook: specifier: 0.0.0-pr-32289-sha-5b7a0231 - version: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) svelte: specifier: 'catalog:' version: 5.45.3 @@ -564,19 +561,19 @@ importers: version: 5.9.3 typescript-eslint: specifier: 'catalog:' - version: 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + version: 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) vaul-svelte: specifier: 1.0.0-next.6 version: 1.0.0-next.6(svelte@5.45.3) vite: specifier: 'catalog:' - version: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + version: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) vite-plugin-webfont-dl: specifier: ^3.11.1 - version: 3.11.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + version: 3.11.1(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2) + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) vitest-browser-svelte: specifier: ^2.0.1 version: 2.0.1(svelte@5.45.3)(vitest@4.0.15) @@ -6879,13 +6876,13 @@ snapshots: hashery: 1.3.0 keyv: 5.5.4 - '@chromatic-com/storybook@4.1.3(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))': + '@chromatic-com/storybook@4.1.3(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))': dependencies: '@neoconfetti/react': 1.0.0 chromatic: 13.3.4 filesize: 10.1.6 jsonfile: 6.2.0 - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) strip-ansi: 7.1.2 transitivePeerDependencies: - '@chromatic-com/cypress' @@ -8092,11 +8089,11 @@ snapshots: '@messageformat/parser': 5.1.1 js-sha256: 0.10.1 - '@lingui/vite-plugin@5.6.1(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@lingui/vite-plugin@5.6.1(typescript@5.9.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: '@lingui/cli': 5.6.1(typescript@5.9.3) '@lingui/conf': 5.6.1(typescript@5.9.3) - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -9049,21 +9046,21 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-a11y@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))': + '@storybook/addon-a11y@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))': dependencies: '@storybook/global': 5.0.0 axe-core: 4.11.0 - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) - '@storybook/addon-docs@0.0.0-pr-32289-sha-5b7a0231(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@storybook/addon-docs@0.0.0-pr-32289-sha-5b7a0231(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.0) - '@storybook/csf-plugin': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@storybook/csf-plugin': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@storybook/icons': 1.6.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@storybook/react-dom-shim': 0.0.0-pr-32289-sha-5b7a0231(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))) + '@storybook/react-dom-shim': 0.0.0-pr-32289-sha-5b7a0231(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' @@ -9072,56 +9069,56 @@ snapshots: - vite - webpack - '@storybook/addon-svelte-csf@5.0.10(@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3))(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@storybook/addon-svelte-csf@5.0.10(@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3))(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: '@storybook/csf': 0.1.13 - '@storybook/svelte': 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@storybook/svelte': 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) dedent: 1.7.0 es-toolkit: 1.42.0 esrap: 1.4.9 magic-string: 0.30.21 - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) svelte: 5.45.3 svelte-ast-print: 0.4.2(svelte@5.45.3) - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) zimmerframe: 1.1.4 transitivePeerDependencies: - babel-plugin-macros - '@storybook/addon-vitest@0.0.0-pr-32289-sha-5b7a0231(@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vitest@4.0.15)': + '@storybook/addon-vitest@0.0.0-pr-32289-sha-5b7a0231(@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vitest@4.0.15)': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 1.6.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) prompts: 2.4.2 - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) ts-dedent: 2.2.0 optionalDependencies: - '@vitest/browser': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15) - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2) + '@vitest/browser': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) transitivePeerDependencies: - react - react-dom - '@storybook/builder-vite@0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@storybook/builder-vite@0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: - '@storybook/csf-plugin': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@storybook/csf-plugin': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) ts-dedent: 2.2.0 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@storybook/csf-plugin@0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) unplugin: 2.3.11 optionalDependencies: esbuild: 0.25.12 rollup: 4.53.3 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) '@storybook/csf@0.1.13': dependencies: @@ -9134,31 +9131,31 @@ snapshots: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - '@storybook/react-dom-shim@0.0.0-pr-32289-sha-5b7a0231(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))': + '@storybook/react-dom-shim@0.0.0-pr-32289-sha-5b7a0231(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))': dependencies: react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) - '@storybook/svelte-vite@0.0.0-pr-32289-sha-5b7a0231(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@storybook/svelte-vite@0.0.0-pr-32289-sha-5b7a0231(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: - '@storybook/builder-vite': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) - '@storybook/svelte': 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@storybook/builder-vite': 0.0.0-pr-32289-sha-5b7a0231(esbuild@0.25.12)(rollup@4.53.3)(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@storybook/svelte': 0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) magic-string: 0.30.21 - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) svelte: 5.45.3 svelte2tsx: 0.7.45(svelte@5.45.3)(typescript@5.9.3) typescript: 5.9.3 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)': + '@storybook/svelte@0.0.0-pr-32289-sha-5b7a0231(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)': dependencies: - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) svelte: 5.45.3 ts-dedent: 2.2.0 type-fest: 2.19.0 @@ -9187,12 +9184,12 @@ snapshots: dependencies: acorn: 8.15.0 - '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))': + '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))': dependencies: '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3) '@rollup/plugin-json': 6.1.0(rollup@4.53.3) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.53.3) - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) rollup: 4.53.3 '@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': @@ -9215,6 +9212,7 @@ snapshots: vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 + optional: true '@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': dependencies: @@ -9236,14 +9234,13 @@ snapshots: vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) optionalDependencies: '@opentelemetry/api': 1.9.0 - optional: true - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) debug: 4.4.3 svelte: 5.45.3 - vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -9265,17 +9262,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) debug: 4.4.3 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.21 svelte: 5.45.3 svelte-hmr: 0.16.0(svelte@5.45.3) - vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) - vitefu: 0.2.5(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vitefu: 0.2.5(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -9684,13 +9681,12 @@ snapshots: '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) playwright: 1.57.0 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@27.2.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - optional: true '@vitest/browser-playwright@4.0.15(playwright@1.57.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15)': dependencies: @@ -9698,12 +9694,13 @@ snapshots: '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) playwright: 1.57.0 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@27.2.0)(yaml@2.8.2) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite + optional: true '@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15)': dependencies: @@ -9714,14 +9711,13 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@27.2.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) ws: 8.18.3 transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - optional: true '@vitest/browser@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15)': dependencies: @@ -9732,13 +9728,14 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@27.2.0)(yaml@2.8.2) ws: 8.18.3 transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite + optional: true '@vitest/expect@3.2.4': dependencies: @@ -9757,13 +9754,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) '@vitest/mocker@4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))': dependencies: @@ -9815,7 +9812,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@27.2.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) '@vitest/utils@3.2.4': dependencies: @@ -9994,15 +9991,15 @@ snapshots: binary-extensions@2.3.0: {} - bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3): + bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3): dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/dom': 1.7.4 '@internationalized/date': 3.10.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) + runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) svelte: 5.45.3 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) tabbable: 6.3.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -10658,11 +10655,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-storybook@0.0.0-pr-32289-sha-5b7a0231(eslint@9.39.1(jiti@2.6.1))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(typescript@5.9.3): + eslint-plugin-storybook@0.0.0-pr-32289-sha-5b7a0231(eslint@9.39.1(jiti@1.21.7))(storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.39.1(jiti@2.6.1) - storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@typescript-eslint/utils': 8.48.1(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.1(jiti@1.21.7) + storybook: 0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - typescript @@ -13024,14 +13021,14 @@ snapshots: esm-env: 1.2.2 svelte: 5.45.3 - runed@0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3): + runed@0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.45.3 optionalDependencies: - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) runed@0.37.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(zod@4.1.13): dependencies: @@ -13182,14 +13179,14 @@ snapshots: std-env@3.10.0: {} - storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)): + storybook@0.0.0-pr-32289-sha-5b7a0231(@testing-library/dom@10.4.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 1.6.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@testing-library/jest-dom': 6.9.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) '@vitest/spy': 3.2.4 better-opn: 3.0.2 esbuild: 0.25.12 @@ -13373,10 +13370,10 @@ snapshots: runed: 0.28.0(svelte@5.45.3) svelte: 5.45.3 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3) + runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3) style-to-object: 1.0.14 svelte: 5.45.3 transitivePeerDependencies: @@ -13426,18 +13423,18 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-search-params@3.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): + sveltekit-search-params@3.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)): dependencies: - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) svelte: 5.45.3 transitivePeerDependencies: - supports-color - vite - sveltekit-superforms@2.28.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(@types/json-schema@7.0.15)(esbuild@0.25.12)(svelte@5.45.3)(typescript@5.9.3): + sveltekit-superforms@2.28.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(@types/json-schema@7.0.15)(esbuild@0.25.12)(svelte@5.45.3)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) + '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.45.3)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) devalue: 5.5.0 memoize-weak: 1.0.2 svelte: 5.45.3 @@ -13837,19 +13834,19 @@ snapshots: react-dom: 19.2.0(react@19.2.0) svelte: 5.45.3 - vite-plugin-graphql-codegen@3.7.0(@graphql-codegen/cli@6.1.0(@types/node@24.10.1)(graphql@16.12.0)(typescript@5.9.3))(graphql@16.12.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): + vite-plugin-graphql-codegen@3.7.0(@graphql-codegen/cli@6.1.0(@types/node@24.10.1)(graphql@16.12.0)(typescript@5.9.3))(graphql@16.12.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)): dependencies: '@graphql-codegen/cli': 6.1.0(@types/node@24.10.1)(graphql@16.12.0)(typescript@5.9.3) graphql: 16.12.0 - vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) - vite-plugin-webfont-dl@3.11.1(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)): + vite-plugin-webfont-dl@3.11.1(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): dependencies: axios: 1.13.2 clean-css: 5.3.3 flat-cache: 6.1.19 picocolors: 1.1.1 - vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) transitivePeerDependencies: - debug @@ -13881,9 +13878,9 @@ snapshots: jiti: 2.6.1 yaml: 2.8.2 - vitefu@0.2.5(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): + vitefu@0.2.5(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)): optionalDependencies: - vite: 7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2) + vite: 7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2) vitefu@1.1.1(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)): optionalDependencies: @@ -13896,9 +13893,9 @@ snapshots: vitest-browser-svelte@2.0.1(svelte@5.45.3)(vitest@4.0.15): dependencies: svelte: 5.45.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2) - vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@27.2.0)(yaml@2.8.2): + vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@1.21.7)(jsdom@26.1.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.15 '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2)) @@ -13925,7 +13922,7 @@ snapshots: '@types/node': 24.10.1 '@vitest/browser-playwright': 4.0.15(playwright@1.57.0)(vite@7.2.6(@types/node@24.10.1)(jiti@1.21.7)(yaml@2.8.2))(vitest@4.0.15) '@vitest/ui': 4.0.15(vitest@4.0.15) - jsdom: 27.2.0 + jsdom: 26.1.0 transitivePeerDependencies: - jiti - less @@ -13939,7 +13936,7 @@ snapshots: - tsx - yaml - vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(yaml@2.8.2): + vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.0.15)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@27.2.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.15 '@vitest/mocker': 4.0.15(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2)) @@ -13966,7 +13963,7 @@ snapshots: '@types/node': 24.10.1 '@vitest/browser-playwright': 4.0.15(playwright@1.57.0)(vite@7.2.6(@types/node@24.10.1)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.15) '@vitest/ui': 4.0.15(vitest@4.0.15) - jsdom: 26.1.0 + jsdom: 27.2.0 transitivePeerDependencies: - jiti - less diff --git a/frontend/viewer/.gitignore b/frontend/viewer/.gitignore index 7e0b04f8df..1fa96a2456 100644 --- a/frontend/viewer/.gitignore +++ b/frontend/viewer/.gitignore @@ -26,4 +26,4 @@ html-test-results *storybook.log storybook-static -"screenshots/" +screenshots/ diff --git a/frontend/viewer/AGENTS.md b/frontend/viewer/AGENTS.md index 90f9e86a4d..bfc6d8e968 100644 --- a/frontend/viewer/AGENTS.md +++ b/frontend/viewer/AGENTS.md @@ -15,6 +15,35 @@ pnpm install pnpm run dev ``` +### Generated .NET Types + +This project depends on TypeScript types and API interfaces generated from .NET (via `Reinforced.Typings`). If you change .NET models or `JSInvokable` APIs, you must rebuild the backend to update these types. + +```bash +# From repo root +dotnet build backend/FwLite/FwLiteShared/FwLiteShared.csproj +``` + +The generated files are located in `src/lib/dotnet-types/generated-types/`. + +### E2E Testing (Playwright) + +To run E2E tests for the viewer: +```bash +# From frontend/viewer/ directory +# Automatically starts dev server if needed +# For debugging e.g. with Chrome MCP: dev server/demo project will be available at port 5173 & path /testing/project-view/browse + +# Filter by test name (the ONLY RIGHT choice if testing specific features or changes) e.g. +task playwright-test-standalone -- entries-list + +# All tests +task playwright-test-standalone + +# In UI mode +task playwright-test-standalone -- entries-list --ui +``` + ## Tech Stack - **Framework**: SvelteKit + Vite diff --git a/frontend/viewer/Taskfile.yml b/frontend/viewer/Taskfile.yml index af04e77550..d9faef2d29 100644 --- a/frontend/viewer/Taskfile.yml +++ b/frontend/viewer/Taskfile.yml @@ -46,6 +46,7 @@ tasks: desc: 'runs playwright tests against already running server' cmd: pnpm run test:playwright {{.CLI_ARGS}} playwright-test-standalone: + aliases: [pts] desc: 'runs playwright tests and runs dev automatically, run ui mode by calling with -- --ui or use --update-snapshots' env: AUTO_START_SERVER: true diff --git a/frontend/viewer/eslint.config.js b/frontend/viewer/eslint.config.js index 240d3eeeb2..0b2770dc00 100644 --- a/frontend/viewer/eslint.config.js +++ b/frontend/viewer/eslint.config.js @@ -67,6 +67,11 @@ export default [ 'format': ['camelCase', 'UPPER_CASE'], 'leadingUnderscore': 'allow', }, + { + 'selector': 'classProperty', + 'modifiers': ['static', 'readonly'], + 'format': ['camelCase', 'UPPER_CASE'], + }, { 'selector': ['typeLike', 'enumMember'], 'format': ['PascalCase'], diff --git a/frontend/viewer/playwright.config.ts b/frontend/viewer/playwright.config.ts index 094f0c9d5e..99f0384271 100644 --- a/frontend/viewer/playwright.config.ts +++ b/frontend/viewer/playwright.config.ts @@ -1,5 +1,6 @@ -import { defineConfig, devices, type ReporterDescription } from '@playwright/test'; +import {defineConfig, devices, type ReporterDescription} from '@playwright/test'; import * as testEnv from '../tests/envVars'; + const vitePort = '5173'; const dotnetPort = '5137'; const autoStartServer = process.env.AUTO_START_SERVER ? Boolean(process.env.AUTO_START_SERVER) : false; diff --git a/frontend/viewer/src/lib/components/Delayed.svelte b/frontend/viewer/src/lib/components/Delayed.svelte new file mode 100644 index 0000000000..9f72d1982c --- /dev/null +++ b/frontend/viewer/src/lib/components/Delayed.svelte @@ -0,0 +1,104 @@ + + + +{@render children(state)} diff --git a/frontend/viewer/src/lib/components/ListItem.svelte b/frontend/viewer/src/lib/components/ListItem.svelte index a690c04a20..097b8693b1 100644 --- a/frontend/viewer/src/lib/components/ListItem.svelte +++ b/frontend/viewer/src/lib/components/ListItem.svelte @@ -34,6 +34,7 @@