From b3a976abe0dedb4f9fc6ecb20d15f3d57f87ca73 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:35:11 +0200 Subject: [PATCH 01/43] feat(markers-checklist): add HeadingMarkers + JoinedTextLanguage project data type constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For new MarkerNames PDP data types — see tj-review-design.md §2.1. --- c-sharp/Projects/ProjectDataType.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/c-sharp/Projects/ProjectDataType.cs b/c-sharp/Projects/ProjectDataType.cs index 29d1ba20585..cae20fbe7f8 100644 --- a/c-sharp/Projects/ProjectDataType.cs +++ b/c-sharp/Projects/ProjectDataType.cs @@ -21,4 +21,8 @@ public static class ProjectDataType public const string FINAL_VERSE_NUMBER = "FinalVerseNumber"; public const string FINAL_CHAPTER = "FinalChapter"; public const string FINAL_VERSE_NUMBERS_IN_BOOK = "FinalVerseNumbersInBook"; + + // platformScripture.MarkerNames projectInterface — Heading classification + per-book language + public const string HEADING_MARKERS = "HeadingMarkers"; + public const string JOINED_TEXT_LANGUAGE = "JoinedTextLanguage"; } From 25506d637aee02ba925ce4b5ccadcd31dbfe6b0c Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:36:23 +0200 Subject: [PATCH 02/43] feat(markers-checklist): extend IMarkerNamesProjectDataProvider with HeadingMarkers + JoinedTextLanguage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New data types follow the canonical get/set/subscribe trio with throwing setters (mirrors the Versification PDP pattern from PR #2277). The underlying stylesheet + language data is read-only — owned by libpalaso, not writable through PDPs. --- .../src/types/platform-scripture.d.ts | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 54f1da5ed51..11f677c08b7 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -835,13 +835,31 @@ declare module 'platform-scripture' { // #region Marker Types - /** Provides information about markers */ + /** Provides information about markers + heading classification + per-book language. */ export type MarkerNamesProjectInterfaceDataTypes = { /** Gets an array of string that contain information about markers */ MarkerNames: DataProviderDataType; + /** + * Heading-style paragraph markers per book. Drawn from libpalaso's `ScrStylesheet` — markers + * whose `StyleType == scParagraphStyle` AND `TextType == scSection`. Read-only — derived from + * the project's stylesheet; `set*` is not supported. + */ + HeadingMarkers: DataProviderDataType; + /** + * The language tag of the (possibly joined) text for a book. Most projects return the + * project-level language tag for every book; sigla projects (those that join multiple + * base-language texts per book — see Glossary) return per-book language tags. Read-only. + */ + JoinedTextLanguage: DataProviderDataType; }; - /** Provides a string array that contains information about the markers used in this project */ + /** + * Provides marker info, heading classification, and per-book language for a project. + * + * All `set*` methods exist only to satisfy the canonical DataProvider contract; the underlying + * values are derived from the project's stylesheet and joined-text settings (libpalaso authority) + * and cannot be written through this PDP. + */ export type IMarkerNamesProjectDataProvider = IProjectDataProvider & { /** @@ -858,10 +876,7 @@ declare module 'platform-scripture' { * Subscribe to run a callback function when marker info changed * * @param bookNum Tells the provider what changes to listen for - * @param callback Function to run with the updated marker info for this selector. If there is - * an error while retrieving the updated data, the function will run with a - * {@link PlatformError} instead of the data. You can call {@link isPlatformError} on this - * value to check if it is an error. + * @param callback Function to run with the updated marker info for this selector. * @param options Various options to adjust how the subscriber emits updates * @returns Unsubscriber function (run to unsubscribe from listening for updates) */ @@ -870,6 +885,40 @@ declare module 'platform-scripture' { callback: (markerNames: string[] | undefined | PlatformError) => void, options?: DataProviderSubscriberOptions, ): Promise; + /** + * Returns the project's heading-style paragraph markers for the given book. Empty array means + * the stylesheet defines none. + */ + getHeadingMarkers(bookNum: number): Promise; + /** + * Read-only — throws if called. Heading classification is owned by the project's stylesheet + * (libpalaso `ScrStylesheet`); there is no setter. + */ + setHeadingMarkers( + newValue: never, + ): Promise>; + /** Subscribe to heading-markers changes for the given book. */ + subscribeHeadingMarkers( + bookNum: number, + callback: (headingMarkers: string[] | PlatformError) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + /** + * Returns the (possibly joined-text) language tag for the given book. For most projects this + * equals the project-level language tag; for sigla projects (Glossary), this can vary + * per-book. + */ + getJoinedTextLanguage(bookNum: number): Promise; + /** Read-only — throws if called. */ + setJoinedTextLanguage( + newValue: never, + ): Promise>; + /** Subscribe to joined-text-language changes for the given book. */ + subscribeJoinedTextLanguage( + bookNum: number, + callback: (joinedTextLanguage: string | PlatformError) => void, + options?: DataProviderSubscriberOptions, + ): Promise; }; // #endregion Marker Types From 0f14c54584d1f469145a641e567509de5fe06835 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:38:58 +0200 Subject: [PATCH 03/43] feat(markers-checklist): GetHeadingMarkers + throwing SetHeadingMarkers on ParatextProjectDataProvider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Returns scParagraphStyle + scSection markers from the project's stylesheet, per book. Read-only — Set throws. Used by TS-side checklist orchestrator for INV-009 heading-paragraph classification. --- ...tProjectDataProviderHeadingMarkersTests.cs | 106 ++++++++++++++++++ .../Projects/ParatextProjectDataProvider.cs | 36 ++++++ 2 files changed, 142 insertions(+) create mode 100644 c-sharp-tests/Projects/ParatextProjectDataProviderHeadingMarkersTests.cs diff --git a/c-sharp-tests/Projects/ParatextProjectDataProviderHeadingMarkersTests.cs b/c-sharp-tests/Projects/ParatextProjectDataProviderHeadingMarkersTests.cs new file mode 100644 index 00000000000..b6174ee24b6 --- /dev/null +++ b/c-sharp-tests/Projects/ParatextProjectDataProviderHeadingMarkersTests.cs @@ -0,0 +1,106 @@ +using System.Diagnostics.CodeAnalysis; +using Paranext.DataProvider.Projects; +using Paratext.Data; + +namespace TestParanextDataProvider.Projects +{ + /// + /// Unit tests for + /// and . + /// + /// + /// Setup mirrors : + /// a is created and registered via + /// ParatextProjects.FakeAddProject, then we construct a + /// that subclasses the real PDP and + /// exposes its registered methods directly. + /// + /// + [ExcludeFromCodeCoverage] + [TestFixture] + internal class ParatextProjectDataProviderHeadingMarkersTests : PapiTestBase + { + private const string PdpName = "headingMarkersTestProject"; + private const int GenesisBookNum = 1; + + private ScrText _scrText = null!; + private ProjectDetails _projectDetails = null!; + private DummyParatextProjectDataProvider _provider = null!; + + [SetUp] + public override async Task TestSetupAsync() + { + await base.TestSetupAsync(); + + _scrText = CreateDummyProject(); + _projectDetails = CreateProjectDetails(_scrText); + ParatextProjects.FakeAddProject(_projectDetails, _scrText); + + _provider = new DummyParatextProjectDataProvider( + PdpName, + Client, + _projectDetails, + ParatextProjects + ); + } + + [TearDown] + public void TearDown() + { + _scrText?.Dispose(); + } + + [Test] + [Description( + "GetHeadingMarkers returns the markers from the stylesheet where " + + "StyleType == scParagraphStyle AND TextType == scSection. " + + "Default Paratext stylesheet should include at least 's', 'ms', 'mr' headings." + )] + public void GetHeadingMarkers_DefaultStylesheet_IncludesStandardHeadingMarkers() + { + var result = _provider.GetHeadingMarkers(GenesisBookNum); + + Assert.That(result, Is.Not.Null); + Assert.That(result, Is.Not.Empty); + // Standard USFM heading markers in default Paratext stylesheet. + Assert.That(result, Contains.Item("s")); + } + + [Test] + [Description( + "GetHeadingMarkers should never include non-paragraph markers (e.g. 'c', 'v')." + )] + public void GetHeadingMarkers_DefaultStylesheet_ExcludesChapterAndVerseMarkers() + { + var result = _provider.GetHeadingMarkers(GenesisBookNum); + + Assert.That(result, Does.Not.Contain("c"), "chapter marker should not be a heading"); + Assert.That(result, Does.Not.Contain("v"), "verse marker should not be a heading"); + } + + [Test] + [Description( + "GetHeadingMarkers should not include scVerseText paragraph markers like 'p'." + )] + public void GetHeadingMarkers_DefaultStylesheet_ExcludesBodyParagraphMarkers() + { + var result = _provider.GetHeadingMarkers(GenesisBookNum); + + Assert.That( + result, + Does.Not.Contain("p"), + "p is scVerseText, not scSection — should not be a heading" + ); + } + + [Test] + [Description("SetHeadingMarkers is read-only and must throw NotSupportedException.")] + public void SetHeadingMarkers_AlwaysThrows() + { + Assert.That( + () => _provider.SetHeadingMarkers(GenesisBookNum, new[] { "s" }), + Throws.TypeOf() + ); + } + } +} diff --git a/c-sharp/Projects/ParatextProjectDataProvider.cs b/c-sharp/Projects/ParatextProjectDataProvider.cs index e57b459aaa3..d81ac02da69 100644 --- a/c-sharp/Projects/ParatextProjectDataProvider.cs +++ b/c-sharp/Projects/ParatextProjectDataProvider.cs @@ -1606,6 +1606,42 @@ public string[] GetMarkerNames(int bookNum) #endregion + #region Heading classification (MarkerNames PDP — HeadingMarkers) + + private const string HeadingMarkersReadOnlyMessage = + "HeadingMarkers is read-only on the platformScripture.MarkerNames projectInterface. " + + "Heading classification is owned by the project's stylesheet (libpalaso ScrStylesheet) " + + "and cannot be written through this PDP."; + + /// + /// Returns the heading-style paragraph markers from the project's stylesheet for the given book + /// — i.e. markers where StyleType == scParagraphStyle AND TextType == scSection. + /// Used by the TS-side checklist orchestrator for INV-009 heading classification. + /// + public string[] GetHeadingMarkers(int bookNum) + { + var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.Id); + ScrStylesheet stylesheet = + scrText.ScrStylesheet(bookNum) + ?? throw new InvalidDataException($"ScrStylesheet for book number '{bookNum}' is null"); + return stylesheet + .Tags.Where(t => + t != null + && t.StyleType == ScrStyleType.scParagraphStyle + && t.TextType == ScrTextType.scSection + ) + .Select(t => t.Marker) + .ToArray(); + } + + /// + /// Read-only — always throws. See . + /// + public bool SetHeadingMarkers(int bookNum, string[] value) => + throw new NotSupportedException(HeadingMarkersReadOnlyMessage); + + #endregion + #region Versification (platformScripture.Versification) // Read-only projectInterface. The three data types (FinalVerseNumber, FinalChapter, From 8d060ebb7374779947e4bc9e183780e66a93ccdb Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:40:11 +0200 Subject: [PATCH 04/43] feat(markers-checklist): GetJoinedTextLanguage + throwing SetJoinedTextLanguage on ParatextProjectDataProvider Returns scrText.GetJoinedText(bookNum).Settings.LanguageID.Id with fallback to the project-level language tag. Per-book language is needed for sigla projects that join multiple base-language texts per book. --- ...jectDataProviderJoinedTextLanguageTests.cs | 69 +++++++++++++++++++ .../Projects/ParatextProjectDataProvider.cs | 38 ++++++++++ 2 files changed, 107 insertions(+) create mode 100644 c-sharp-tests/Projects/ParatextProjectDataProviderJoinedTextLanguageTests.cs diff --git a/c-sharp-tests/Projects/ParatextProjectDataProviderJoinedTextLanguageTests.cs b/c-sharp-tests/Projects/ParatextProjectDataProviderJoinedTextLanguageTests.cs new file mode 100644 index 00000000000..94f84e6cb97 --- /dev/null +++ b/c-sharp-tests/Projects/ParatextProjectDataProviderJoinedTextLanguageTests.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.CodeAnalysis; +using Paranext.DataProvider.Projects; +using Paratext.Data; + +namespace TestParanextDataProvider.Projects +{ + /// + /// Unit tests for + /// and . + /// + [ExcludeFromCodeCoverage] + [TestFixture] + internal class ParatextProjectDataProviderJoinedTextLanguageTests : PapiTestBase + { + private const string PdpName = "joinedTextLanguageTestProject"; + private const int GenesisBookNum = 1; + + private ScrText _scrText = null!; + private ProjectDetails _projectDetails = null!; + private DummyParatextProjectDataProvider _provider = null!; + + [SetUp] + public override async Task TestSetupAsync() + { + await base.TestSetupAsync(); + + _scrText = CreateDummyProject(); + _projectDetails = CreateProjectDetails(_scrText); + ParatextProjects.FakeAddProject(_projectDetails, _scrText); + + _provider = new DummyParatextProjectDataProvider( + PdpName, + Client, + _projectDetails, + ParatextProjects + ); + } + + [TearDown] + public void TearDown() + { + _scrText?.Dispose(); + } + + [Test] + [Description( + "For a non-sigla DummyScrText, GetJoinedTextLanguage falls back to the project-level " + + "language tag (since the dummy project has no joined-text setup)." + )] + public void GetJoinedTextLanguage_DummyProject_ReturnsProjectLanguageOrEmptyString() + { + // DummyScrText typically has no language tag configured or returns the dummy's + // default. The contract: returns a string (possibly empty); never throws. + var result = _provider.GetJoinedTextLanguage(GenesisBookNum); + + Assert.That(result, Is.Not.Null, "must return a string, never null"); + } + + [Test] + [Description("SetJoinedTextLanguage is read-only and must throw NotSupportedException.")] + public void SetJoinedTextLanguage_AlwaysThrows() + { + Assert.That( + () => _provider.SetJoinedTextLanguage(GenesisBookNum, "en"), + Throws.TypeOf() + ); + } + } +} diff --git a/c-sharp/Projects/ParatextProjectDataProvider.cs b/c-sharp/Projects/ParatextProjectDataProvider.cs index d81ac02da69..955ad0c964d 100644 --- a/c-sharp/Projects/ParatextProjectDataProvider.cs +++ b/c-sharp/Projects/ParatextProjectDataProvider.cs @@ -1642,6 +1642,44 @@ public bool SetHeadingMarkers(int bookNum, string[] value) => #endregion + #region Joined-text language (MarkerNames PDP — JoinedTextLanguage) + + private const string JoinedTextLanguageReadOnlyMessage = + "JoinedTextLanguage is read-only on the platformScripture.MarkerNames projectInterface. " + + "Language is owned by the project's settings; use the standard setSetting API instead."; + + /// + /// Returns the (possibly joined-text) language tag for the given book. For most projects this + /// equals the project-level language tag (from scrText.Settings.LanguageID); for sigla + /// projects that join multiple base-language texts per book, the per-book joined-text language + /// is returned via scrText.GetJoinedText(bookNum).Settings.LanguageID.Id. + /// Falls back to scrText.Settings.LanguageID?.Id ?? "" on any failure. + /// + public string GetJoinedTextLanguage(int bookNum) + { + var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.Id); + try + { + var joined = scrText.GetJoinedText(bookNum); + var languageId = joined?.Settings?.LanguageID?.Id; + if (!string.IsNullOrEmpty(languageId)) + return languageId; + } + catch + { + // GetJoinedText can throw for books with no joined-text config; fall through to fallback. + } + return scrText.Settings.LanguageID?.Id ?? string.Empty; + } + + /// + /// Read-only — always throws. See . + /// + public bool SetJoinedTextLanguage(int bookNum, string value) => + throw new NotSupportedException(JoinedTextLanguageReadOnlyMessage); + + #endregion + #region Versification (platformScripture.Versification) // Read-only projectInterface. The three data types (FinalVerseNumber, FinalChapter, From 90eac1faff76a54ebfd4fc4062fe7d79ac88732e Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:42:12 +0200 Subject: [PATCH 05/43] feat(markers-checklist): wire HeadingMarkers + JoinedTextLanguage methods into PDP GetFunctions Registers all four delegates (get + throwing set per data type) so the TS side can invoke them over the wire. Mirrors the Versification PDP wiring pattern from PR #2277. --- c-sharp/Projects/ParatextProjectDataProvider.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/c-sharp/Projects/ParatextProjectDataProvider.cs b/c-sharp/Projects/ParatextProjectDataProvider.cs index 955ad0c964d..8e31e418b74 100644 --- a/c-sharp/Projects/ParatextProjectDataProvider.cs +++ b/c-sharp/Projects/ParatextProjectDataProvider.cs @@ -135,6 +135,10 @@ LocalParatextProjects paratextProjects ); retVal.Add(("getMarkerNames", GetMarkerNames)); + retVal.Add(("getHeadingMarkers", GetHeadingMarkers)); + retVal.Add(("setHeadingMarkers", SetHeadingMarkers)); + retVal.Add(("getJoinedTextLanguage", GetJoinedTextLanguage)); + retVal.Add(("setJoinedTextLanguage", SetJoinedTextLanguage)); retVal.Add(("getFinalVerseNumber", GetFinalVerseNumber)); retVal.Add(("setFinalVerseNumber", SetFinalVerseNumber)); From d1f49ab151dc2901382ad568ffa0c303536d3448 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:45:22 +0200 Subject: [PATCH 06/43] feat(markers-checklist): add localized strings for new checklist project settings --- .../platform-scripture/contributions/localizedStrings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/src/platform-scripture/contributions/localizedStrings.json b/extensions/src/platform-scripture/contributions/localizedStrings.json index c161b01a802..505054241d6 100644 --- a/extensions/src/platform-scripture/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture/contributions/localizedStrings.json @@ -313,6 +313,10 @@ "%project_settings_platformScripture_baseCharacterClassRegex_label%": "Base Character Class Regex", "%project_settings_platformScripture_booksPresent_description%": "Which books of the Bible are present in this Scripture project", "%project_settings_platformScripture_booksPresent_label%": "Scripture Books Present", + "%project_settings_platformScripture_checklistEquivalentMarkers_description%": "Space-separated marker pairs in 'marker1/marker2' format identifying markers that should be treated as equivalent when 'Hide matches' is enabled in the Markers Checklist. Example: 'p/q s/s1' declares two pairs.", + "%project_settings_platformScripture_checklistEquivalentMarkers_label%": "Checklist: equivalent markers", + "%project_settings_platformScripture_checklistMarkerFilter_description%": "Space-separated USFM marker names to include in the Markers Checklist (e.g. 'p q m'). When empty, all paragraph markers in the project are included.", + "%project_settings_platformScripture_checklistMarkerFilter_label%": "Checklist: marker filter", "%project_settings_platformScripture_diacriticCharacterClassRegex_label%": "Diacritic Character Class Regex", "%project_settings_platformScripture_group1_label%": "Scripture Project Settings", "%project_settings_platformScripture_invalidCharacters_description%": "List of characters that are not accepted in this project.", From 8e3d73006e3d273a9b573b1702ee03318726cdcc Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:45:43 +0200 Subject: [PATCH 07/43] feat(markers-checklist): declare checklistEquivalentMarkers + checklistMarkerFilter project settings --- .../contributions/projectSettings.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/extensions/src/platform-scripture/contributions/projectSettings.json b/extensions/src/platform-scripture/contributions/projectSettings.json index 12799d9c236..41113817370 100644 --- a/extensions/src/platform-scripture/contributions/projectSettings.json +++ b/extensions/src/platform-scripture/contributions/projectSettings.json @@ -50,6 +50,18 @@ "default": "", "includeProjectInterfaces": ["Paratext", "Scripture"] }, + "platformScripture.checklistEquivalentMarkers": { + "label": "%project_settings_platformScripture_checklistEquivalentMarkers_label%", + "description": "%project_settings_platformScripture_checklistEquivalentMarkers_description%", + "default": "", + "includeProjectInterfaces": ["Paratext", "Scripture"] + }, + "platformScripture.checklistMarkerFilter": { + "label": "%project_settings_platformScripture_checklistMarkerFilter_label%", + "description": "%project_settings_platformScripture_checklistMarkerFilter_description%", + "default": "", + "includeProjectInterfaces": ["Paratext", "Scripture"] + }, "platformScripture.validPunctuation": { "label": "%project_settings_platformScripture_validPunctuation_label%", "description": "%project_settings_platformScripture_validPunctuation_description%", From 85c8ccdd14ab40dd72dcf55c1bc93e66862b88a4 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 20:47:26 +0200 Subject: [PATCH 08/43] feat(markers-checklist): register stub project-setting validators for checklist marker settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stubs accept any string until Phase 3's parseMarkerSettings lands (Task 3.1 will replace the equivalent-markers validator with the real strict parse). Also adds the two new keys to the ProjectSettingTypes augmentation in platform-scripture.d.ts — required so ProjectSettingValidator<...> typechecks for the new setting names. The plan's escalation note (Phase 2 Task 2.3, "When to escalate") explicitly anticipated this; without the augmentation the generic-type lookup fails with TS2344. Declarations are minimal — just the key plus a one-paragraph TSDoc — matching the existing valid/invalidMarkers shape. --- extensions/src/platform-scripture/src/main.ts | 22 +++++++++++++++++++ .../src/types/platform-scripture.d.ts | 14 ++++++++++++ 2 files changed, 36 insertions(+) diff --git a/extensions/src/platform-scripture/src/main.ts b/extensions/src/platform-scripture/src/main.ts index fe06fb8774d..dec40e932eb 100644 --- a/extensions/src/platform-scripture/src/main.ts +++ b/extensions/src/platform-scripture/src/main.ts @@ -80,6 +80,18 @@ const punctuationValidator: ProjectSettingValidator< 'platformScripture.validPunctuation' | 'platformScripture.invalidPunctuation' > = async (newValue) => typeof newValue === 'string'; +// Equivalent-markers validator: delegates to the pure-TS parseMarkerSettings. +// Until Phase 3 lands, this is a stub that accepts any string; Phase 3 Task 3.1 +// will replace this with the real strict-parse check. +const checklistEquivalentMarkersValidator: ProjectSettingValidator< + 'platformScripture.checklistEquivalentMarkers' +> = async (newValue) => typeof newValue === 'string'; + +// Marker filter: plain string check; the runtime parser is tolerant. +const checklistMarkerFilterValidator: ProjectSettingValidator< + 'platformScripture.checklistMarkerFilter' +> = async (newValue) => typeof newValue === 'string'; + // #endregion async function openPlatformCharactersInventory( @@ -433,6 +445,14 @@ export async function activate(context: ExecutionActivationContext) { 'platformScripture.invalidMarkers', markersValidator, ); + const checklistEquivalentMarkersPromise = papi.projectSettings.registerValidator( + 'platformScripture.checklistEquivalentMarkers', + checklistEquivalentMarkersValidator, + ); + const checklistMarkerFilterPromise = papi.projectSettings.registerValidator( + 'platformScripture.checklistMarkerFilter', + checklistMarkerFilterValidator, + ); const openMarkersInventoryPromise = papi.commands.registerCommand( 'platformScripture.openMarkersInventory', openPlatformMarkersInventory, @@ -647,6 +667,8 @@ export async function activate(context: ExecutionActivationContext) { await repeatableWordsInventoryWebViewProviderPromise, await validMarkersPromise, await invalidMarkersPromise, + await checklistEquivalentMarkersPromise, + await checklistMarkerFilterPromise, await openMarkersInventoryPromise, await markersInventoryWebViewProviderPromise, await validPunctuationPromise, diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 11f677c08b7..38ca6f79f54 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -2272,6 +2272,20 @@ declare module 'papi-shared-types' { 'platformScripture.invalidMarkers': string; + /** + * Space-separated marker pairs in `marker1/marker2` format identifying markers that should be + * treated as equivalent when "Hide matches" is enabled in the Markers Checklist. Example: `p/q + * s/s1` declares two pairs. Phase 3 will introduce a strict parser; for now the validator only + * checks that the value is a string. + */ + 'platformScripture.checklistEquivalentMarkers': string; + + /** + * Space-separated USFM marker names to include in the Markers Checklist (e.g. `p q m`). When + * empty, all paragraph markers in the project are included. + */ + 'platformScripture.checklistMarkerFilter': string; + 'platformScripture.validPunctuation': string; 'platformScripture.invalidPunctuation': string; From a885c316c8e05e392b43fbdf956d2cfee2861048 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:03:31 +0200 Subject: [PATCH 09/43] feat(markers-checklist): parse-marker-settings.ts (port from C# MarkerSettingsValidationTests) Pure TS strict parser; replaces the backend validateMarkerSettings NetworkObject method. Used by both the Marker Settings dialog's keystroke feedback (live) and the new platformScripture.checklistEquivalentMarkers project-setting validator (set-time). 24 tests ported byte-for-byte from MarkerSettingsValidationTests.cs. --- .../checklists/parse-marker-settings.test.ts | 250 ++++++++++++++++++ .../src/checklists/parse-marker-settings.ts | 77 ++++++ extensions/src/platform-scripture/src/main.ts | 10 +- 3 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 extensions/src/platform-scripture/src/checklists/parse-marker-settings.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/parse-marker-settings.ts diff --git a/extensions/src/platform-scripture/src/checklists/parse-marker-settings.test.ts b/extensions/src/platform-scripture/src/checklists/parse-marker-settings.test.ts new file mode 100644 index 00000000000..a2ab88849c5 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/parse-marker-settings.test.ts @@ -0,0 +1,250 @@ +import { describe, expect, it } from 'vitest'; +import { INVALID_MARKER_PAIR_ERROR_KEY, parseMarkerSettings } from './parse-marker-settings'; + +/** + * Tests ported byte-for-byte from + * `c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs` (22 tests). + * + * The C# implementation `MarkersDataSource.ValidateMarkerSettings(string)` returns a + * `MarkerSettingsValidationResult` with shape `{ Valid, ParsedPairs, ErrorMessage }`. The TS port + * returns a discriminated union `{ valid: true; parsedPairs } | { valid: false; errorMessageKey }` + * — narrowing on `valid` exposes the populated field. The structural invariant (§3.13 mutex) is + * enforced by TypeScript narrowing rather than runtime null checks. Tests use `toEqual` against + * full result shapes to avoid conditional expectations. + */ + +describe('parseMarkerSettings', () => { + // ===================================================================== + // Happy-path scenarios — valid input returns valid=true with parsed pairs + // ===================================================================== + + describe('happy path', () => { + it('TS-VAL-002-01: single pair "p/q" parses to one MarkerPair', () => { + expect(parseMarkerSettings('p/q')).toEqual({ + valid: true, + parsedPairs: [{ marker1: 'p', marker2: 'q' }], + }); + }); + + it('TS-VAL-002-02: multiple pairs "p/q q1/q2" parses to TWO pairs in source order', () => { + expect(parseMarkerSettings('p/q q1/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'p', marker2: 'q' }, + { marker1: 'q1', marker2: 'q2' }, + ], + }); + }); + + it('TS-VAL-002-07: empty string is valid with empty pair list', () => { + expect(parseMarkerSettings('')).toEqual({ valid: true, parsedPairs: [] }); + }); + + // Derived from PT9 line 30: `string equivalents = EquivalentMarkers ?? "";`. Null coerces to + // empty, which takes the valid-empty branch. + it('TS-VAL-002-07 (null): null input is valid with empty pair list (defensive)', () => { + // The TS signature is `string`, but C# coerces null to empty via `?? ""` — mirror that + // behavior here by passing a type-erased null-ish value. Runtime nullish-coalescing handles + // it identically. Build the null at runtime so the file stays free of literal `null`s. + // eslint-disable-next-line no-type-assertion/no-type-assertion -- defensive nullish check + const nullishInput = JSON.parse('null') as unknown as string; + expect(parseMarkerSettings(nullishInput)).toEqual({ valid: true, parsedPairs: [] }); + }); + + it('TS-VAL-002-07 (whitespace): whitespace-only input is valid with empty pair list', () => { + expect(parseMarkerSettings(' ')).toEqual({ valid: true, parsedPairs: [] }); + }); + + it('TS-VAL-002-06: multiple spaces between pairs are collapsed before splitting', () => { + expect(parseMarkerSettings('p/q q1/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'p', marker2: 'q' }, + { marker1: 'q1', marker2: 'q2' }, + ], + }); + }); + + it('leading and trailing whitespace is trimmed', () => { + expect(parseMarkerSettings(' p/q ')).toEqual({ + valid: true, + parsedPairs: [{ marker1: 'p', marker2: 'q' }], + }); + }); + }); + + // ===================================================================== + // Error scenarios — malformed input returns valid=false with localize key + // ===================================================================== + + describe('error scenarios', () => { + it('TS-VAL-002-03: single marker with no slash returns invalid', () => { + expect(parseMarkerSettings('p')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('TS-VAL-002-04: triple slash returns invalid', () => { + expect(parseMarkerSettings('p/q/r')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('TS-VAL-002-05: empty left side returns invalid', () => { + expect(parseMarkerSettings('/q')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('empty right side "p/" returns invalid', () => { + expect(parseMarkerSettings('p/')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('both sides empty "/" returns invalid', () => { + expect(parseMarkerSettings('/')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('trailing whitespace on right side "p/q a/ " returns invalid', () => { + // PT9 :37 requires items[1].Trim().Length > 0. PT9 collapses inner whitespace via + // Regex.Replace(" +", " ") then splits on ' ', so "p/q a/ " becomes "p/q a/ " → tokens + // [p/q, a/]. Second token has empty rhs after trim. + expect(parseMarkerSettings('p/q a/ ')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('whitespace-only right side "p/ " returns invalid', () => { + expect(parseMarkerSettings('p/ ')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('whitespace-only left side " /q" returns invalid', () => { + expect(parseMarkerSettings(' /q')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('mixed valid+invalid "p/q invalid good/bad" fails fast on first invalid pair', () => { + // PT9 :41 uses `return;` inside the foreach — first invalid token aborts the entire + // validation even when surrounded by valid pairs. Contract divergence from + // initializeMarkerMappings (VAL-005 silent-skip). + expect(parseMarkerSettings('p/q invalid good/bad')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + + it('errorMessageKey is the canonical localize key (VAL-002)', () => { + // The static service returns the localize key; resolution to a localized string happens at + // the wire boundary. Mirrors C# `MarkersDataSource.InvalidMarkerPairErrorKey` constant. + expect(INVALID_MARKER_PAIR_ERROR_KEY).toBe('%markersChecklist_errorInvalidMarkerPair%'); + }); + }); + + // ===================================================================== + // §3.13 structural invariants — parsedPairs ⊕ errorMessageKey mutually exclusive + // ===================================================================== + + describe('§3.13 structural invariants — parsedPairs ⊕ errorMessageKey mutually exclusive', () => { + it('valid=false ⇒ no parsedPairs field (no partial-parse leakage)', () => { + // C# asserts `result.ParsedPairs is null` on failure even when "p/q" parsed successfully + // BEFORE "invalid" failed — no partial-parse leakage. The TS port uses a discriminated + // union, so parsedPairs is structurally absent on the failure branch. + const result = parseMarkerSettings('p/q invalid'); + expect(result).toEqual({ valid: false, errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY }); + expect('parsedPairs' in result).toBe(false); + }); + + it('valid=true ⇒ no errorMessageKey field', () => { + const result = parseMarkerSettings('p/q q1/q2'); + expect('errorMessageKey' in result).toBe(false); + expect(result).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'p', marker2: 'q' }, + { marker1: 'q1', marker2: 'q2' }, + ], + }); + }); + }); + + // ===================================================================== + // Golden-master-derived scenarios — gm-007, gm-008 + // ===================================================================== + + describe('golden-master scenarios', () => { + it('gm-007: bidirectional input "p/q q1/q2" parses to two pairs', () => { + expect(parseMarkerSettings('p/q q1/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'p', marker2: 'q' }, + { marker1: 'q1', marker2: 'q2' }, + ], + }); + }); + + it('gm-008: accumulated pairs "q/q1 q/q2" preserved in source order', () => { + expect(parseMarkerSettings('q/q1 q/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'q', marker2: 'q1' }, + { marker1: 'q', marker2: 'q2' }, + ], + }); + }); + }); + + // ===================================================================== + // CAP-002 cross-reference scenarios — TS-016, TS-017, TS-018 + // ===================================================================== + + describe('CAP-002 cross-reference scenarios', () => { + it('TS-016: parses bidirectional input "p/q q1/q2" as two pairs (no expansion)', () => { + // The validator does not perform bidirectional expansion (that is + // initializeMarkerMappings' concern). It just preserves the source pairs. + expect(parseMarkerSettings('p/q q1/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'p', marker2: 'q' }, + { marker1: 'q1', marker2: 'q2' }, + ], + }); + }); + + it('TS-017: accumulated pairs "q/q1 q/q2" preserved as two distinct pairs', () => { + // Same left-hand marker in multiple pairs is not a format violation. + // initializeMarkerMappings will later accumulate the targets; the validator keeps them as + // two pairs in source order. + expect(parseMarkerSettings('q/q1 q/q2')).toEqual({ + valid: true, + parsedPairs: [ + { marker1: 'q', marker2: 'q1' }, + { marker1: 'q', marker2: 'q2' }, + ], + }); + }); + + it('TS-018: invalid pair rejected, NOT silently skipped (contract divergence from runtime parser)', () => { + // Pins the divergence: initializeMarkerMappings silently drops "invalid" and "p/q1/q2" + // (VAL-005), but the strict parser used here REJECTS the entire input on first malformed + // token (VAL-002). + expect(parseMarkerSettings('p/q invalid p/q1/q2 good/bad')).toEqual({ + valid: false, + errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY, + }); + }); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/parse-marker-settings.ts b/extensions/src/platform-scripture/src/checklists/parse-marker-settings.ts new file mode 100644 index 00000000000..cc983e7bada --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/parse-marker-settings.ts @@ -0,0 +1,77 @@ +import type { LocalizeKey } from 'platform-bible-utils'; + +/** + * Localize key emitted in `errorMessageKey` when validation fails. The wire boundary is responsible + * for resolving this key to a localized string before display. + * + * Mirrors C# `MarkersDataSource.InvalidMarkerPairErrorKey`. Maps to PT9 `MarkerSettingsForm_1`. + * Translations live in `extensions/src/platform-scripture/contributions/localizedStrings.json`. + */ +export const INVALID_MARKER_PAIR_ERROR_KEY: LocalizeKey = + '%markersChecklist_errorInvalidMarkerPair%'; + +/** A single equivalent-markers pair: `"marker1/marker2"`. */ +export type MarkerPair = { marker1: string; marker2: string }; + +/** + * Discriminated-union result of parsing the equivalent-markers string. The structural invariant + * (§3.13 mutex) is enforced by narrowing on `valid`: the success branch exposes `parsedPairs`, the + * failure branch exposes `errorMessageKey`, and neither field is reachable on the other branch. + */ +export type ParseMarkerSettingsResult = + | { valid: true; parsedPairs: MarkerPair[] } + | { valid: false; errorMessageKey: LocalizeKey }; + +/** + * Parses a single `"marker1/marker2"` token. Returns the pair when the token is well-formed + * (exactly one slash, both sides non-empty after trim), or `undefined` when malformed. + */ +function parsePairToken(token: string): MarkerPair | undefined { + const items = token.split('/'); + if (items.length !== 2) return undefined; + const marker1 = items[0].trim(); + const marker2 = items[1].trim(); + if (marker1.length === 0 || marker2.length === 0) return undefined; + return { marker1, marker2 }; +} + +/** + * Strict pre-commit parser for the equivalent-markers settings string. Used by the Marker Settings + * dialog for live keystroke feedback AND by the `platformScripture.checklistEquivalentMarkers` + * project-setting validator. + * + * Format: zero or more space-separated `marker1/marker2` pairs. Empty / null / whitespace-only + * inputs are valid with an empty pair list. On the FIRST malformed token, validation fails fast + * with no partial-parse leakage (§3.13 mutex invariant: valid=true ⇔ parsedPairs populated; + * valid=false ⇔ errorMessageKey populated). + * + * Port of PT9 `MarkerSettingsForm.btnOk_Click` (Paratext/Checklists/MarkerSettingsForm.cs:28-49) + * via C# `MarkersDataSource.ValidateMarkerSettings`. Distinct from `initializeMarkerMappings` in + * `match-detection.ts`, which is the LENIENT runtime parser that silently skips invalid tokens + * (VAL-005). + */ +export function parseMarkerSettings(input: string): ParseMarkerSettingsResult { + // PT9 line 30: null coerces to empty. + const safeInput = input ?? ''; + + // PT9 line 31: trim + collapse multiple spaces to single space (no culture sensitivity — + // matches C# `Regex.Replace(equivalents.Trim(), " +", " ")`). + const normalized = safeInput.trim().replace(/ +/g, ' '); + + // PT9 line 32: empty after normalization → valid with zero pairs. + if (normalized.length === 0) return { valid: true, parsedPairs: [] }; + + // PT9 lines 34-43: parse each token; the first malformed one rejects the entire input. The + // `.map` + `.find` form is equivalent to fail-fast: we map every token to a Maybe, then + // bail if any are `undefined`. §3.13 forbids partial-parse leak, so the failure branch returns + // only `errorMessageKey` (no parsedPairs). + const parsedTokens = normalized.split(' ').map(parsePairToken); + if (parsedTokens.some((pair) => pair === undefined)) { + return { valid: false, errorMessageKey: INVALID_MARKER_PAIR_ERROR_KEY }; + } + + // All tokens parsed; narrow undefined-bearing union to a concrete MarkerPair[]. The filter + // condition is identical to the `some` check above, so TS narrows away the undefined arm. + const parsedPairs = parsedTokens.filter((pair): pair is MarkerPair => pair !== undefined); + return { valid: true, parsedPairs }; +} diff --git a/extensions/src/platform-scripture/src/main.ts b/extensions/src/platform-scripture/src/main.ts index dec40e932eb..dc8e9736265 100644 --- a/extensions/src/platform-scripture/src/main.ts +++ b/extensions/src/platform-scripture/src/main.ts @@ -35,6 +35,7 @@ import { } from './project-data-provider/platform-scripture-finder.pdpef.model'; import { SCRIPTURE_FINDER_PROJECT_INTERFACES } from './project-data-provider/platform-scripture-finder-pdpe.model'; import { resourceReferenceListValidator } from './resource-reference-list.utils'; +import { parseMarkerSettings } from './checklists/parse-marker-settings'; const characterInventoryWebViewType = 'platformScripture.characterInventory'; const repeatedWordsInventoryWebViewType = 'platformScripture.repeatedWordsInventory'; @@ -81,11 +82,14 @@ const punctuationValidator: ProjectSettingValidator< > = async (newValue) => typeof newValue === 'string'; // Equivalent-markers validator: delegates to the pure-TS parseMarkerSettings. -// Until Phase 3 lands, this is a stub that accepts any string; Phase 3 Task 3.1 -// will replace this with the real strict-parse check. +// `parseMarkerSettings` enforces VAL-002 (strict pre-commit format check). The marker filter +// uses a lenient validator below because the runtime parser silently skips malformed tokens. const checklistEquivalentMarkersValidator: ProjectSettingValidator< 'platformScripture.checklistEquivalentMarkers' -> = async (newValue) => typeof newValue === 'string'; +> = async (newValue) => { + if (typeof newValue !== 'string') return false; + return parseMarkerSettings(newValue).valid; +}; // Marker filter: plain string check; the runtime parser is tolerant. const checklistMarkerFilterValidator: ProjectSettingValidator< From 4bec91cb6a908a512a30e64440d287fcb30fe062 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:05:46 +0200 Subject: [PATCH 10/43] feat(markers-checklist): match-detection.ts + shared checklist.types.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initializeMarkerMappings is the lenient runtime parser (VAL-005 — silently skips invalid tokens). hasSameValue implements INV-002 / BHV-104 / INV-005 (bidirectional marker equivalence). Test cases ported from MarkersDataSourceTests.cs. --- .../src/checklists/checklist.types.ts | 98 +++++++++++++ .../src/checklists/match-detection.test.ts | 134 ++++++++++++++++++ .../src/checklists/match-detection.ts | 85 +++++++++++ 3 files changed, 317 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/checklist.types.ts create mode 100644 extensions/src/platform-scripture/src/checklists/match-detection.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/match-detection.ts diff --git a/extensions/src/platform-scripture/src/checklists/checklist.types.ts b/extensions/src/platform-scripture/src/checklists/checklist.types.ts new file mode 100644 index 00000000000..bbb77f55499 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/checklist.types.ts @@ -0,0 +1,98 @@ +import type { SerializedVerseRef } from '@sillsdev/scripture'; +import type { ScriptureRange } from 'platform-scripture'; +import type { LocalizeKey } from 'platform-bible-utils'; + +/** + * Discriminated-union content item. Mirrors the C# `ChecklistContentItem` hierarchy in + * `c-sharp/Checklists/ChecklistContentItem.cs`. Structurally aligned with the presentational + * component's `components/checklist.types.ts` (TextItem uses `characterStyle`); this module is the + * orchestrator's internal shape, independent from the component's frozen one. Phase 6 will wire a + * `toChecklistData` adapter between them. + */ +export type ChecklistContentItem = + | TextItem + | VerseItem + | EditLinkItem + | LinkItem + | ErrorItem + | MessageItem; + +export type TextItem = { + type: 'text'; + text: string; + /** + * Character marker name (e.g. `ior`, `f`) when this text fragment sits inside a `\marker ... + * \marker*` span; `undefined` otherwise. Mirrors C# `TextItem.CharacterStyle` (which is `string?` + * — nullable). The wire emits `null` for empty; the orchestrator can normalize at the + * serialization boundary. + */ + characterStyle: string | undefined; +}; + +export type VerseItem = { type: 'verse'; verseNumber: string }; + +export type EditLinkItem = { + type: 'editLink'; + bookNum: number; + chapterNum: number; + verseNum: number; +}; + +/** Reserved — not emitted by the Markers checklist branch; kept for wire-shape parity. */ +export type LinkItem = { type: 'link'; reference: ScriptureRange; displayText: string }; + +export type ErrorItem = { type: 'error'; message: string }; + +export type MessageItem = { type: 'message'; message: string }; + +/** USFM paragraph in a cell — wire shape (no walker-internal `verseRefStart` / `isHeading`). */ +export type ChecklistParagraph = { + /** USFM marker name without leading backslash (e.g. `p`, `q1`). */ + marker: string; + items: ChecklistContentItem[]; +}; + +/** + * Walker-internal paragraph carrying orchestration metadata (`verseRefStart`, `isHeading`) used by + * the orchestrator to group paragraphs into cells. NOT serialized to the wire — the orchestrator + * converts `WalkerParagraph[]` → `ChecklistParagraph[]` by dropping these fields before populating + * cells. + */ +export type WalkerParagraph = { + marker: string; + verseRefStart: SerializedVerseRef; + isHeading: boolean; + items: ChecklistContentItem[]; +}; + +export type ChecklistCell = { + paragraphs: ChecklistParagraph[]; + /** Bridge-capable verse reference; `undefined` for an empty-placeholder cell. */ + reference: ScriptureRange | undefined; + /** Language tag for the cell's content (from the `GetJoinedTextLanguage` PDP). */ + language: string; + /** Cell-level error message (overrides paragraph rendering when present). */ + error: string | undefined; +}; + +export type ChecklistRow = { + cells: ChecklistCell[]; + isMatch: boolean; + includeEditLink: boolean; + /** Always 0 for Markers checklist (the CrossRef checklist uses non-zero scores). */ + score: number; + /** Earliest verse reference across populated cells; `undefined` when none carries a ref. */ + firstRef: ScriptureRange | undefined; +}; + +/** + * Empty-result message. The wire emits `message` as a localize KEY (e.g. + * `%markersChecklist_emptyResult_identicalMarkers%`); the React layer resolves at render-time. The + * `noResults` variant carries arrays for the localized template substitution. + */ +export type ChecklistEmptyResultMessage = { + variant: 'identical' | 'noResults'; + message: LocalizeKey | string; + searchedMarkers: string[] | undefined; + searchedBooks: string[] | undefined; +}; diff --git a/extensions/src/platform-scripture/src/checklists/match-detection.test.ts b/extensions/src/platform-scripture/src/checklists/match-detection.test.ts new file mode 100644 index 00000000000..72b4ef43b21 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/match-detection.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from 'vitest'; +import { hasSameValue, initializeMarkerMappings } from './match-detection'; +import type { ChecklistCell, ChecklistRow } from './checklist.types'; + +/** + * Tests ported from `c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs` — the + * `HasSameValue` and `InitializeMarkerMappings` sections. The TS port surfaces the bidirectional + * map as `ReadonlyMap>` (instead of C#'s `IReadOnlyDictionary>`) — same lookup semantics, different concrete type. + */ + +/** Builds a row containing the given cells. Convenience for hasSameValue tests. */ +function makeRow(cells: ChecklistCell[]): ChecklistRow { + return { cells, isMatch: false, includeEditLink: false, score: 0, firstRef: undefined }; +} + +/** Builds a cell with the given paragraph markers (no content items). */ +function makeCell(markers: string[]): ChecklistCell { + return { + paragraphs: markers.map((marker) => ({ marker, items: [] })), + reference: undefined, + language: 'en', + error: undefined, + }; +} + +describe('initializeMarkerMappings', () => { + it('empty input produces empty map', () => { + expect(initializeMarkerMappings('').size).toBe(0); + }); + + it('whitespace-only input produces empty map', () => { + expect(initializeMarkerMappings(' ').size).toBe(0); + }); + + it('null-ish input produces empty map (defensive — mirrors C# `?? ""` coercion)', () => { + // The TS signature is `string`, but C# coerces null to empty via `?? ""`. Build the null at + // runtime so the file stays free of literal `null`s. + // eslint-disable-next-line no-type-assertion/no-type-assertion -- defensive nullish check + const nullishInput = JSON.parse('null') as unknown as string; + expect(initializeMarkerMappings(nullishInput).size).toBe(0); + }); + + it('TS-016: "p/q q1/q2" stores both directions for both pairs (INV-005 bidirectional)', () => { + const map = initializeMarkerMappings('p/q q1/q2'); + expect(map.get('p')).toEqual(new Set(['q'])); + expect(map.get('q')).toEqual(new Set(['p'])); + expect(map.get('q1')).toEqual(new Set(['q2'])); + expect(map.get('q2')).toEqual(new Set(['q1'])); + }); + + it('TS-017: "q/q1 q/q2" accumulates [q1, q2] under "q"', () => { + const map = initializeMarkerMappings('q/q1 q/q2'); + expect(map.get('q')).toEqual(new Set(['q1', 'q2'])); + expect(map.get('q1')).toEqual(new Set(['q'])); + expect(map.get('q2')).toEqual(new Set(['q'])); + }); + + it('TS-018 / VAL-005: invalid tokens are silently skipped (lenient runtime parser)', () => { + // C# `ParseEquivalentMarkerMappings` skips tokens whose split('/') length is not exactly 2. + // 'invalid' (zero slashes) and 'p/q1/q2' (two slashes) are skipped; 'p/q' and 'good/bad' + // remain. The 'q1'/'q2' from the skipped 3-part token must NOT have leaked through. + const map = initializeMarkerMappings('p/q invalid p/q1/q2 good/bad'); + expect(map.get('p')).toEqual(new Set(['q'])); + expect(map.get('q')).toEqual(new Set(['p'])); + expect(map.get('good')).toEqual(new Set(['bad'])); + expect(map.get('bad')).toEqual(new Set(['good'])); + expect(map.has('invalid')).toBe(false); + // q1 must not link to q2 via the skipped 3-part token. + expect(map.get('q1')?.has('q2') ?? false).toBe(false); + }); +}); + +describe('hasSameValue', () => { + it('TS-011: identical-marker pair across two cells matches (no mappings needed)', () => { + const row = makeRow([makeCell(['p']), makeCell(['p'])]); + expect(hasSameValue(row, new Map())).toBe(true); + }); + + it('TS-012: different markers without mapping do not match', () => { + const row = makeRow([makeCell(['p']), makeCell(['q'])]); + expect(hasSameValue(row, new Map())).toBe(false); + }); + + it('TS-013: bidirectional mapping (forward direction) makes mapped markers equivalent', () => { + const row = makeRow([makeCell(['p']), makeCell(['q'])]); + const mappings = initializeMarkerMappings('p/q'); + expect(hasSameValue(row, mappings)).toBe(true); + }); + + it('TS-013 (INV-005 reverse): reverse direction also matches via the bidirectional map', () => { + // INV-005 CRITICAL: the help docs imply directional mapping (first-text/second-text), but the + // code stores BOTH directions. Cells (q, p) with mapping "p/q" must match. + const row = makeRow([makeCell(['q']), makeCell(['p'])]); + const mappings = initializeMarkerMappings('p/q'); + expect(hasSameValue(row, mappings)).toBe(true); + }); + + it('TS-014: partial mapping fails when unmapped markers differ', () => { + // cell1=[p, q1], cell2=[q, q2], mapping only has p<->q. q1/q2 are not mapped. + const row = makeRow([makeCell(['p', 'q1']), makeCell(['q', 'q2'])]); + const mappings = initializeMarkerMappings('p/q'); + expect(hasSameValue(row, mappings)).toBe(false); + }); + + it('TS-015: paragraph-count mismatch across cells returns false', () => { + const row = makeRow([makeCell(['p', 'q1']), makeCell(['p'])]); + expect(hasSameValue(row, new Map())).toBe(false); + }); + + it('TS-065: three-cell pairwise comparison — (0,1) match, (1,2) differ → row not a match', () => { + const row = makeRow([makeCell(['p']), makeCell(['p']), makeCell(['q'])]); + expect(hasSameValue(row, new Map())).toBe(false); + }); + + it('three-cell row with all identical markers matches', () => { + const row = makeRow([makeCell(['p']), makeCell(['p']), makeCell(['p'])]); + expect(hasSameValue(row, new Map())).toBe(true); + }); + + it('TS-066: empty cell vs populated cell → paragraph count mismatch → false', () => { + const row = makeRow([makeCell(['p']), makeCell([])]); + expect(hasSameValue(row, new Map())).toBe(false); + }); + + it('PT9 :230 — single-cell row is never a "match" (nothing to compare against)', () => { + const row = makeRow([makeCell(['p'])]); + expect(hasSameValue(row, new Map())).toBe(false); + }); + + it('zero-cell row is never a "match"', () => { + expect(hasSameValue(makeRow([]), new Map())).toBe(false); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/match-detection.ts b/extensions/src/platform-scripture/src/checklists/match-detection.ts new file mode 100644 index 00000000000..a0c87f55ec4 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/match-detection.ts @@ -0,0 +1,85 @@ +import type { ChecklistRow } from './checklist.types'; + +/** + * Lenient runtime parser for the equivalent-markers string. Silently skips invalid tokens (VAL-005) + * to preserve runtime robustness — a corrupted settings file must not crash the orchestrator + * mid-build. Distinct from `parseMarkerSettings`, which is the strict pre-commit UI parser. + * + * Returns a bidirectional mapping: every pair `a/b` stores both `a → {b}` and `b → {a}` (INV-005). + * When the same left-hand marker appears in multiple pairs (e.g. `"q/q1 q/q2"`), the values + * accumulate into the set. + * + * Port of C# `MarkersDataSource.ParseEquivalentMarkerMappings` + * (`c-sharp/Checklists/Markers/MarkersDataSource.cs:218-240`). + */ +export function initializeMarkerMappings(input: string): ReadonlyMap> { + const safeInput = input ?? ''; + const normalized = safeInput.trim().replace(/ +/g, ' '); + const result = new Map>(); + if (normalized.length === 0) return result; + + normalized.split(' ').forEach((token) => { + const items = token.split('/'); + if (items.length !== 2) return; // VAL-005 — silently skip invalid token. + const a = items[0].trim(); + const b = items[1].trim(); + if (a.length === 0 || b.length === 0) return; // VAL-005 — empty-side tokens also skipped. + addBidirectional(result, a, b); + }); + + return result; +} + +/** Inserts both `a → b` and `b → a` into the bidirectional map (INV-005). */ +function addBidirectional(map: Map>, a: string, b: string): void { + const aSet = map.get(a) ?? new Set(); + aSet.add(b); + map.set(a, aSet); + const bSet = map.get(b) ?? new Set(); + bSet.add(a); + map.set(b, bSet); +} + +/** + * INV-002 / BHV-104: returns true when every adjacent pair of cells in the row has equal paragraph + * count AND every paragraph is equivalent (identical marker OR mapped via the INV-005 bidirectional + * mapping). + * + * Single-cell (and zero-cell) rows are never a "match" — there's nothing to compare against (PT9 + * line 230). + * + * Port of C# `MarkersDataSource.HasSameValue` + * (`c-sharp/Checklists/Markers/MarkersDataSource.cs:93-124`). + */ +export function hasSameValue( + row: ChecklistRow, + markerMappings: ReadonlyMap>, +): boolean { + if (row.cells.length <= 1) return false; + + return row.cells.slice(0, -1).every((cell, index) => { + const nextCell = row.cells[index + 1]; + if (cell.paragraphs.length !== nextCell.paragraphs.length) return false; + return cell.paragraphs.every((paragraph, paragraphIndex) => + isEquivalentMarker( + paragraph.marker, + nextCell.paragraphs[paragraphIndex].marker, + markerMappings, + ), + ); + }); +} + +/** + * Returns true when `a` and `b` are equal, OR when the forward mapping edge `a → b` is present. + * Bidirectionality (INV-005) is guaranteed by `initializeMarkerMappings` storing both edges at + * parse time. + */ +function isEquivalentMarker( + a: string, + b: string, + mappings: ReadonlyMap>, +): boolean { + if (a === b) return true; + return mappings.get(a)?.has(b) ?? false; +} From 2c1938c65584f0d3c02a5d271b3a2547e10f0b4e Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:07:35 +0200 Subject: [PATCH 11/43] feat(markers-checklist): post-process-paragraph.ts (port from MarkersDataSource.PostProcessParagraph) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INV-004 (prepend backslash-marker TextItem) + BHV-103 (drop body items when showVerseText=false). RTL prefix is applied by the upstream walker on original text items, not by this module — the synthetic \\marker label is never RTL-prefixed, matching C# MarkersDataSource.PostProcessParagraph. Pure function — never mutates input. --- .../checklists/post-process-paragraph.test.ts | 145 ++++++++++++++++++ .../src/checklists/post-process-paragraph.ts | 41 +++++ 2 files changed, 186 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/post-process-paragraph.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/post-process-paragraph.ts diff --git a/extensions/src/platform-scripture/src/checklists/post-process-paragraph.test.ts b/extensions/src/platform-scripture/src/checklists/post-process-paragraph.test.ts new file mode 100644 index 00000000000..f870076b3ac --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/post-process-paragraph.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, it } from 'vitest'; +import { postProcessParagraph, RTL_MARKER } from './post-process-paragraph'; +import type { ChecklistContentItem, WalkerParagraph } from './checklist.types'; + +/** + * Tests ported from `c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs` — + * `PostProcessParagraph` section (TS-009, TS-010, TS-067). The C# `PostProcessParagraph` + * (`c-sharp/Checklists/Markers/MarkersDataSource.cs:67-79`) takes a `ChecklistParagraph` and a + * `showVerseText` flag; the TS version additionally takes per-cell `language` and `rtl` (the + * upstream walker applies the RTL prefix to text items it emits, but this module is exercised in + * the orchestrator boundary where the RTL flag is threaded explicitly). + * + * RTL behavior: per `c-sharp/Checklists/ChecklistService.cs:1088`, RTL prefix is applied to + * ORIGINAL text items only by the walker, NOT to the synthetic `\marker` label added by + * `PostProcessParagraph`. This module follows the same contract: text items in the input have + * already been RTL-prefixed by the walker; PostProcessParagraph does NOT re-prefix them and does + * NOT prefix the synthetic `\marker` label. + */ + +function makeParagraph(marker: string, items: ChecklistContentItem[]): WalkerParagraph { + return { + marker, + verseRefStart: { book: 'GEN', chapterNum: 1, verseNum: 1 }, + isHeading: false, + items, + }; +} + +describe('postProcessParagraph', () => { + describe('INV-004 — prepend \\marker text item at index 0', () => { + it('TS-009: showVerseText=false clears items and inserts marker-only', () => { + // C# TS-009: input items = [TextItem("verse text here"), TextItem("more text")]; with + // showVerseText=false, the result must be a single TextItem("\\p"). + const input = makeParagraph('p', [ + { type: 'text', text: 'verse text here', characterStyle: undefined }, + { type: 'text', text: 'more text', characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: false }); + expect(result.marker).toBe('p'); + expect(result.items).toEqual([{ type: 'text', text: '\\p', characterStyle: undefined }]); + }); + + it('TS-010: showVerseText=true prepends marker before original items', () => { + // C# TS-010: input items = [TextItem("indented "), TextItem("poetry")]; with + // showVerseText=true, result must be [TextItem("\\q2"), TextItem("indented "), TextItem("poetry")]. + const input = makeParagraph('q2', [ + { type: 'text', text: 'indented ', characterStyle: undefined }, + { type: 'text', text: 'poetry', characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: true }); + expect(result.items).toEqual([ + { type: 'text', text: '\\q2', characterStyle: undefined }, + { type: 'text', text: 'indented ', characterStyle: undefined }, + { type: 'text', text: 'poetry', characterStyle: undefined }, + ]); + }); + + it('TS-067: q1 marker with showVerseText=false displays exactly "\\q1"', () => { + const input = makeParagraph('q1', [ + { type: 'text', text: 'some content', characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: false }); + expect(result.items).toEqual([{ type: 'text', text: '\\q1', characterStyle: undefined }]); + }); + }); + + describe('non-text items', () => { + it('preserves VerseItem in the items list when showVerseText=true', () => { + const input = makeParagraph('p', [ + { type: 'verse', verseNumber: '1' }, + { type: 'text', text: 'In the beginning', characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: true }); + expect(result.items).toEqual([ + { type: 'text', text: '\\p', characterStyle: undefined }, + { type: 'verse', verseNumber: '1' }, + { type: 'text', text: 'In the beginning', characterStyle: undefined }, + ]); + }); + + it('drops VerseItem (and all non-marker items) when showVerseText=false', () => { + const input = makeParagraph('p', [ + { type: 'verse', verseNumber: '1' }, + { type: 'text', text: 'In the beginning', characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: false }); + expect(result.items).toEqual([{ type: 'text', text: '\\p', characterStyle: undefined }]); + }); + }); + + describe('RTL handling — synthetic marker label is NOT RTL-prefixed', () => { + it('does NOT add the RTL prefix to the synthetic "\\p" label', () => { + // Per c-sharp/Checklists/ChecklistService.cs:1088, the walker applies RTL prefix to original + // text items at extraction time. PostProcessParagraph does NOT touch the items' RTL status + // and does NOT prefix the synthetic \marker label. (C# MarkersDataSource.PostProcessParagraph + // at MarkersDataSource.cs:67-79 inserts `new TextItem("\\" + paragraph.Marker, null)` with no + // RTL handling.) + const rtlTextFromWalker = `${RTL_MARKER}שלום`; + const input = makeParagraph('p', [ + { type: 'text', text: rtlTextFromWalker, characterStyle: undefined }, + ]); + const result = postProcessParagraph(input, { showVerseText: true }); + + // Synthetic marker label is not RTL-prefixed. + expect(result.items[0]).toEqual({ type: 'text', text: '\\p', characterStyle: undefined }); + + // Original text item passes through unchanged (RTL prefix is preserved as-emitted by the + // walker). + expect(result.items[1]).toEqual({ + type: 'text', + text: rtlTextFromWalker, + characterStyle: undefined, + }); + }); + }); + + describe('paragraph metadata preservation', () => { + it('preserves marker / verseRefStart / isHeading fields on the output paragraph', () => { + const input: WalkerParagraph = { + marker: 's', + verseRefStart: { book: 'GEN', chapterNum: 2, verseNum: 4 }, + isHeading: true, + items: [{ type: 'text', text: 'Heading text', characterStyle: undefined }], + }; + const result = postProcessParagraph(input, { showVerseText: true }); + expect(result.marker).toBe('s'); + expect(result.verseRefStart).toEqual({ book: 'GEN', chapterNum: 2, verseNum: 4 }); + expect(result.isHeading).toBe(true); + }); + }); + + describe('immutability', () => { + it('returns a new paragraph object — does not mutate the input items array', () => { + const originalItems: ChecklistContentItem[] = [ + { type: 'text', text: 'hello', characterStyle: undefined }, + ]; + const input = makeParagraph('p', originalItems); + postProcessParagraph(input, { showVerseText: true }); + // Input items list reference and length are unchanged. + expect(input.items).toBe(originalItems); + expect(input.items).toHaveLength(1); + expect(input.items[0]).toEqual({ type: 'text', text: 'hello', characterStyle: undefined }); + }); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/post-process-paragraph.ts b/extensions/src/platform-scripture/src/checklists/post-process-paragraph.ts new file mode 100644 index 00000000000..6521203e448 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/post-process-paragraph.ts @@ -0,0 +1,41 @@ +import type { ChecklistContentItem, WalkerParagraph } from './checklist.types'; + +/** + * U+202B — Right-to-Left Embedding character. Mirrors libpalaso `StringUtils.rtlMarker`. Exported + * for the USJ-walker phase, which applies it to text items at extraction time + * (`c-sharp/Checklists/ChecklistService.cs:1088`). `postProcessParagraph` does NOT use this + * directly — the walker has already prefixed any RTL text by the time items arrive here. + */ +export const RTL_MARKER = '‫'; + +/** + * Per-paragraph post-processor. Two behaviors (BHV-103, INV-004): + * + * 1. Prepend a synthetic `TextItem` containing `\marker` at index 0 (INV-004). + * 2. When `showVerseText` is false, drop the rest of the items (only the marker label remains). + * + * RTL handling: per `c-sharp/Checklists/ChecklistService.cs:1088`, the upstream walker applies the + * RTL prefix to original text items at extraction time. `postProcessParagraph` does NOT re-prefix + * those items, and the synthetic `\marker` label is NEVER RTL-prefixed. (C# + * `MarkersDataSource.PostProcessParagraph` at MarkersDataSource.cs:67-79 inserts a + * backslash-prefixed TextItem with no RTL handling.) + * + * Returns a new paragraph; never mutates the input. + * + * Port of C# `MarkersDataSource.PostProcessParagraph` + * (`c-sharp/Checklists/Markers/MarkersDataSource.cs:67-79`). + */ +export function postProcessParagraph( + paragraph: WalkerParagraph, + options: { showVerseText: boolean }, +): WalkerParagraph { + const markerLabel: ChecklistContentItem = { + type: 'text', + text: `\\${paragraph.marker}`, + characterStyle: undefined, + }; + const newItems: ChecklistContentItem[] = options.showVerseText + ? [markerLabel, ...paragraph.items] + : [markerLabel]; + return { ...paragraph, items: newItems }; +} From 0332e3955c0316c856a2b785617c7bd9dc4eb773 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:09:38 +0200 Subject: [PATCH 12/43] feat(markers-checklist): checklist-row-builder.ts (port from ChecklistRowBuilder.BuildRowsMergingCells) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simple column-zip aligner. Per working-docs/tj-review-consumer-inventory.md § 6, verse-bridge expansion and MaxCellsToGrab capping are NOT implemented in this module — they happen upstream in buildChecklistData (Phase 5) by pre-grouping per-verseRef cells. This module just lines up cells across columns by their reference and emits empty placeholders where a column lacks coverage. --- .../checklists/checklist-row-builder.test.ts | 127 ++++++++++++++++++ .../src/checklists/checklist-row-builder.ts | 93 +++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/checklist-row-builder.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/checklist-row-builder.ts diff --git a/extensions/src/platform-scripture/src/checklists/checklist-row-builder.test.ts b/extensions/src/platform-scripture/src/checklists/checklist-row-builder.test.ts new file mode 100644 index 00000000000..e9bf1dba551 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/checklist-row-builder.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from 'vitest'; +import type { SerializedVerseRef } from '@sillsdev/scripture'; +import { buildRowsMergingCells } from './checklist-row-builder'; +import type { ChecklistCell, ChecklistParagraph } from './checklist.types'; + +/** + * Per `working-docs/tj-review-consumer-inventory.md` § 6, this module is a SIMPLE column-zip + * aligner. It assumes the caller has already grouped per-column paragraphs into cells (one cell per + * distinct verseRef per column). Verse-bridge expansion and MaxCellsToGrab capping happen upstream + * in `buildChecklistData` (Phase 5). + * + * Tests exercise the simple alignment cases. Behavior-parity for verse bridges is gated by Phase 7 + * baseline diffs against `working-docs/tj-review-baseline/`. + */ + +function ref(book: string, chapterNum: number, verseNum: number): SerializedVerseRef { + return { book, chapterNum, verseNum }; +} + +function makeCell( + markers: string[], + reference: { start: SerializedVerseRef; end?: SerializedVerseRef } | undefined, +): ChecklistCell { + const paragraphs: ChecklistParagraph[] = markers.map((marker) => ({ marker, items: [] })); + return { paragraphs, reference, language: 'en', error: undefined }; +} + +describe('buildRowsMergingCells', () => { + it('empty input produces empty output', () => { + expect(buildRowsMergingCells([])).toEqual([]); + }); + + it('single column with one cell produces one row with that cell', () => { + const columns = [[makeCell(['p'], { start: ref('GEN', 1, 1) })]]; + const rows = buildRowsMergingCells(columns); + expect(rows).toHaveLength(1); + expect(rows[0].cells).toHaveLength(1); + expect(rows[0].cells[0].paragraphs[0].marker).toBe('p'); + expect(rows[0].firstRef).toEqual({ start: ref('GEN', 1, 1) }); + }); + + it('two parallel columns with same verseRef produce one row with both cells', () => { + const columns = [ + [makeCell(['p'], { start: ref('GEN', 1, 1) })], + [makeCell(['q'], { start: ref('GEN', 1, 1) })], + ]; + const rows = buildRowsMergingCells(columns); + expect(rows).toHaveLength(1); + expect(rows[0].cells).toHaveLength(2); + expect(rows[0].cells[0].paragraphs[0].marker).toBe('p'); + expect(rows[0].cells[1].paragraphs[0].marker).toBe('q'); + }); + + it('mismatched verseRefs across columns emit empty-placeholder cells', () => { + // Column 0 has GEN 1:1; column 1 has GEN 1:2. Result: row 0 = [(p), empty], row 1 = [empty, (q)]. + const columns = [ + [makeCell(['p'], { start: ref('GEN', 1, 1) })], + [makeCell(['q'], { start: ref('GEN', 1, 2) })], + ]; + const rows = buildRowsMergingCells(columns); + expect(rows).toHaveLength(2); + expect(rows[0].cells[0].paragraphs[0].marker).toBe('p'); + expect(rows[0].cells[1].paragraphs).toHaveLength(0); + expect(rows[0].cells[1].reference).toBeUndefined(); + expect(rows[1].cells[0].paragraphs).toHaveLength(0); + expect(rows[1].cells[1].paragraphs[0].marker).toBe('q'); + }); + + it('multiple paragraphs in one cell are preserved as one row cell (upstream-merged)', () => { + // Phase 5's buildChecklistData is responsible for grouping same-verseRef paragraphs into a + // single cell; the row builder just passes them through. (Canary: scenario-01 row 0 has 24 + // paragraphs in one cell.) + const cell = makeCell(['p', 'q', 's'], { start: ref('GEN', 1, 0) }); + const rows = buildRowsMergingCells([[cell]]); + expect(rows).toHaveLength(1); + expect(rows[0].cells[0].paragraphs).toHaveLength(3); + expect(rows[0].cells[0].paragraphs.map((p) => p.marker)).toEqual(['p', 'q', 's']); + }); + + it('aligns multi-row sequences in order', () => { + const columns = [ + [makeCell(['p'], { start: ref('GEN', 1, 1) }), makeCell(['q'], { start: ref('GEN', 1, 2) })], + [makeCell(['p'], { start: ref('GEN', 1, 1) }), makeCell(['q'], { start: ref('GEN', 1, 2) })], + ]; + const rows = buildRowsMergingCells(columns); + expect(rows).toHaveLength(2); + expect(rows[0].firstRef).toEqual({ start: ref('GEN', 1, 1) }); + expect(rows[1].firstRef).toEqual({ start: ref('GEN', 1, 2) }); + }); + + it('shorter column produces trailing empty-placeholder cells', () => { + const columns = [ + [makeCell(['p'], { start: ref('GEN', 1, 1) }), makeCell(['q'], { start: ref('GEN', 1, 2) })], + [makeCell(['p'], { start: ref('GEN', 1, 1) })], + ]; + const rows = buildRowsMergingCells(columns); + expect(rows).toHaveLength(2); + expect(rows[0].cells[0].paragraphs[0].marker).toBe('p'); + expect(rows[0].cells[1].paragraphs[0].marker).toBe('p'); + expect(rows[1].cells[0].paragraphs[0].marker).toBe('q'); + expect(rows[1].cells[1].paragraphs).toHaveLength(0); + }); + + it('row emitted with isMatch=false and includeEditLink=false (populated downstream)', () => { + // Match-detection and edit-link gating happen in buildChecklistData; the row builder emits + // safe defaults. + const rows = buildRowsMergingCells([[makeCell(['p'], { start: ref('GEN', 1, 1) })]]); + expect(rows[0].isMatch).toBe(false); + expect(rows[0].includeEditLink).toBe(false); + expect(rows[0].score).toBe(0); + }); + + it('orders by book then chapter then verse', () => { + // Multiple books: GEN before EXO. + const columns = [ + [ + makeCell(['p'], { start: ref('EXO', 1, 1) }), + makeCell(['p'], { start: ref('GEN', 2, 1) }), + makeCell(['p'], { start: ref('GEN', 1, 1) }), + ], + ]; + const rows = buildRowsMergingCells(columns); + // Single column means rows come out in column order; verify the builder doesn't reshuffle a + // single column. + expect(rows.map((r) => r.firstRef?.start.book)).toEqual(['EXO', 'GEN', 'GEN']); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/checklist-row-builder.ts b/extensions/src/platform-scripture/src/checklists/checklist-row-builder.ts new file mode 100644 index 00000000000..f3f50676562 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/checklist-row-builder.ts @@ -0,0 +1,93 @@ +import type { SerializedVerseRef } from '@sillsdev/scripture'; +import type { ScriptureRange } from 'platform-scripture'; +import type { ChecklistCell, ChecklistRow } from './checklist.types'; + +/** + * Aligns per-column already-merged cells into rows. Each input cell is assumed to carry all + * paragraphs at one distinct `verseRef` in its column (Phase 5's `buildChecklistData` performs the + * per-verseRef grouping upstream). This module is a simple column-zip aligner: + * + * 1. For each step, find the minimum starting verseRef across non-empty column heads. + * 2. For each column whose head matches that minimum, consume it into the row; otherwise emit an + * empty-placeholder cell for that column. + * 3. Repeat until every column is exhausted. + * + * Per `working-docs/tj-review-consumer-inventory.md` § 6, verse-bridge expansion and + * `MaxCellsToGrab` capping are NOT implemented here. If Phase 7 baseline parity reveals scenarios + * that need them, escalate. + * + * Match detection (`isMatch`), edit-link gating (`includeEditLink`), and scoring are populated by + * the orchestrator after row alignment. + * + * Port of (a simplified subset of) `c-sharp/Checklists/ChecklistRowBuilder.cs`. + */ +export function buildRowsMergingCells(columnsCells: ChecklistCell[][]): ChecklistRow[] { + if (columnsCells.length === 0) return []; + + const columnQueues: ChecklistCell[][] = columnsCells.map((cells) => [...cells]); + const rows: ChecklistRow[] = []; + + while (columnQueues.some((queue) => queue.length > 0)) { + const headsWithIndex = columnQueues + .map((queue, columnIndex) => ({ head: queue[0], columnIndex })) + .filter((entry): entry is { head: ChecklistCell; columnIndex: number } => + Boolean(entry.head), + ); + if (headsWithIndex.length === 0) break; + + // Find the column whose head has the smallest reference. In a multi-column layout this is the + // "current" verseRef to align this row to; in a single-column layout this simply consumes the + // first head. + const minEntry = headsWithIndex.reduce((min, entry) => + compareReference(entry.head.reference, min.head.reference) < 0 ? entry : min, + ); + const targetRef = minEntry.head.reference; + + // For each column: if the head matches the target ref, consume it; otherwise emit an empty + // placeholder. When there are no references on any heads (all undefined), only the first + // matching column consumes; this collapses degenerate "no-ref" rows in column order. + const rowCells: ChecklistCell[] = columnQueues.map((queue) => { + const head = queue[0]; + if (head && referencesEqual(head.reference, targetRef)) { + queue.shift(); + return head; + } + return makeEmptyCell(); + }); + + rows.push({ + cells: rowCells, + isMatch: false, + includeEditLink: false, + score: 0, + firstRef: targetRef, + }); + } + + return rows; +} + +function makeEmptyCell(): ChecklistCell { + return { paragraphs: [], reference: undefined, language: '', error: undefined }; +} + +/** + * Three-way comparison on the `start` of two scripture ranges. `undefined` sorts last so columns + * with no reference don't displace columns with references during alignment. + */ +function compareReference(a: ScriptureRange | undefined, b: ScriptureRange | undefined): number { + if (a === undefined && b === undefined) return 0; + if (a === undefined) return 1; + if (b === undefined) return -1; + return compareVerseRef(a.start, b.start); +} + +function compareVerseRef(a: SerializedVerseRef, b: SerializedVerseRef): number { + if (a.book !== b.book) return a.book < b.book ? -1 : 1; + if (a.chapterNum !== b.chapterNum) return a.chapterNum - b.chapterNum; + return a.verseNum - b.verseNum; +} + +function referencesEqual(a: ScriptureRange | undefined, b: ScriptureRange | undefined): boolean { + return compareReference(a, b) === 0; +} From b6f021e94609695e03960efff9c0a74fc5164b2d Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:20:48 +0200 Subject: [PATCH 13/43] test(markers-checklist): USJ fixtures for paragraph-walker tests --- .../checklists/test-fixtures/usj-fixtures.ts | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/test-fixtures/usj-fixtures.ts diff --git a/extensions/src/platform-scripture/src/checklists/test-fixtures/usj-fixtures.ts b/extensions/src/platform-scripture/src/checklists/test-fixtures/usj-fixtures.ts new file mode 100644 index 00000000000..c7106b2e8d9 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/test-fixtures/usj-fixtures.ts @@ -0,0 +1,139 @@ +import type { Usj } from '@eten-tech-foundation/scripture-utilities'; + +/** + * Hand-authored USJ documents used by the USJ-paragraph-walker unit tests. Kept small and focused + * on the behaviors we care about — heading classification, INV-009 forward-scan, FB-35863 + * chapter-boundary stop, and the "skip nested notes/figures" invariant. Real-USJ smoke verification + * happens in Phase 7's behavior-parity baseline diff, not here. + */ + +/** + * Minimal USJ document for a single book with one chapter containing one paragraph. Used by walker + * tests as the "smallest sane input". + */ +export const SIMPLE_GENESIS_ONE_PARA: Usj = { + type: 'USJ', + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 'p', + content: [ + { type: 'verse', marker: 'v', number: '1', sid: 'GEN 1:1' }, + 'In the beginning God created the heavens and the earth.', + ], + }, + ], +}; + +/** Genesis 1 with one heading + one body paragraph. Heading should forward-scan to verse 1. */ +export const GENESIS_WITH_HEADING: Usj = { + type: 'USJ', + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 's', + content: ['Section heading'], + }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1', sid: 'GEN 1:1' }, 'Body content.'], + }, + ], +}; + +/** + * Genesis with a heading directly before a chapter boundary — FB-35863 case. The heading must NOT + * pull forward to the next chapter's verse 1; it stays at the previous chapter's last verseRef. + */ +export const GENESIS_HEADING_BEFORE_CHAPTER: Usj = { + type: 'USJ', + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1', sid: 'GEN 1:1' }, 'Chapter 1 content.'], + }, + { + type: 'para', + marker: 's', + content: ['Orphan heading at end of chapter 1'], + }, + { type: 'chapter', marker: 'c', number: '2', sid: 'GEN 2' }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1', sid: 'GEN 2:1' }, 'Chapter 2 content.'], + }, + ], +}; + +/** Empty book — no paragraph nodes. Used for edge-case tests. */ +export const EMPTY_BOOK: Usj = { + type: 'USJ', + version: '3.1', + content: [{ type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }], +}; + +/** Paragraph containing a nested footnote — walker must NOT descend into the note. */ +export const PARA_WITH_NESTED_NOTE: Usj = { + type: 'USJ', + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 'p', + content: [ + { type: 'verse', marker: 'v', number: '1', sid: 'GEN 1:1' }, + 'Before note. ', + { + type: 'note', + marker: 'f', + caller: '+', + content: [ + { + type: 'para', + marker: 'fp', + content: ['Footnote paragraph — must NOT be walked as a top-level paragraph.'], + }, + ], + }, + ' After note.', + ], + }, + ], +}; + +/** + * Paragraph containing a `\ior` character span — exercises the consumer-inventory § 3 override: the + * walker must emit a `TextItem` with `characterStyle: 'ior'`, NOT a `LinkItem`. The plan's earlier + * `\ior` LinkItem special-case is wrong; this fixture is the canary for that decision. + */ +export const PARA_WITH_IOR_CHAR: Usj = { + type: 'USJ', + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 'io1', + content: [ + 'Outline entry — ', + { type: 'char', marker: 'ior', content: ['1:1'] }, + ' first reference.', + ], + }, + ], +}; From c2d07b466f4b347edfe6f4f80bee00921c9c6f13 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:29:53 +0200 Subject: [PATCH 14/43] feat(markers-checklist): usj-paragraph-walker.ts (USJ-based replacement for C# GetTokensForBook) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure function over USJ. Replaces the C# state-machine token walker with a straight iteration of top-level usj.content nodes (paragraphs, chapter, verse markers). Notes/figures are skipped implicitly — they're nested, not top-level. Implements INV-009 heading forward-scan + FB-35863 chapter-boundary stop. Char-marker handling drops the plan's \\ior LinkItem special-case per consumer-inventory § 3: all char nodes flatten to a TextItem with characterStyle: marker, matching the C# walker at ChecklistService.cs ~1082-1095. LinkItem is reserved for the CrossReferences branch (out of scope for the Markers checklist). Walker emits WalkerParagraph[] (the walker-internal shape with verseRefStart + isHeading); the orchestrator strips those fields before populating the wire ChecklistParagraph[]. 19 unit tests cover: basic walking, user markerFilter, heading classification, INV-009 forward scan (including consecutive headings + FB-35863 chapter boundary), nested-note skipping, item extraction (text/verse/char), verseRef tracking state (intro=0, inherit-last-verse, chapter reset), and Canon book-id mapping. --- .../checklists/usj-paragraph-walker.test.ts | 317 ++++++++++++++++++ .../src/checklists/usj-paragraph-walker.ts | 210 ++++++++++++ 2 files changed, 527 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.ts diff --git a/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.test.ts b/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.test.ts new file mode 100644 index 00000000000..e809db25de2 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.test.ts @@ -0,0 +1,317 @@ +import { describe, it, expect } from 'vitest'; +import { walkParagraphMarkers } from './usj-paragraph-walker'; +import { + SIMPLE_GENESIS_ONE_PARA, + GENESIS_WITH_HEADING, + GENESIS_HEADING_BEFORE_CHAPTER, + EMPTY_BOOK, + PARA_WITH_NESTED_NOTE, + PARA_WITH_IOR_CHAR, +} from './test-fixtures/usj-fixtures'; + +const GEN_BOOK_NUM = 1; + +describe('walkParagraphMarkers', () => { + describe('basic walking', () => { + it('emits one paragraph for SIMPLE_GENESIS_ONE_PARA with verseRef at GEN 1:1', () => { + const paragraphs = walkParagraphMarkers( + SIMPLE_GENESIS_ONE_PARA, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + expect(paragraphs).toHaveLength(1); + expect(paragraphs[0].marker).toBe('p'); + expect(paragraphs[0].isHeading).toBe(false); + expect(paragraphs[0].verseRefStart).toEqual({ book: 'GEN', chapterNum: 1, verseNum: 1 }); + }); + + it('emits empty array for empty book', () => { + const paragraphs = walkParagraphMarkers(EMPTY_BOOK, GEN_BOOK_NUM, new Set(), new Set()); + expect(paragraphs).toEqual([]); + }); + }); + + describe('user markerFilter', () => { + it('non-empty filter excludes paragraphs whose marker is not in the filter', () => { + const paragraphs = walkParagraphMarkers( + GENESIS_WITH_HEADING, + GEN_BOOK_NUM, + new Set(['p']), // only paragraphs with marker 'p' + new Set(['s']), // heading classification + ); + expect(paragraphs).toHaveLength(1); + expect(paragraphs[0].marker).toBe('p'); + }); + + it('empty filter includes all paragraphs', () => { + const paragraphs = walkParagraphMarkers( + GENESIS_WITH_HEADING, + GEN_BOOK_NUM, + new Set(), + new Set(['s']), + ); + expect(paragraphs).toHaveLength(2); + expect(paragraphs.map((p) => p.marker)).toEqual(['s', 'p']); + }); + }); + + describe('heading classification + INV-009 forward scan', () => { + it('marks heading paragraphs with isHeading=true', () => { + const paragraphs = walkParagraphMarkers( + GENESIS_WITH_HEADING, + GEN_BOOK_NUM, + new Set(), + new Set(['s']), + ); + const heading = paragraphs.find((p) => p.marker === 's'); + expect(heading?.isHeading).toBe(true); + }); + + it("heading paragraph's verseRefStart is the next non-heading paragraph's first verse", () => { + const paragraphs = walkParagraphMarkers( + GENESIS_WITH_HEADING, + GEN_BOOK_NUM, + new Set(), + new Set(['s']), + ); + const heading = paragraphs.find((p) => p.marker === 's'); + // Forward-scan should land on GEN 1:1 (the first verse of the next body paragraph). + expect(heading?.verseRefStart).toEqual({ book: 'GEN', chapterNum: 1, verseNum: 1 }); + }); + + it('FB-35863: heading right before chapter boundary does NOT pull forward into next chapter', () => { + const paragraphs = walkParagraphMarkers( + GENESIS_HEADING_BEFORE_CHAPTER, + GEN_BOOK_NUM, + new Set(), + new Set(['s']), + ); + const heading = paragraphs.find((p) => p.marker === 's'); + // The forward-scan must STOP at the chapter boundary; the heading retains the + // verseRef of the previous content (GEN 1:1 in this fixture — the current verseRef + // state when the heading was encountered). + expect(heading?.verseRefStart.chapterNum).toBe(1); + expect(heading?.verseRefStart.book).toBe('GEN'); + // Specifically, it must NOT have advanced to chapter 2's verse 1. + expect(heading?.verseRefStart).not.toEqual({ book: 'GEN', chapterNum: 2, verseNum: 1 }); + }); + + it('skips intermediate heading markers during forward-scan (e.g. \\s1 then \\s2 then \\p)', () => { + const usj = { + type: 'USJ' as const, + version: '3.1' as const, + content: [ + { type: 'book', marker: 'id', code: 'GEN' as const, content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1' }, + { type: 'para', marker: 's1', content: ['Major heading'] }, + { type: 'para', marker: 's2', content: ['Sub heading'] }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1' }, 'Body'], + }, + ], + }; + const paragraphs = walkParagraphMarkers(usj, GEN_BOOK_NUM, new Set(), new Set(['s1', 's2'])); + const s1 = paragraphs.find((p) => p.marker === 's1'); + const s2 = paragraphs.find((p) => p.marker === 's2'); + // Both headings inherit GEN 1:1 from the body paragraph that follows them. + expect(s1?.verseRefStart.verseNum).toBe(1); + expect(s2?.verseRefStart.verseNum).toBe(1); + }); + }); + + describe('skip nested notes/figures', () => { + it('does NOT emit paragraphs nested inside note nodes', () => { + const paragraphs = walkParagraphMarkers( + PARA_WITH_NESTED_NOTE, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + // Should emit exactly one paragraph — the outer 'p' — not the 'fp' inside the note. + expect(paragraphs).toHaveLength(1); + expect(paragraphs[0].marker).toBe('p'); + }); + + it('outer paragraph items skip the inline note entirely', () => { + const paragraphs = walkParagraphMarkers( + PARA_WITH_NESTED_NOTE, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + // The text content from inside the note must not appear in the paragraph's items. + const allText = paragraphs[0].items + .flatMap((i) => (i.type === 'text' ? [i.text] : [])) + .join(''); + expect(allText).not.toMatch(/Footnote paragraph/); + }); + }); + + describe('item extraction', () => { + it('extracts text strings as TextItems', () => { + const paragraphs = walkParagraphMarkers( + SIMPLE_GENESIS_ONE_PARA, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + const textItems = paragraphs[0].items.filter((i) => i.type === 'text'); + expect(textItems.length).toBeGreaterThan(0); + }); + + it('top-level string text items have characterStyle=undefined', () => { + const paragraphs = walkParagraphMarkers( + SIMPLE_GENESIS_ONE_PARA, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + const text = paragraphs[0].items.find((i) => i.type === 'text'); + expect(text).toBeDefined(); + expect(text && 'characterStyle' in text ? text.characterStyle : 'missing').toBeUndefined(); + }); + + it('extracts verse markers as VerseItems', () => { + const paragraphs = walkParagraphMarkers( + SIMPLE_GENESIS_ONE_PARA, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + const verseItems = paragraphs[0].items.filter((i) => i.type === 'verse'); + expect(verseItems).toEqual([{ type: 'verse', verseNumber: '1' }]); + }); + + it('emits TextItem with characterStyle for char-marker spans (NOT LinkItem) — consumer-inventory § 3', () => { + const paragraphs = walkParagraphMarkers( + PARA_WITH_IOR_CHAR, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + expect(paragraphs).toHaveLength(1); + const { items } = paragraphs[0]; + // No LinkItem should be emitted — that's reserved for the CrossReferences checklist branch. + expect(items.some((i) => i.type === 'link')).toBe(false); + // The \ior content must show up as a TextItem with characterStyle: 'ior'. + const iorItem = items.find((i) => i.type === 'text' && i.characterStyle === 'ior'); + expect(iorItem).toBeDefined(); + expect(iorItem?.type === 'text' ? iorItem.text : '').toBe('1:1'); + }); + + it('skips inline note nodes when extracting items (no items from inside the note)', () => { + const paragraphs = walkParagraphMarkers( + PARA_WITH_NESTED_NOTE, + GEN_BOOK_NUM, + new Set(), + new Set(), + ); + // Items should be: verse 1, "Before note. ", " After note." — the note is skipped entirely. + const { items } = paragraphs[0]; + expect(items.some((i) => i.type === 'verse')).toBe(true); + const textsConcat = items.flatMap((i) => (i.type === 'text' ? [i.text] : [])).join(''); + expect(textsConcat).toMatch(/Before note\./); + expect(textsConcat).toMatch(/After note\./); + }); + }); + + describe('verseRef tracking state', () => { + it('initial verseRef before any verse marker is chapter=1 verse=0', () => { + // A paragraph before the first verse should carry the introductory verseRef. + const introUsj = { + type: 'USJ' as const, + version: '3.1' as const, + content: [ + { type: 'book', marker: 'id', code: 'GEN' as const, content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1' }, + { type: 'para', marker: 'ip', content: ['Introduction paragraph.'] }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1' }, 'Body'], + }, + ], + }; + const paragraphs = walkParagraphMarkers(introUsj, GEN_BOOK_NUM, new Set(), new Set()); + const intro = paragraphs.find((p) => p.marker === 'ip'); + expect(intro?.verseRefStart.verseNum).toBe(0); + expect(intro?.verseRefStart.chapterNum).toBe(1); + }); + + it("a body paragraph after a previous paragraph inherits the previous paragraph's last verse", () => { + // Two paragraphs in the same chapter — the second has no verse marker but should inherit + // the last seen verse from the first paragraph. + const usj = { + type: 'USJ' as const, + version: '3.1' as const, + content: [ + { type: 'book', marker: 'id', code: 'GEN' as const, content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1' }, + { + type: 'para', + marker: 'p', + content: [ + { type: 'verse', marker: 'v', number: '1' }, + 'First verse text. ', + { type: 'verse', marker: 'v', number: '2' }, + 'Second verse text.', + ], + }, + { type: 'para', marker: 'p', content: ['Continuation text in the same verse.'] }, + ], + }; + const paragraphs = walkParagraphMarkers(usj, GEN_BOOK_NUM, new Set(), new Set()); + expect(paragraphs).toHaveLength(2); + // The first paragraph starts at verse 1 (its first verse marker). + expect(paragraphs[0].verseRefStart).toEqual({ book: 'GEN', chapterNum: 1, verseNum: 1 }); + // The second paragraph inherits verse 2 (the last verse seen in the first paragraph). + expect(paragraphs[1].verseRefStart).toEqual({ book: 'GEN', chapterNum: 1, verseNum: 2 }); + }); + + it('chapter boundary resets verseNum to 0', () => { + const usj = { + type: 'USJ' as const, + version: '3.1' as const, + content: [ + { type: 'book', marker: 'id', code: 'GEN' as const, content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1' }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '5' }, 'Verse 5.'], + }, + { type: 'chapter', marker: 'c', number: '2' }, + // No verse marker — should be GEN 2:0. + { type: 'para', marker: 'p', content: ['Chapter 2 lead text.'] }, + ], + }; + const paragraphs = walkParagraphMarkers(usj, GEN_BOOK_NUM, new Set(), new Set()); + expect(paragraphs).toHaveLength(2); + expect(paragraphs[1].verseRefStart).toEqual({ book: 'GEN', chapterNum: 2, verseNum: 0 }); + }); + }); + + describe('book number mapping', () => { + it('uses Canon.bookNumberToId to populate the book field (bookNum=40 -> MAT)', () => { + // Canon's 1-based numbering: 1=GEN, 40=MAT, 41=MRK, ..., 66=REV. + const matUsj = { + type: 'USJ' as const, + version: '3.1' as const, + content: [ + { type: 'book', marker: 'id', code: 'MAT' as const, content: ['Matthew'] }, + { type: 'chapter', marker: 'c', number: '1' }, + { + type: 'para', + marker: 'p', + content: [{ type: 'verse', marker: 'v', number: '1' }, 'In the beginning'], + }, + ], + }; + const paragraphs = walkParagraphMarkers(matUsj, 40, new Set(), new Set()); + expect(paragraphs[0].verseRefStart.book).toBe('MAT'); + }); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.ts b/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.ts new file mode 100644 index 00000000000..a991c8aa47d --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/usj-paragraph-walker.ts @@ -0,0 +1,210 @@ +import type { MarkerContent, MarkerObject, Usj } from '@eten-tech-foundation/scripture-utilities'; +import { Canon, type SerializedVerseRef } from '@sillsdev/scripture'; +import type { ChecklistContentItem, WalkerParagraph } from './checklist.types'; + +/** + * Walks `usj.content` top-level nodes in document order, emitting a `WalkerParagraph` per + * qualifying paragraph node. Pure function — no PDP access, no UsfmToken state machine. + * + * Replaces the C# `ChecklistService.GetTokensForBook` + per-token switch (see + * `c-sharp/Checklists/ChecklistService.cs` ~660-1099). Top-level walk semantics: + * + * - `{type: 'chapter'}` advances `currentVerseRef` to `(book, n, 0)`. + * - `{type: 'verse'}` advances `currentVerseRef`'s `verseNum`. + * - `{type: 'para'}` emits a `WalkerParagraph` (subject to `userMarkerFilter`); the paragraph's + * `content` is walked for items + state advancement, but its nested `note` / `figure` / etc. + * children are NOT descended for paragraph emission (they're nested, not top-level). + * - All other top-level node types (`book`, stray notes, etc.) are skipped. + * + * INV-009 / FB-35863: heading paragraphs forward-scan to the next non-heading content paragraph for + * their `verseRefStart`; the scan stops at a chapter boundary. + */ +export function walkParagraphMarkers( + usj: Usj, + bookNum: number, + userMarkerFilter: ReadonlySet, + headingMarkers: ReadonlySet, +): WalkerParagraph[] { + const result: WalkerParagraph[] = []; + const bookId = Canon.bookNumberToId(bookNum); + + // Initial state: chapter 1, verse 0 (introductory material is "verse 0"). + let currentVerseRef: SerializedVerseRef = { book: bookId, chapterNum: 1, verseNum: 0 }; + + const topLevel = usj.content ?? []; + + topLevel.forEach((node, i) => { + if (typeof node === 'string') return; + if (!isMarkerObject(node)) return; + + if (node.type === 'chapter') { + const chapterNum = parseInt(node.number ?? '0', 10); + currentVerseRef = { book: bookId, chapterNum, verseNum: 0 }; + return; + } + + if (node.type === 'verse') { + const verseNum = parseInt(node.number ?? '0', 10); + currentVerseRef = { ...currentVerseRef, verseNum }; + return; + } + + if (node.type !== 'para') { + // Other top-level node types (`book`, stray `note`, etc.) — don't descend, don't emit. + return; + } + + const { marker } = node; + if (!marker) return; + + // User markerFilter: empty set = no filter; otherwise gate. + if (userMarkerFilter.size > 0 && !userMarkerFilter.has(marker)) { + // Even though we don't EMIT this paragraph, we must still advance verseRef state from its + // content — otherwise a subsequent emitted paragraph would inherit a stale verse. + currentVerseRef = advanceVerseRefAcrossContent(node.content ?? [], currentVerseRef); + return; + } + + const isHeading = headingMarkers.has(marker); + const verseRefStart = isHeading + ? findVerseRefForHeading(topLevel, i, currentVerseRef, headingMarkers) + : computeBodyVerseRefStart(node, currentVerseRef); + + const items = extractItems(node.content ?? []); + result.push({ marker, verseRefStart, isHeading, items }); + + // Advance currentVerseRef state by walking the paragraph's content for verse markers, so the + // next paragraph inherits the last-seen verse. + currentVerseRef = advanceVerseRefAcrossContent(node.content ?? [], currentVerseRef); + }); + + return result; +} + +/** True when `value` is a `MarkerObject` (has a `type` field). */ +function isMarkerObject(value: unknown): value is MarkerObject { + // USJ JSON is parsed by the platform's `JSON.parse`, which may produce `null` for explicit JSON + // `null` values. A runtime `=== null` check is the safe way to gate a `typeof === 'object'` + // narrowing because `typeof null === 'object'`. Using `undefined` here would not protect us. + // eslint-disable-next-line no-null/no-null + return typeof value === 'object' && value !== null && 'type' in value; +} + +/** + * INV-009 / FB-35863: a heading paragraph's effective verseRef is the first verse of the next + * non-heading content paragraph. Skip `b` (blank line) and any marker starting with `i` + * (introductory: `\ib`, `\ip`, `\im`, ...) during the scan; STOP at the next chapter boundary + * (return the input `fallback` unchanged — the heading retains its current verseRef). + */ +function findVerseRefForHeading( + topLevel: MarkerContent[], + startIndex: number, + fallback: SerializedVerseRef, + headingMarkers: ReadonlySet, +): SerializedVerseRef { + // Slice the lookahead window, then find the first qualifying node. Using `.find` keeps us out + // of `for-of` territory while preserving the "first match wins / chapter stops the scan" + // semantics. + const lookahead = topLevel.slice(startIndex + 1); + let stopped = false; + let foundParagraph: MarkerObject | undefined; + lookahead.some((node) => { + if (typeof node === 'string') return false; + if (!isMarkerObject(node)) return false; + if (node.type === 'chapter') { + stopped = true; // FB-35863 + return true; + } + if (node.type !== 'para') return false; + const m = node.marker; + if (!m) return false; + if (m === 'b' || m.startsWith('i')) return false; + if (headingMarkers.has(m)) return false; + foundParagraph = node; + return true; + }); + if (stopped || !foundParagraph) return fallback; + return computeBodyVerseRefStart(foundParagraph, fallback); +} + +/** + * Looks into the paragraph's content for the first `{type: 'verse'}` and uses its number; otherwise + * returns the inherited `currentVerseRef`. + */ +function computeBodyVerseRefStart( + paraNode: MarkerObject, + currentVerseRef: SerializedVerseRef, +): SerializedVerseRef { + const content = paraNode.content ?? []; + const verseNode = content.find( + (n): n is MarkerObject => typeof n !== 'string' && isMarkerObject(n) && n.type === 'verse', + ); + if (!verseNode) return currentVerseRef; + const verseNum = parseInt(verseNode.number ?? '0', 10); + return { ...currentVerseRef, verseNum }; +} + +/** + * Extracts typed items from a paragraph's content array. Mirrors the C# per-token switch in + * `ChecklistService.cs` ~1082-1095: + * + * - `string` → `TextItem` with `characterStyle: undefined`. + * - `{type: 'verse'}` → `VerseItem`. + * - `{type: 'char', marker, content}` → flatten string children into a `TextItem` with + * `characterStyle: marker` (see consumer-inventory § 3 — the plan's `\ior` LinkItem special-case + * is dropped; `LinkItem` is reserved for the CrossReferences branch, which is out of scope for + * the Markers checklist). + * - `{type: 'note' | 'figure' | ...}` → SKIPPED entirely (no items emitted). + * + * Char-node content is assumed flat (string children only) per the USJ shape we see in practice — + * nested chars within a char are not expected; if encountered, the nested string content is still + * flattened into the surrounding `characterStyle` TextItem. + */ +function extractItems(content: MarkerContent[]): ChecklistContentItem[] { + const items: ChecklistContentItem[] = []; + content.forEach((node) => { + if (typeof node === 'string') { + items.push({ type: 'text', text: node, characterStyle: undefined }); + return; + } + if (!isMarkerObject(node)) return; + + if (node.type === 'verse') { + items.push({ type: 'verse', verseNumber: node.number ?? '' }); + return; + } + + if (node.type === 'char') { + const charText = (node.content ?? []) + .filter((c): c is string => typeof c === 'string') + .join(''); + // Don't emit an empty TextItem if the char node has no string content (e.g. a self-closing + // char with only nested non-string children — unusual but defensive). + if (charText.length > 0) { + items.push({ type: 'text', text: charText, characterStyle: node.marker }); + } + } + + // `note`, `figure`, and any other inline marker types are NOT part of the cell's checklist + // content — skip them entirely. + }); + return items; +} + +/** + * Walks a paragraph's content for verse markers and returns the updated verseRef. Used both for + * emitted paragraphs (advance state for the next paragraph) and for filtered-out paragraphs (so a + * `markerFilter`-excluded paragraph still contributes its verse advances to following paragraphs' + * inherited state). + */ +function advanceVerseRefAcrossContent( + content: MarkerContent[], + start: SerializedVerseRef, +): SerializedVerseRef { + return content.reduce((vr, node) => { + if (typeof node === 'string') return vr; + if (!isMarkerObject(node)) return vr; + if (node.type !== 'verse') return vr; + return { ...vr, verseNum: parseInt(node.number ?? '0', 10) }; + }, start); +} From fd2b60ff6718c9025f18aa84376ed4a84088ff3d Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:38:23 +0200 Subject: [PATCH 15/43] feat(markers-checklist): fetch-column-book-data.ts (batched per-book PDP helper) Acquires the 3 PDPs (USJ_Book, MarkerNames, base) and fetches everything in parallel. Single call site in the orchestrator; easy to test in isolation. Introduces PapiLike structural interface so the helper avoids depending on @papi/frontend (React-bound, not importable from pure-Node test runs). --- .../checklists/fetch-column-book-data.test.ts | 201 ++++++++++++++++++ .../src/checklists/fetch-column-book-data.ts | 94 ++++++++ 2 files changed, 295 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts new file mode 100644 index 00000000000..d6126006952 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts @@ -0,0 +1,201 @@ +import { describe, it, expect, vi } from 'vitest'; +import { fetchColumnBookData, type PapiLike } from './fetch-column-book-data'; + +/** + * Construct a minimal mock PAPI that exposes the three PDPs `fetchColumnBookData` consumes: + * `platformScripture.USJ_Book`, `platformScripture.MarkerNames`, and `platform.base`. Tests vary + * the per-(key, type) shape via `overrides` to exercise the helper's batching + normalization + * behavior in isolation. + */ +function makePapi( + overrides: { + usj?: unknown; + headingMarkers?: string[]; + joinedTextLanguage?: string; + isEditable?: unknown; + textDirection?: unknown; + } = {}, +): PapiLike { + const factory = async (type: string) => { + if (type === 'platformScripture.USJ_Book') + return { + getBookUSJ: vi + .fn() + .mockResolvedValue(overrides.usj ?? { type: 'USJ', version: '3.1', content: [] }), + }; + if (type === 'platformScripture.MarkerNames') + return { + getHeadingMarkers: vi.fn().mockResolvedValue(overrides.headingMarkers ?? ['s', 'ms']), + getJoinedTextLanguage: vi.fn().mockResolvedValue(overrides.joinedTextLanguage ?? 'en'), + }; + if (type === 'platform.base') + return { + getSetting: vi.fn().mockImplementation(async (key: string) => { + if (key === 'platform.isEditable') return overrides.isEditable ?? true; + if (key === 'platformScripture.textDirection') return overrides.textDirection ?? 'ltr'; + return undefined; + }), + }; + throw new Error(`Unknown PDP type: ${type}`); + }; + return { + projectDataProviders: { + // Test-only mock factory: real `get` is overloaded by string type; the mock dispatches at + // runtime, so we cast through `unknown` to satisfy the overload union. + // eslint-disable-next-line no-type-assertion/no-type-assertion + get: vi + .fn() + .mockImplementation(factory) as unknown as PapiLike['projectDataProviders']['get'], + }, + }; +} + +describe('fetchColumnBookData', () => { + it('returns USJ + headingMarkers + joinedTextLanguage + isEditable + rtl in one call', async () => { + const usj = { type: 'USJ', version: '3.1', content: [] }; + const papi = makePapi({ + usj, + headingMarkers: ['s', 'ms'], + joinedTextLanguage: 'en', + isEditable: true, + textDirection: 'ltr', + }); + + const data = await fetchColumnBookData(papi, 'PROJ-1', 1); + + expect(data.usj).toEqual(usj); + expect(data.headingMarkers).toEqual(new Set(['s', 'ms'])); + expect(data.joinedTextLanguage).toBe('en'); + expect(data.isEditable).toBe(true); + expect(data.rtl).toBe(false); + }); + + it('rtl=true when textDirection setting is "rtl"', async () => { + const papi = makePapi({ + headingMarkers: [], + joinedTextLanguage: 'he', + isEditable: false, + textDirection: 'rtl', + }); + + const data = await fetchColumnBookData(papi, 'PROJ-RTL', 1); + + expect(data.rtl).toBe(true); + expect(data.isEditable).toBe(false); + expect(data.joinedTextLanguage).toBe('he'); + expect(data.headingMarkers).toEqual(new Set()); + }); + + it('rtl=false for any non-"rtl" textDirection value (defensive)', async () => { + const papi = makePapi({ textDirection: 'auto' }); + const data = await fetchColumnBookData(papi, 'PROJ-1', 1); + expect(data.rtl).toBe(false); + }); + + it('passes the bookNum through to USJ + heading + language fetches', async () => { + const usjMock = vi.fn().mockResolvedValue({ type: 'USJ', version: '3.1', content: [] }); + const headingMock = vi.fn().mockResolvedValue([]); + const languageMock = vi.fn().mockResolvedValue('en'); + const papi: PapiLike = { + projectDataProviders: { + // Test-only mock: dispatch the overloaded `get` at runtime by string type, cast through + // `unknown` so TS accepts the factory implementation against the overload union. + // eslint-disable-next-line no-type-assertion/no-type-assertion + get: vi.fn().mockImplementation(async (type: string) => { + if (type === 'platformScripture.USJ_Book') return { getBookUSJ: usjMock }; + if (type === 'platformScripture.MarkerNames') + return { getHeadingMarkers: headingMock, getJoinedTextLanguage: languageMock }; + if (type === 'platform.base') return { getSetting: vi.fn().mockResolvedValue(undefined) }; + throw new Error(`Unknown PDP type: ${type}`); + }) as unknown as PapiLike['projectDataProviders']['get'], + }, + }; + + await fetchColumnBookData(papi, 'PROJ-1', 40); + + expect(usjMock).toHaveBeenCalledWith(40); + expect(headingMock).toHaveBeenCalledWith(40); + expect(languageMock).toHaveBeenCalledWith(40); + }); + + it('acquires all three PDPs with the same projectId', async () => { + const getMock = vi.fn().mockImplementation(async (type: string) => { + if (type === 'platformScripture.USJ_Book') + return { + getBookUSJ: vi.fn().mockResolvedValue({ type: 'USJ', version: '3.1', content: [] }), + }; + if (type === 'platformScripture.MarkerNames') + return { + getHeadingMarkers: vi.fn().mockResolvedValue([]), + getJoinedTextLanguage: vi.fn().mockResolvedValue('en'), + }; + if (type === 'platform.base') return { getSetting: vi.fn().mockResolvedValue(undefined) }; + throw new Error(`Unknown PDP type: ${type}`); + }); + const papi: PapiLike = { + projectDataProviders: { + // Test-only mock: cast through `unknown` to bridge the runtime-dispatch factory mock to + // the production overload-union type signature. + // eslint-disable-next-line no-type-assertion/no-type-assertion + get: getMock as unknown as PapiLike['projectDataProviders']['get'], + }, + }; + + await fetchColumnBookData(papi, 'PROJ-XYZ', 1); + + expect(getMock).toHaveBeenCalledWith('platformScripture.USJ_Book', 'PROJ-XYZ'); + expect(getMock).toHaveBeenCalledWith('platformScripture.MarkerNames', 'PROJ-XYZ'); + expect(getMock).toHaveBeenCalledWith('platform.base', 'PROJ-XYZ'); + }); + + it('coerces falsy isEditable values to boolean false', async () => { + // Construct a papi where getSetting('platform.isEditable') explicitly returns null — the most + // realistic "falsy non-boolean" case (some PDPs use null for unset). + const papi: PapiLike = { + projectDataProviders: { + // Test-only mock: dispatch the overloaded `get` at runtime by string type, cast through + // `unknown` so TS accepts the factory implementation against the overload union. + // eslint-disable-next-line no-type-assertion/no-type-assertion + get: vi.fn().mockImplementation(async (type: string) => { + if (type === 'platformScripture.USJ_Book') + return { + getBookUSJ: vi.fn().mockResolvedValue({ type: 'USJ', version: '3.1', content: [] }), + }; + if (type === 'platformScripture.MarkerNames') + return { + getHeadingMarkers: vi.fn().mockResolvedValue([]), + getJoinedTextLanguage: vi.fn().mockResolvedValue('en'), + }; + if (type === 'platform.base') + return { + getSetting: vi.fn().mockImplementation(async (key: string) => { + if (key === 'platform.isEditable') + // PDPs may return null for unset booleans; verify Boolean(null) -> false. + // eslint-disable-next-line no-null/no-null + return null; + return undefined; + }), + }; + throw new Error(`Unknown PDP type: ${type}`); + }) as unknown as PapiLike['projectDataProviders']['get'], + }, + }; + const data = await fetchColumnBookData(papi, 'PROJ-1', 1); + expect(data.isEditable).toBe(false); + }); + + it('coerces truthy non-boolean isEditable values to boolean true', async () => { + const papi = makePapi({ isEditable: 1 }); + const data = await fetchColumnBookData(papi, 'PROJ-1', 1); + expect(data.isEditable).toBe(true); + }); + + it('headingMarkers is a ReadonlySet — supports .has() lookups', async () => { + const papi = makePapi({ headingMarkers: ['s', 's1', 'ms'] }); + const data = await fetchColumnBookData(papi, 'PROJ-1', 1); + expect(data.headingMarkers.has('s')).toBe(true); + expect(data.headingMarkers.has('s1')).toBe(true); + expect(data.headingMarkers.has('ms')).toBe(true); + expect(data.headingMarkers.has('p')).toBe(false); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts new file mode 100644 index 00000000000..7ec1fee97ff --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts @@ -0,0 +1,94 @@ +import type { Usj } from '@eten-tech-foundation/scripture-utilities'; + +/** + * Minimal structural interface satisfied by the real `@papi/frontend` PAPI client at the call sites + * `fetchColumnBookData` exercises. Defined here so the helper (and its tests) need not depend on + * `@papi/frontend` directly — that module is React-Electron-bound and not importable from pure-Node + * test runs. Production callers pass `papi` from `@papi/frontend`; tests construct `vi.fn()`-based + * mocks. Only the methods this helper invokes are typed. + */ +export type PapiLike = { + projectDataProviders: { + get(type: 'platformScripture.USJ_Book', projectId: string): Promise; + get(type: 'platformScripture.MarkerNames', projectId: string): Promise; + get(type: 'platform.base', projectId: string): Promise; + }; +}; + +export type UsjBookPdpLike = { getBookUSJ(bookNum: number): Promise }; + +export type MarkerNamesPdpLike = { + getHeadingMarkers(bookNum: number): Promise; + getJoinedTextLanguage(bookNum: number): Promise; +}; + +export type BasePdpLike = { + getSetting(key: string): Promise; +}; + +/** + * Per-(project, book) data needed by the checklist orchestrator. Aggregates the three PDP fetches + * (`USJ_Book`, `MarkerNames`, `base`) into a single batched payload so the orchestrator can iterate + * (column, book) without juggling individual PDP lifetimes. + */ +export type ColumnBookData = { + /** Raw USJ document for the requested book. Fed to `walkParagraphMarkers`. */ + usj: Usj; + /** + * Set of heading-marker names for this project/book (e.g. `s`, `s1`, `ms`). Used by the walker to + * apply INV-009 forward-scan when computing a heading paragraph's `verseRefStart`. + */ + headingMarkers: ReadonlySet; + /** + * BCP-47 language tag for the joined text content of this (project, book). Surfaced on the cell + * for downstream language-aware rendering. Mirrors C# + * `MarkerNamesDataProvider.GetJoinedTextLanguage`. + */ + joinedTextLanguage: string; + /** + * `platform.isEditable` setting — gates the per-cell `EditLinkItem` emission (BHV-114 / VAL-007 + * cond 4). When `false`, the orchestrator skips appending an `EditLinkItem` to the cell's last + * paragraph. + */ + isEditable: boolean; + /** + * `true` when `platformScripture.textDirection === 'rtl'`. The walker uses this to prefix RTL + * text runs at extraction time (mirrors C# `ChecklistService.cs:1088`); `fetchColumnBookData` + * simply surfaces the resolved flag. + */ + rtl: boolean; +}; + +/** + * Batched per-(project, book) PDP fetch. Acquires the three PDPs the orchestrator needs and reads + * everything in parallel. Centralizes the PDP wiring so `buildChecklistData` stays focused on + * pipeline composition rather than PDP lifetime management. + */ +export async function fetchColumnBookData( + papi: PapiLike, + projectId: string, + bookNum: number, +): Promise { + const [usjPdp, markerNamesPdp, basePdp] = await Promise.all([ + papi.projectDataProviders.get('platformScripture.USJ_Book', projectId), + papi.projectDataProviders.get('platformScripture.MarkerNames', projectId), + papi.projectDataProviders.get('platform.base', projectId), + ]); + + const [usj, headingMarkersArray, joinedTextLanguage, isEditableRaw, textDirectionRaw] = + await Promise.all([ + usjPdp.getBookUSJ(bookNum), + markerNamesPdp.getHeadingMarkers(bookNum), + markerNamesPdp.getJoinedTextLanguage(bookNum), + basePdp.getSetting('platform.isEditable'), + basePdp.getSetting('platformScripture.textDirection'), + ]); + + return { + usj, + headingMarkers: new Set(headingMarkersArray), + joinedTextLanguage, + isEditable: Boolean(isEditableRaw), + rtl: textDirectionRaw === 'rtl', + }; +} From 181b7f0253eca00b7c5e04f9fcc40099220c9c0f Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:47:56 +0200 Subject: [PATCH 16/43] feat(markers-checklist): build-checklist-data.ts (TS replacement for ChecklistService.BuildChecklistData) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Composes USJ walker + post-processor + per-verseRef cell grouping (§6) + row builder + match detection + hideMatches filter + EditLinkItem placement (BHV-114) + empty-result variant detection (§7.2 — markerFilter.size===0 gate only). Replaces the entire backend ChecklistService.BuildChecklistData pipeline. Highlights: - Per-verseRef paragraph grouping happens here (scenario-01 row 0 = 24-paragraph intro cell). - markerFilter strips backslashes before tokenizing (§7.1 / VAL-001). - Both platform.name (header) and platform.fullName (tooltip) resolved per column (§7.3). - EditLink gated per-cell on column-level isEditable AND non-undefined cell.reference, appended to last paragraph (mirrors c-sharp/Checklists/ChecklistService.cs:565). - row.includeEditLink populated post-row-build per VAL-007 cond 2 row-level signal. 20 new tests cover end-to-end behaviors: single-column shape, verseRef grouping, INV-002 single-column match override, multi-column match/no-match, hideMatches + excludedCount, empty-result variants (identical vs noResults), markerFilter parsing + backslash strip, ProjectNotFoundError on missing project, column headers, booksPresent intersection. --- .../checklists/build-checklist-data.test.ts | 421 ++++++++++++++++++ .../src/checklists/build-checklist-data.ts | 391 ++++++++++++++++ 2 files changed, 812 insertions(+) create mode 100644 extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts create mode 100644 extensions/src/platform-scripture/src/checklists/build-checklist-data.ts diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts new file mode 100644 index 00000000000..7529eaeaf57 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts @@ -0,0 +1,421 @@ +import { describe, it, expect, vi } from 'vitest'; +import { buildChecklistData, ProjectNotFoundError } from './build-checklist-data'; +import type { PapiLike } from './fetch-column-book-data'; +import { SIMPLE_GENESIS_ONE_PARA, GENESIS_WITH_HEADING } from './test-fixtures/usj-fixtures'; +import type { ChecklistRequest } from './build-checklist-data'; + +/** + * Build a per-(projectId, key) lookup-driven papi mock. Each entry under `projects` describes one + * project; the orchestrator's PDP fetches are dispatched off the projectId argument so multi-column + * tests can vary per-project shapes (USJ, isEditable, name, etc.) independently. Verbose by design + * — keeps test setup local-and-explicit per behavior under test. + */ +function makePapiMock(opts: { + projects: Record< + string, + { + usjPerBook?: Record; + headingMarkers?: string[]; + joinedTextLanguage?: string; + isEditable?: boolean; + textDirection?: 'ltr' | 'rtl'; + booksPresent?: string; + name?: string; + fullName?: string; + missing?: true; + } + >; +}): PapiLike { + const factory = async (type: string, projectId: string) => { + const proj = opts.projects[projectId]; + if (!proj || proj.missing) throw new Error(`Project not found: ${projectId}`); + + if (type === 'platformScripture.USJ_Book') + return { + getBookUSJ: vi + .fn() + .mockImplementation( + async (n: number) => + proj.usjPerBook?.[n] ?? { type: 'USJ', version: '3.1', content: [] }, + ), + }; + if (type === 'platformScripture.MarkerNames') + return { + getHeadingMarkers: vi.fn().mockResolvedValue(proj.headingMarkers ?? []), + getJoinedTextLanguage: vi.fn().mockResolvedValue(proj.joinedTextLanguage ?? 'en'), + }; + if (type === 'platform.base') + return { + getSetting: vi.fn().mockImplementation(async (key: string) => { + if (key === 'platform.isEditable') return proj.isEditable ?? false; + if (key === 'platformScripture.textDirection') return proj.textDirection ?? 'ltr'; + if (key === 'platformScripture.booksPresent') + return proj.booksPresent ?? `1${'0'.repeat(122)}`; + if (key === 'platform.name') return proj.name ?? projectId; + if (key === 'platform.fullName') return proj.fullName ?? proj.name ?? projectId; + return undefined; + }), + }; + throw new Error(`Unknown PDP type: ${type}`); + }; + return { + projectDataProviders: { + // Test-only mock: dispatch the overloaded `get` at runtime by string type, cast through + // `unknown` so TS accepts the factory implementation against the overload union. + // eslint-disable-next-line no-type-assertion/no-type-assertion + get: vi + .fn() + .mockImplementation(factory) as unknown as PapiLike['projectDataProviders']['get'], + }, + }; +} + +/** Default request scaffold for single-column tests; spread + override per test. */ +function makeRequest(overrides: Partial = {}): ChecklistRequest { + return { + projectId: 'PROJ-1', + comparativeTextIds: [], + markerSettings: { equivalentMarkers: '', markerFilter: '' }, + verseRange: undefined, + hideMatches: false, + showVerseText: true, + ...overrides, + }; +} + +describe('buildChecklistData', () => { + describe('basic single-column shape', () => { + it('produces a ChecklistResult with one row for a single-book single-paragraph request', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, name: 'TestProject' }, + }, + }); + + const result = await buildChecklistData(makeRequest(), papi); + + expect(result.rows.length).toBeGreaterThan(0); + expect(result.columnHeaders).toEqual(['TestProject']); + expect(result.columnProjectIds).toEqual(['PROJ-1']); + expect(result.excludedCount).toBe(0); + expect(result.truncated).toBe(false); + }); + + it('groups all paragraphs at the same verseRef into ONE cell (per § 6)', async () => { + // GENESIS_WITH_HEADING has: chapter 1, then a heading `\s`, then `\p` with verse 1. + // The `\s` heading forward-scans (INV-009) to verse 1, so heading + p share verseRef GEN 1:1 + // and should land in ONE cell with 2 paragraphs. + const papi = makePapiMock({ + projects: { + 'PROJ-1': { + usjPerBook: { 1: GENESIS_WITH_HEADING }, + headingMarkers: ['s'], + name: 'P1', + }, + }, + }); + + const result = await buildChecklistData(makeRequest(), papi); + + // Expect: exactly 1 row with 1 cell containing 2 paragraphs (s, p) at GEN 1:1. + expect(result.rows.length).toBe(1); + expect(result.rows[0].cells.length).toBe(1); + expect(result.rows[0].cells[0].paragraphs.length).toBe(2); + expect(result.rows[0].cells[0].paragraphs[0].marker).toBe('s'); + expect(result.rows[0].cells[0].paragraphs[1].marker).toBe('p'); + }); + + it('single-column row.isMatch is always true (INV-002)', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + const result = await buildChecklistData(makeRequest(), papi); + result.rows.forEach((row) => expect(row.isMatch).toBe(true)); + }); + + it('cell.language is populated from joinedTextLanguage', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, joinedTextLanguage: 'es' }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.rows[0].cells[0].language).toBe('es'); + }); + + it('row.firstRef carries the cell start verseRef', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.rows[0].firstRef?.start).toEqual({ + book: 'GEN', + chapterNum: 1, + verseNum: 1, + }); + }); + + it('row.includeEditLink is true when first cell has a non-undefined reference', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.rows[0].includeEditLink).toBe(true); + }); + }); + + describe('EditLinkItem placement', () => { + it('appends EditLinkItem to last paragraph of each editable cell with a reference', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { + usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, + isEditable: true, + name: 'P1', + }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + + const cellParagraphs = result.rows[0].cells[0].paragraphs; + const lastPara = cellParagraphs[cellParagraphs.length - 1]; + const editLinkItem = lastPara.items.find((item) => item.type === 'editLink'); + expect(editLinkItem).toBeDefined(); + expect(editLinkItem).toMatchObject({ + type: 'editLink', + bookNum: 1, + chapterNum: 1, + verseNum: 1, + }); + }); + + it('does NOT append EditLinkItem when isEditable=false', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { + usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, + isEditable: false, + }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + const allItems = result.rows.flatMap((r) => + r.cells.flatMap((c) => c.paragraphs.flatMap((p) => p.items)), + ); + expect(allItems.find((item) => item.type === 'editLink')).toBeUndefined(); + }); + + it('does NOT append EditLinkItem to non-editable comparative columns', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { + usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, + isEditable: true, + name: 'Primary', + }, + 'PROJ-2': { + usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, + isEditable: false, + name: 'Comparative', + }, + }, + }); + const result = await buildChecklistData( + makeRequest({ comparativeTextIds: ['PROJ-2'] }), + papi, + ); + + const primaryCell = result.rows[0].cells[0]; + const compCell = result.rows[0].cells[1]; + const primaryHasLink = primaryCell.paragraphs + .flatMap((p) => p.items) + .some((item) => item.type === 'editLink'); + const compHasLink = compCell.paragraphs + .flatMap((p) => p.items) + .some((item) => item.type === 'editLink'); + + expect(primaryHasLink).toBe(true); + expect(compHasLink).toBe(false); + }); + }); + + describe('multi-column match detection', () => { + it('two-column identical content → all rows isMatch=true', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, name: 'A' }, + 'PROJ-2': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, name: 'B' }, + }, + }); + const result = await buildChecklistData( + makeRequest({ comparativeTextIds: ['PROJ-2'] }), + papi, + ); + expect(result.columnHeaders).toEqual(['A', 'B']); + result.rows.forEach((row) => expect(row.isMatch).toBe(true)); + }); + + it('two-column differing markers → rows isMatch=false', async () => { + const projAUsj = SIMPLE_GENESIS_ONE_PARA; + const projBUsj = { + type: 'USJ' as const, + version: '3.1', + content: [ + { type: 'book', marker: 'id', code: 'GEN', content: ['Genesis'] }, + { type: 'chapter', marker: 'c', number: '1', sid: 'GEN 1' }, + { + type: 'para', + marker: 'q1', // <-- different marker from PROJ-1's 'p' + content: [{ type: 'verse', marker: 'v', number: '1', sid: 'GEN 1:1' }, 'text'], + }, + ], + }; + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: projAUsj } }, + 'PROJ-2': { usjPerBook: { 1: projBUsj } }, + }, + }); + const result = await buildChecklistData( + makeRequest({ comparativeTextIds: ['PROJ-2'] }), + papi, + ); + expect(result.rows.length).toBeGreaterThan(0); + result.rows.forEach((row) => expect(row.isMatch).toBe(false)); + }); + + it('hideMatches=true drops matching rows and increments excludedCount', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } }, + 'PROJ-2': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } }, + }, + }); + const result = await buildChecklistData( + makeRequest({ comparativeTextIds: ['PROJ-2'], hideMatches: true }), + papi, + ); + expect(result.rows.length).toBe(0); + expect(result.excludedCount).toBeGreaterThan(0); + // Empty result + empty filter → 'identical' variant (per § 7.2). + expect(result.emptyResultMessage?.variant).toBe('identical'); + }); + }); + + describe('empty-result message', () => { + it('emptyResultMessage variant=identical when markerFilter is empty (§ 7.2)', async () => { + // Use an empty USJ (no content) so no rows are produced — and markerFilter is empty. + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: { type: 'USJ', version: '3.1', content: [] } } }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.rows).toEqual([]); + expect(result.emptyResultMessage).toBeDefined(); + expect(result.emptyResultMessage?.variant).toBe('identical'); + expect(result.emptyResultMessage?.searchedMarkers).toBeUndefined(); + expect(result.emptyResultMessage?.searchedBooks).toBeUndefined(); + }); + + it('emptyResultMessage variant=noResults when markerFilter is non-empty', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } }, + }, + }); + const result = await buildChecklistData( + makeRequest({ markerSettings: { equivalentMarkers: '', markerFilter: 'nonexistent' } }), + papi, + ); + expect(result.rows).toEqual([]); + expect(result.emptyResultMessage?.variant).toBe('noResults'); + expect(result.emptyResultMessage?.message).toBe(''); + expect(result.emptyResultMessage?.searchedMarkers).toEqual(['nonexistent']); + expect(result.emptyResultMessage?.searchedBooks).toEqual(['GEN']); + }); + + it('no emptyResultMessage when rows are present', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.emptyResultMessage).toBeUndefined(); + }); + }); + + describe('markerFilter parsing (§ 7.1 — VAL-001 backslash strip)', () => { + it('strips backslashes from markerFilter input before tokenizing', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + // User types `\p` — backslash must be stripped so the filter matches the `p` paragraph. + const result = await buildChecklistData( + makeRequest({ markerSettings: { equivalentMarkers: '', markerFilter: '\\p' } }), + papi, + ); + expect(result.rows.length).toBeGreaterThan(0); + }); + + it('tokenizes whitespace-separated markers into a filter set', async () => { + const papi = makePapiMock({ + projects: { 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } } }, + }); + const result = await buildChecklistData( + makeRequest({ markerSettings: { equivalentMarkers: '', markerFilter: 'q1 nonexistent' } }), + papi, + ); + // SIMPLE_GENESIS_ONE_PARA has only `p`, neither q1 nor nonexistent — empty result, noResults + // variant, both searched markers preserved. + expect(result.rows).toEqual([]); + expect(result.emptyResultMessage?.variant).toBe('noResults'); + expect(result.emptyResultMessage?.searchedMarkers?.sort()).toEqual( + ['nonexistent', 'q1'].sort(), + ); + }); + }); + + describe('error handling', () => { + it('throws ProjectNotFoundError when a column projectId fails to resolve', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA } }, + MISSING: { missing: true }, + }, + }); + await expect( + buildChecklistData(makeRequest({ comparativeTextIds: ['MISSING'] }), papi), + ).rejects.toBeInstanceOf(ProjectNotFoundError); + }); + }); + + describe('column headers (§ 7.3)', () => { + it('reads platform.name (short name) as the column header', async () => { + const papi = makePapiMock({ + projects: { + 'PROJ-1': { + usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, + name: 'TPTS', + fullName: 'TPTS Full', + }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.columnHeaders).toEqual(['TPTS']); + }); + }); + + describe('booksPresent intersection', () => { + it('skips books absent from booksPresent setting', async () => { + // booksPresent = '00...' (no books) → no rows iterated. + const papi = makePapiMock({ + projects: { + 'PROJ-1': { usjPerBook: { 1: SIMPLE_GENESIS_ONE_PARA }, booksPresent: '0'.repeat(123) }, + }, + }); + const result = await buildChecklistData(makeRequest(), papi); + expect(result.rows).toEqual([]); + expect(result.emptyResultMessage?.variant).toBe('identical'); + }); + }); +}); diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts new file mode 100644 index 00000000000..c9b960fb834 --- /dev/null +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts @@ -0,0 +1,391 @@ +import { Canon, type SerializedVerseRef } from '@sillsdev/scripture'; +import type { ScriptureRange } from 'platform-scripture'; +import type { LocalizeKey } from 'platform-bible-utils'; +import { fetchColumnBookData, type PapiLike, type ColumnBookData } from './fetch-column-book-data'; +import { walkParagraphMarkers } from './usj-paragraph-walker'; +import { postProcessParagraph } from './post-process-paragraph'; +import { buildRowsMergingCells } from './checklist-row-builder'; +import { hasSameValue, initializeMarkerMappings } from './match-detection'; +import type { + ChecklistCell, + ChecklistEmptyResultMessage, + ChecklistParagraph, + ChecklistRow, + EditLinkItem, + WalkerParagraph, +} from './checklist.types'; + +/** INV-012 — hard cap on rows returned. C# `ChecklistService.cs` MaxRows = 5000. */ +const MAX_ROWS = 5000; + +/** + * Localize KEY emitted in `emptyResultMessage.message` when the result is empty and `markerFilter` + * is empty. The React layer resolves this key at render-time via `useLocalizedStrings`. + */ +const IDENTICAL_MARKERS_MESSAGE_KEY: LocalizeKey = + '%markersChecklist_emptyResult_identicalMarkers%'; + +/** + * Input request shape for `buildChecklistData`. Matches the existing wire shape exposed by the web + * view's checklist-request adapter so Phase 6 wiring stays minimal. See + * `c-sharp/Checklists/Markers/MarkersDataSource.cs` and the existing web view adapter for the + * authoritative field semantics. + */ +export type ChecklistRequest = { + /** Primary (left-most column) project. Drives column 0 of the result. */ + projectId: string; + /** Comparative columns to align against `projectId`. Empty array → single-column mode (INV-002). */ + comparativeTextIds: string[]; + /** + * Marker settings parsed by Phase 3 helpers. `equivalentMarkers` feeds `initializeMarkerMappings` + * (lenient); `markerFilter` is whitespace-tokenized into a `Set` after backslash strip (§ + * 7.1 / VAL-001). + */ + markerSettings: { equivalentMarkers: string; markerFilter: string }; + /** + * Effective scripture range. `undefined` means "default range" (GEN 1:0 → REV 22:21). VAL-003 + * coerces `GEN 1:1 → GEN 1:0` to ensure intro material at verse 0 is included. + */ + verseRange: ScriptureRange | undefined; + /** When true (multi-column only), drop rows where `isMatch === true` and bump `excludedCount`. */ + hideMatches: boolean; + /** When false, postProcessParagraph emits ONLY the synthetic `\marker` label (BHV-103). */ + showVerseText: boolean; +}; + +/** + * Output shape. Mirrors the C# `ChecklistResult` record so the React layer doesn't need a separate + * adapter for the orchestrator path. Per consumer-inventory § 7.4, the orchestrator emits + * `undefined` for missing optional fields (Phase 3/4 convention); the React layer doesn't serialize + * through JSON-RPC so the difference is invisible at the UI boundary. + */ +export type ChecklistResult = { + rows: ChecklistRow[]; + columnHeaders: string[]; + columnProjectIds: string[]; + excludedCount: number; + helpText: string | undefined; + truncated: boolean; + emptyResultMessage: ChecklistEmptyResultMessage | undefined; +}; + +/** + * Thrown when a column projectId fails to resolve to a `platform.base` PDP (e.g. the project was + * removed or its GUID is malformed). The caller (`useDataProvider` in the web view) maps this to a + * user-facing error toast. + */ +export class ProjectNotFoundError extends Error { + readonly code = 'PROJECT_NOT_FOUND'; + readonly projectId: string; + + constructor(projectId: string) { + super(`Project not found: ${projectId}`); + this.name = 'ProjectNotFoundError'; + this.projectId = projectId; + } +} + +/** + * Top-level orchestrator — replaces backend `ChecklistService.BuildChecklistData`. + * + * Pipeline: 0. Resolve column project metadata (`platform.name` + `platform.fullName` per § 7.3). + * + * 1. Compute effective `[startRef, endRef]` (BHV-118 defaults + VAL-003 GEN 1:1 → GEN 1:0). + * 2. Parse marker settings via `initializeMarkerMappings` + tokenize `markerFilter` after backslash + * strip (§ 7.1 / VAL-001). + * 3. Compute book-number iteration list (intersect `platformScripture.booksPresent` with range). + * 4. Per column, per book: `fetchColumnBookData` → `walkParagraphMarkers` → `postProcessParagraph` → + * group walker paragraphs by `verseRefStart` into cells (§ 6). + * 5. Row alignment via `buildRowsMergingCells`. + * 6. Match detection (`hasSameValue`) + INV-002 single-column override. + * 7. `hideMatches` filter (drop matches, bump excludedCount). + * 8. Max-rows cap (INV-012; truncate at 5000). + * 9. EditLinkItem placement (BHV-114 / VAL-007 cond 4) — per editable cell with non-undefined + * reference, append `EditLinkItem` to the LAST paragraph's items. + * 10. Empty-result-message variant detection (BHV-106 / INV-008; § 7.2 gate on `markerFilter.size === + * 0` ONLY). + */ +export async function buildChecklistData( + request: ChecklistRequest, + papi: PapiLike, +): Promise { + const columnProjectIds = [request.projectId, ...request.comparativeTextIds]; + + // Step 0: resolve column project metadata (name + fullName). Both are fetched; only `name` is + // currently surfaced in the column header (§ 7.3) — `fullName` is reserved for the tooltip. + const columnMetas = await Promise.all( + columnProjectIds.map(async (id) => { + try { + const basePdp = await papi.projectDataProviders.get('platform.base', id); + const name = await basePdp.getSetting('platform.name'); + const fullName = await basePdp.getSetting('platform.fullName'); + return { + id, + name: typeof name === 'string' ? name : id, + fullName: typeof fullName === 'string' ? fullName : id, + }; + } catch { + throw new ProjectNotFoundError(id); + } + }), + ); + + // Step 1: effective verse range. Defaults: GEN 1:0 → REV 22:21 (intro verse 0 always included). + const startRef = applyVal003(request.verseRange?.start ?? defaultStartRef()); + const endRef = request.verseRange?.end ?? defaultEndRef(); + + // Step 2: parse marker settings. + const markerMappings = initializeMarkerMappings(request.markerSettings.equivalentMarkers); + const markerFilter = parseMarkerFilter(request.markerSettings.markerFilter); + + // Step 3: book-number iteration list (intersect booksPresent with range). + const primaryBasePdp = await papi.projectDataProviders.get('platform.base', request.projectId); + const booksPresentSetting = await primaryBasePdp.getSetting('platformScripture.booksPresent'); + const booksPresent = + typeof booksPresentSetting === 'string' ? booksPresentSetting : `1${'0'.repeat(122)}`; + const bookNumbers = computeBookNumbers(booksPresent, startRef, endRef); + + // Step 4: per (column, book) → cells. Walk + post-process + group by verseRefStart. We dispatch + // all (column, book) fetches in parallel via Promise.all — PDP fetches dominate the wall-clock + // cost, so the parallelism is meaningful; the synchronous walker/post-process/grouping work + // happens deterministically in the same order regardless. + const perColumnBookData = await Promise.all( + columnProjectIds.map((projectId) => + Promise.all(bookNumbers.map((bookNum) => fetchColumnBookData(papi, projectId, bookNum))), + ), + ); + + const columnsCells: ChecklistCell[][] = perColumnBookData.map((bookDataList) => + bookDataList.flatMap((data, bookIndex) => { + const walkerParagraphs = walkParagraphMarkers( + data.usj, + bookNumbers[bookIndex], + markerFilter, + data.headingMarkers, + ); + const processed = walkerParagraphs.map((p) => + postProcessParagraph(p, { showVerseText: request.showVerseText }), + ); + return groupParagraphsIntoCells(processed, data); + }), + ); + + // Editability is per-(project, book); for the EditLink gate we treat the column as editable + // when any book in that column is editable. In practice `isEditable` is a project-level setting, + // so this OR-reduce simply mirrors the project's flag — but doing it per-book keeps us correct + // if PDP-side gating ever differentiates. + const columnsEditability: boolean[] = perColumnBookData.map((bookDataList) => + bookDataList.some((data) => data.isEditable), + ); + + // Step 5: row alignment. + let rows = buildRowsMergingCells(columnsCells); + + // Populate row-level `includeEditLink` per c-sharp/Checklists/ChecklistRowBuilder.cs:712 — + // VAL-007 cond 2 row-level signal: the first cell of the row has a non-undefined reference. + rows = rows.map((r) => ({ + ...r, + includeEditLink: r.cells.length > 0 && r.cells[0].reference !== undefined, + })); + + // Step 6: match detection (+ INV-002 single-column override). + if (columnProjectIds.length === 1) { + rows = rows.map((r) => ({ ...r, isMatch: true })); + } else { + rows = rows.map((r) => ({ ...r, isMatch: hasSameValue(r, markerMappings) })); + } + + // Step 7: hideMatches filter (multi-column only — there's nothing to compare against in a + // single-column layout, so INV-002 forces every row to match and we'd drop everything). + let excludedCount = 0; + if (request.hideMatches && columnProjectIds.length > 1) { + const filtered: ChecklistRow[] = []; + rows.forEach((row) => { + if (row.isMatch) excludedCount += 1; + else filtered.push(row); + }); + rows = filtered; + } + + // Step 8: max-rows cap (INV-012). + const truncated = rows.length > MAX_ROWS; + if (truncated) rows = rows.slice(0, MAX_ROWS); + + // Step 9: EditLinkItem placement (BHV-114 / VAL-007 cond 4). Gate per-cell on per-column + // `isEditable` AND non-undefined `cell.reference` (an empty-placeholder cell has reference + // undefined). The link payload reads bookNum/chapterNum/verseNum from the cell's reference.start. + rows = rows.map((row) => ({ + ...row, + cells: row.cells.map((cell, columnIndex) => + maybeAppendEditLink(cell, columnsEditability[columnIndex]), + ), + })); + + // Step 10: empty-result-message variant detection. + const emptyResultMessage = + rows.length === 0 ? buildEmptyResultMessage(markerFilter, bookNumbers) : undefined; + + return { + rows, + columnHeaders: columnMetas.map((m) => m.name), + columnProjectIds, + excludedCount, + helpText: undefined, + truncated, + emptyResultMessage, + }; +} + +// ===== Helpers ===================================================================== + +/** + * Group walker paragraphs (carrying `verseRefStart`) into cells by distinct verseRef, preserving + * document order. Per consumer-inventory § 6, multiple paragraphs at the same verseRef merge into + * ONE cell — without this grouping, scenario-01's 24-paragraph intro would become 24 rows instead + * of 1. + * + * The walker-internal `verseRefStart` and `isHeading` fields are dropped here when converting + * `WalkerParagraph` → wire `ChecklistParagraph` (the wire shape carries only `marker` + `items`). + */ +function groupParagraphsIntoCells( + paragraphs: WalkerParagraph[], + data: ColumnBookData, +): ChecklistCell[] { + const cells: ChecklistCell[] = []; + let currentRef: SerializedVerseRef | undefined; + let currentParagraphs: ChecklistParagraph[] = []; + + const flush = () => { + if (currentParagraphs.length === 0 || !currentRef) return; + cells.push({ + paragraphs: currentParagraphs, + reference: { start: currentRef }, + language: data.joinedTextLanguage, + error: undefined, + }); + currentParagraphs = []; + }; + + paragraphs.forEach((p) => { + if (!currentRef || !verseRefsEqual(p.verseRefStart, currentRef)) { + flush(); + currentRef = p.verseRefStart; + } + // Convert walker → wire paragraph by stripping verseRefStart + isHeading. + currentParagraphs.push({ marker: p.marker, items: p.items }); + }); + flush(); + + return cells; +} + +function verseRefsEqual(a: SerializedVerseRef, b: SerializedVerseRef): boolean { + return a.book === b.book && a.chapterNum === b.chapterNum && a.verseNum === b.verseNum; +} + +/** + * BHV-114 / VAL-007 cond 4: append an `EditLinkItem` to the cell's LAST paragraph when the column + * is editable AND the cell has a non-undefined reference (mirrors C# + * `ChecklistService.ApplyEditLinkGating` at c-sharp/Checklists/ChecklistService.cs:565). Returns + * the cell unchanged when either gate fails or the cell has zero paragraphs (defensive). + */ +function maybeAppendEditLink(cell: ChecklistCell, columnIsEditable: boolean): ChecklistCell { + if (!columnIsEditable) return cell; + if (!cell.reference) return cell; + if (cell.paragraphs.length === 0) return cell; + + const vref = cell.reference.start; + const editLink: EditLinkItem = { + type: 'editLink', + bookNum: Canon.bookIdToNumber(vref.book), + chapterNum: vref.chapterNum, + verseNum: vref.verseNum, + }; + + const lastIndex = cell.paragraphs.length - 1; + const lastParagraph = cell.paragraphs[lastIndex]; + const updatedParagraphs = cell.paragraphs.map((p, i) => + i === lastIndex ? { ...lastParagraph, items: [...lastParagraph.items, editLink] } : p, + ); + return { ...cell, paragraphs: updatedParagraphs }; +} + +/** Default range start — intro material (verse 0) of Genesis 1. */ +function defaultStartRef(): SerializedVerseRef { + return { book: 'GEN', chapterNum: 1, verseNum: 0 }; +} + +/** Default range end — last verse of Revelation. */ +function defaultEndRef(): SerializedVerseRef { + return { book: 'REV', chapterNum: 22, verseNum: 21 }; +} + +/** + * VAL-003: coerce `GEN 1:1` → `GEN 1:0` so intro material (\ip at verse 0) is always included when + * the request starts at the canonical first verse. + */ +function applyVal003(ref: SerializedVerseRef): SerializedVerseRef { + if (ref.book === 'GEN' && ref.chapterNum === 1 && ref.verseNum === 1) { + return { ...ref, verseNum: 0 }; + } + return ref; +} + +/** + * Compute the inclusive book-number iteration list. Intersects the `booksPresent` 123-char binary + * string (position N = book number N, '1' = present) with the requested `[startBook, endBook]` + * range. Ordering follows the canonical book-number sequence. + */ +function computeBookNumbers( + booksPresent: string, + startRef: SerializedVerseRef, + endRef: SerializedVerseRef, +): number[] { + const startBookNum = Canon.bookIdToNumber(startRef.book); + const endBookNum = Canon.bookIdToNumber(endRef.book); + const result: number[] = []; + for (let n = startBookNum; n <= endBookNum; n += 1) { + if (booksPresent.charAt(n - 1) === '1') result.push(n); + } + return result; +} + +/** + * Parse the user-entered `markerFilter` string. § 7.1 / VAL-001: strip backslashes first (a user + * typing `\p \q` produces `{p, q}`, not `{\p, \q}`), then trim + whitespace-split into a Set. + */ +function parseMarkerFilter(input: string): ReadonlySet { + const stripped = input.replace(/\\/g, ''); + return new Set( + stripped + .trim() + .split(/\s+/) + .filter((s) => s.length > 0), + ); +} + +/** + * § 7.2: gate the `identical` variant on `markerFilter.size === 0` ONLY (no comparativeCount gate). + * When the filter is empty, the empty result means "no differences found" → identical markers. When + * the filter is non-empty, the empty result means "no paragraphs matched the filter" → noResults, + * carrying searchedMarkers + searchedBooks for the localized template substitution. + */ +function buildEmptyResultMessage( + markerFilter: ReadonlySet, + bookNumbers: readonly number[], +): ChecklistEmptyResultMessage { + if (markerFilter.size === 0) { + return { + variant: 'identical', + message: IDENTICAL_MARKERS_MESSAGE_KEY, + searchedMarkers: undefined, + searchedBooks: undefined, + }; + } + return { + variant: 'noResults', + message: '', + searchedMarkers: [...markerFilter], + searchedBooks: bookNumbers.map((n) => Canon.bookNumberToId(n)), + }; +} From fdc5c5c64a37f42982b1420f02e732f654fbe267 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:57:57 +0200 Subject: [PATCH 17/43] refactor(markers-checklist): rewire web view to local TS orchestrator + project settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buildChecklistData now invoked locally (TS); no network-object roundtrip (addresses TJ C3). - Marker settings storage: useWebViewState → useProjectSetting (cross-web-view persistence on the same project; addresses TJ C3a). - validateMarkerSettings call replaced with local parseMarkerSettings (no per-keystroke PAPI roundtrip; addresses TJ C3b). The validator adapter pre-localizes the parser's errorMessageKey before passing it to the dialog (consumer-inventory § 4, Case A). - isEditable: orchestrator reads platform.isEditable per-(column, book) via the PDP; the previous useChecklistService hook is no longer needed. - MarkerSettingsValidationResult inlined into marker-settings-dialog.component so the dialog is self-contained (the public contract type goes away in 6.3). --- .../src/checklist.web-view.tsx | 193 +++++++++++------- .../marker-settings-dialog.component.tsx | 31 ++- 2 files changed, 147 insertions(+), 77 deletions(-) diff --git a/extensions/src/platform-scripture/src/checklist.web-view.tsx b/extensions/src/platform-scripture/src/checklist.web-view.tsx index 4c6ccdb6726..a2bd7960dea 100644 --- a/extensions/src/platform-scripture/src/checklist.web-view.tsx +++ b/extensions/src/platform-scripture/src/checklist.web-view.tsx @@ -1,6 +1,11 @@ import { WebViewProps } from '@papi/core'; import papi, { logger, network } from '@papi/frontend'; -import { useData, useLocalizedStrings, useProjectDataProvider } from '@papi/frontend/react'; +import { + useData, + useLocalizedStrings, + useProjectDataProvider, + useProjectSetting, +} from '@papi/frontend/react'; import { useEvent, ProjectSelector, @@ -20,29 +25,38 @@ import { isPlatformError, } from 'platform-bible-utils'; import { Canon, type SerializedVerseRef } from '@sillsdev/scripture'; -import type { - ChecklistComparativeTextRef, - ChecklistRequest, - ChecklistResultResponse, - ScriptureRange, -} from 'platform-scripture'; +import type { ScriptureRange } from 'platform-scripture'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChecklistTool, CHECKLIST_STRING_KEYS } from './components/checklist.component'; -import type { - ChecklistCell, - ChecklistData, - ChecklistEmptyResultMessage, - ChecklistRow, -} from './components/checklist.component'; +import type { ChecklistCell, ChecklistData, ChecklistRow } from './components/checklist.component'; import { MarkerSettingsDialog, MARKER_SETTINGS_STRING_KEYS, + type MarkerSettingsValidate, } from './components/marker-settings-dialog.component'; -import { useChecklistService } from './hooks/use-checklist'; +import { + buildChecklistData, + ProjectNotFoundError, + type ChecklistRequest, + type ChecklistResult, +} from './checklists/build-checklist-data'; +import { + parseMarkerSettings, + INVALID_MARKER_PAIR_ERROR_KEY, +} from './checklists/parse-marker-settings'; import { useOpenProjectTabs } from './hooks/use-open-project-tabs'; import { computeRangeFromScope } from './components/compute-range-from-scope.utils'; import { CHECKLIST_OPEN_SETTINGS_EVENT } from './checklist.model'; +/** + * Lightweight local persistence shape for a comparative-text selection. Previously imported as + * `ChecklistComparativeTextRef` from `platform-scripture`; that contract type was removed when the + * checklist NetworkObject was deleted (the web view runs its orchestration locally now). Kept as a + * plain `{ id: string }` so the existing `useWebViewState` schema for `checklistComparativeTexts` + * remains unchanged. + */ +type ComparativeTextRef = { id: string }; + // ─── Constants ───────────────────────────────────────────────────────────── /** @@ -61,27 +75,21 @@ const MARKERS_CHECKLIST_WEB_VIEW_TYPE = 'platformScripture.markersChecklist'; // ─── Helpers ─────────────────────────────────────────────────────────────── /** - * Narrow the discriminated-union `ChecklistResultResponse` success body to the `ChecklistData` - * shape the presentational component consumes. The wire format uses `unknown[]` + `unknown` for the - * rows + empty-result message (see `platform-scripture.d.ts` §Markers Checklist Types); both are - * validated upstream by the backend, so we cast through `unknown` to the stricter component types - * here. If the response shape ever drifts, TypeScript will flag the direct access sites below - * (row/cell/emptyResultMessage destructuring) before this hidden cast blows up. + * Adapt the orchestrator's `ChecklistResult` (from `./checklists/build-checklist-data`) to the + * `ChecklistData` shape the presentational component consumes. The two type families are + * structurally aligned (same `cells`/`paragraphs`/`items` shapes); the orchestrator's row type + * carries an extra `score` field which the component ignores. The `emptyResultMessage.message` + * field is a `LocalizeKey | string` on the orchestrator side; either form is assignable to the + * component's `string` field at runtime (the React layer resolves localize keys when rendering). */ -function toChecklistData(body: Extract): ChecklistData { +function toChecklistData(body: ChecklistResult): ChecklistData { return { - // The backend already emits the structural shape the component expects; we trust the contract - // and narrow via `unknown` so we don't have to re-validate every field at runtime. Any future - // drift will surface at the row/cell destructuring sites below. - // eslint-disable-next-line no-type-assertion/no-type-assertion - rows: body.rows as unknown as ChecklistRow[], + rows: body.rows, columnHeaders: body.columnHeaders, columnProjectIds: body.columnProjectIds, excludedCount: body.excludedCount, truncated: body.truncated, - // Same rationale as the rows cast — trust the data-contract. - // eslint-disable-next-line no-type-assertion/no-type-assertion - emptyResultMessage: body.emptyResultMessage as ChecklistEmptyResultMessage | undefined, + emptyResultMessage: body.emptyResultMessage, }; } @@ -154,11 +162,39 @@ global.webViewComponent = function ChecklistWebView({ // (parity with checks-side-panel Tasks 13/14); omitted here until that UI is wired. const [liveScrRef, setLiveScrRef, scrollGroupId] = useWebViewScrollGroupScrRef(); - const [equivalentMarkers, setEquivalentMarkers] = useWebViewState( - 'checklistEquivalentMarkers', + // Marker settings now persist as PROJECT settings (cross-web-view persistence on the same + // project — TJ C3a). Both keys default to '' to match the previous `useWebViewState` semantics. + // `useProjectSetting` may surface a `PlatformError` when the PDP isn't ready yet (e.g. during + // initial mount); we narrow to the empty default in that case. + const [equivalentMarkersPossiblyError, setEquivalentMarkers] = useProjectSetting( + projectId, + 'platformScripture.checklistEquivalentMarkers', + '', + ); + const equivalentMarkers = useMemo(() => { + if (isPlatformError(equivalentMarkersPossiblyError)) { + logger.warn( + `ChecklistWebView: error reading checklistEquivalentMarkers: ${getErrorMessage(equivalentMarkersPossiblyError)}`, + ); + return ''; + } + return equivalentMarkersPossiblyError; + }, [equivalentMarkersPossiblyError]); + + const [markerFilterPossiblyError, setMarkerFilter] = useProjectSetting( + projectId, + 'platformScripture.checklistMarkerFilter', '', ); - const [markerFilter, setMarkerFilter] = useWebViewState('checklistMarkerFilter', ''); + const markerFilter = useMemo(() => { + if (isPlatformError(markerFilterPossiblyError)) { + logger.warn( + `ChecklistWebView: error reading checklistMarkerFilter: ${getErrorMessage(markerFilterPossiblyError)}`, + ); + return ''; + } + return markerFilterPossiblyError; + }, [markerFilterPossiblyError]); const [hideMatches, setHideMatches] = useWebViewState('checklistHideMatches', false); const [showVerseText, setShowVerseText] = useWebViewState( 'checklistShowVerseText', @@ -166,7 +202,7 @@ global.webViewComponent = function ChecklistWebView({ ); // Comparative-texts selection is driven by the real `ProjectSelector` (`mode: 'project-multi'`, // vendored from draft PR #2223). - const [comparativeTexts, setComparativeTexts] = useWebViewState( + const [comparativeTexts, setComparativeTexts] = useWebViewState( 'checklistComparativeTexts', [], ); @@ -213,9 +249,12 @@ global.webViewComponent = function ChecklistWebView({ const scopeSelectorStringKeys = useMemo(() => Array.from(SCOPE_SELECTOR_STRING_KEYS), []); const [scopeSelectorLocalizedStrings] = useLocalizedStrings(scopeSelectorStringKeys); - // ─── Service + editability ──────────────────────────────────────────────── - - const { service } = useChecklistService(projectId); + // ─── Editability ───────────────────────────────────────────────────────── + // + // The orchestrator (`buildChecklistData` in `./checklists/`) reads `platform.isEditable` + // internally per-(column, book) via the PDP and gates EditLinkItem placement on its own; there is + // no per-cell affordance keyed off a single project-level flag in this web view. The previous + // `useChecklistService` hook is gone (no more NetworkObject roundtrip) — see Phase 6 design notes. // ─── Local UI state (ephemeral) ────────────────────────────────────────── @@ -294,7 +333,7 @@ global.webViewComponent = function ChecklistWebView({ const [refreshCounter, setRefreshCounter] = useState(0); useEffect(() => { - if (!service || !projectId) { + if (!projectId) { setData(undefined); setIsLoading(false); return () => {}; @@ -305,9 +344,9 @@ global.webViewComponent = function ChecklistWebView({ comparativeTextIds: comparativeTexts.map((ref) => ref.id), markerSettings: { equivalentMarkers, markerFilter }, verseRange, - // hideMatches/showVerseText are post-fetch filters; we pass them to the backend anyway so - // the `excludedCount` reflects what would be hidden if the filter were applied server-side, - // but we also filter client-side below for the visible-rows path. + // hideMatches/showVerseText are post-fetch filters; we pass them to the orchestrator so the + // `excludedCount` reflects what would be hidden if the filter were applied at build-time, but + // we also filter client-side below for the visible-rows path. hideMatches, showVerseText, }; @@ -316,23 +355,21 @@ global.webViewComponent = function ChecklistWebView({ let cancelled = false; (async () => { try { - const response = await service.buildChecklistData(request); + // Orchestrator runs locally (no NetworkObject roundtrip). Errors surface via throw — + // `ProjectNotFoundError` for an unresolvable project id, generic `Error` otherwise. + const response = await buildChecklistData(request, papi); if (cancelled || !isMountedRef.current) return; - // `ChecklistResultResponse` is a TS-only discriminated union; the C# side never sends - // a `success` field — narrowing is on the presence of `rows` (success shape) vs `code` - // (ChecklistResultError shape). See data-contracts.md §3.1. - if ('rows' in response) { - // The `'rows' in response` narrowing already proves response is the success variant, - // so we can pass it through without further casting. - setData(toChecklistData(response)); - setError(undefined); - } else { - setData(undefined); - setError(response.message); - } + setData(toChecklistData(response)); + setError(undefined); } catch (err) { if (cancelled || !isMountedRef.current) return; - logger.warn(`ChecklistWebView: buildChecklistData failed: ${getErrorMessage(err)}`); + if (err instanceof ProjectNotFoundError) { + logger.warn( + `ChecklistWebView: project not found (${err.projectId}): ${getErrorMessage(err)}`, + ); + } else { + logger.warn(`ChecklistWebView: buildChecklistData failed: ${getErrorMessage(err)}`); + } setData(undefined); setError(getErrorMessage(err)); } finally { @@ -343,7 +380,6 @@ global.webViewComponent = function ChecklistWebView({ cancelled = true; }; }, [ - service, projectId, comparativeTexts, equivalentMarkers, @@ -645,7 +681,7 @@ global.webViewComponent = function ChecklistWebView({ const handleComparativeTextsChange = useCallback( (selection: { pairs: ProjectSelectorProjectPair[] }) => { - const nextRefs: ChecklistComparativeTextRef[] = selection.pairs.map((pair) => ({ + const nextRefs: ComparativeTextRef[] = selection.pairs.map((pair) => ({ id: pair.projectId, })); setComparativeTexts(nextRefs); @@ -730,29 +766,42 @@ global.webViewComponent = function ChecklistWebView({ }) => { // Collapse internal whitespace runs in the equivalent-markers string before persisting (the // dialog just trims now per Sebastian PR #2219 #3138226285 — validation/normalization - // concerns moved out of the presentational component). The backend stores the value - // verbatim; we keep the canonical wire format here so it matches what the backend's - // validateMarkerSettings parsing expects. - setEquivalentMarkers(nextEquivalent.replace(/\s+/g, ' ')); - setMarkerFilter(nextFilter); + // concerns moved out of the presentational component). The persisted form is the canonical + // wire format that `parseMarkerSettings` expects. The `useProjectSetting` setters are + // `undefined` while the underlying PDP is still resolving — optional-chain to silently no-op + // in that brief window (matches the inventory.web-view pattern). + setEquivalentMarkers?.(nextEquivalent.replace(/\s+/g, ' ')); + setMarkerFilter?.(nextFilter); setIsSettingsOpen(false); }, [setEquivalentMarkers, setMarkerFilter], ); - // Backend validation callback for the MarkerSettingsDialog. Calls the backend's - // `validateMarkerSettings` PAPI command via the network-object proxy. The dialog calls this - // (debounced) on every input change so the inline validation feedback reflects backend truth. - // If the service proxy isn't available yet (e.g. during initial mount), return a permissive - // valid result — the dialog will retry on the next input change once the proxy resolves. - const handleSettingsValidate = useCallback( - async (input: string) => { - if (!service) { - return { valid: true, parsedPairs: undefined, errorMessage: undefined }; + // Validation callback for the MarkerSettingsDialog. Runs the pure-TS `parseMarkerSettings` + // (no PAPI roundtrip; addresses TJ C3b) and adapts its key-based result into the dialog's + // pre-localized `MarkerSettingsValidationResult` shape (consumer-inventory § 4, Case A). The + // English fallback mirrors PT9's `MarkerSettingsForm` error string and only renders when the + // localized strings haven't loaded yet. + const [markerSettingsResolvedStrings] = markerSettingsLocalizedStrings; + const handleSettingsValidate = useCallback( + (equivalentMarkersInput: string) => { + const result = parseMarkerSettings(equivalentMarkersInput); + if (result.valid) { + return { + valid: true, + parsedPairs: result.parsedPairs, + errorMessage: undefined, + }; } - return service.validateMarkerSettings(input); + return { + valid: false, + parsedPairs: undefined, + errorMessage: + markerSettingsResolvedStrings[INVALID_MARKER_PAIR_ERROR_KEY] ?? + 'Equivalent markers need to be entered in the form: p/q', + }; }, - [service], + [markerSettingsResolvedStrings], ); const handleSettingsCancel = useCallback(() => { diff --git a/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx b/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx index 4f928049ea8..965d3347f3f 100644 --- a/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx +++ b/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx @@ -14,7 +14,6 @@ import { TooltipTrigger, } from 'platform-bible-react'; import type { LocalizedStringValue } from 'platform-bible-utils'; -import type { MarkerSettingsValidationResult } from 'platform-scripture'; import { HelpCircle } from 'lucide-react'; import type { KeyboardEvent } from 'react'; import { useCallback, useEffect, useId, useRef, useState } from 'react'; @@ -67,10 +66,32 @@ export type MarkerSettingsSubmitResult = { }; /** - * Validate-callback signature. The wiring layer wraps the backend - * `IChecklistService.validateMarkerSettings(equivalentMarkers)` call (or, in stories, a simple - * synchronous regex) and supplies the result. Returns the same `MarkerSettingsValidationResult` - * shape as the backend so `errorMessage` can be displayed inline without re-localization. + * Parsed/validated equivalent-markers result, used by the Marker Settings Dialog. Previously lived + * on the public `platform-scripture` contract (mirrored the C# `MarkerSettingsValidationResult` + * DTO); the contract type was removed when the checklist NetworkObject was deleted, and the + * dialog's adapter shape now lives here next to its only consumer. + */ +export type MarkerSettingsValidationResult = { + /** True when the string is well-formed; false when `errorMessage` is set. */ + valid: boolean; + /** + * Parsed pairs when `valid` is true; `undefined` otherwise. Each entry is one marker pair as `{ + * marker1, marker2 }` (e.g. `{ marker1: 'p', marker2: 'q' }`). + */ + parsedPairs: { marker1: string; marker2: string }[] | undefined; + /** + * Already-localized error message when `valid` is false; `undefined` otherwise. The wiring layer + * resolves any localize-key produced by the underlying parser into a display string before + * passing it here. + */ + errorMessage: string | undefined; +}; + +/** + * Validate-callback signature. The wiring layer wraps a pure-TS marker-settings parser (or, in + * stories, a simple synchronous regex) and supplies the result. Returns the dialog's + * `MarkerSettingsValidationResult` so `errorMessage` can be displayed inline without further + * localization. */ export type MarkerSettingsValidate = ( equivalentMarkers: string, From e6d723b827f74ed04cae0c9ed6639671471d85bd Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 21:58:28 +0200 Subject: [PATCH 18/43] chore(markers-checklist): delete useChecklistService hook (no longer used) --- .../src/hooks/use-checklist.ts | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 extensions/src/platform-scripture/src/hooks/use-checklist.ts diff --git a/extensions/src/platform-scripture/src/hooks/use-checklist.ts b/extensions/src/platform-scripture/src/hooks/use-checklist.ts deleted file mode 100644 index 40e9af8d78a..00000000000 --- a/extensions/src/platform-scripture/src/hooks/use-checklist.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { useEffect, useState } from 'react'; -import papi, { logger } from '@papi/frontend'; -import { getErrorMessage } from 'platform-bible-utils'; -import type { IChecklistService } from 'platform-scripture'; - -/** Network object name for the Markers Checklist service (see data-contracts.md §4). */ -export const CHECKLIST_SERVICE_NAME = 'platformScripture.checklistService'; - -/** - * Return shape of {@link useChecklistService}. - * - * `service` is `undefined` while the NetworkObject proxy is being acquired (or if acquisition - * fails). `isEditable` reflects the project-level `platform.isEditable` setting and gates the - * editor-launch affordance. It defaults to `false` until the PDP has been read. - */ -export interface UseChecklistServiceResult { - service: IChecklistService | undefined; - isEditable: boolean; -} - -/** - * Acquires the Markers Checklist NetworkObject proxy and reads the project-level - * `platform.isEditable` setting for the supplied `projectId`. - * - * Uses `papi.networkObjects.get(...)` (NOT `useDataProvider`) because the - * checklist server surface is a plain NetworkObject with no get/set/subscribe triplet (see - * `.context/features/markers-checklist/implementation/ui-alignment.md`, Network Object - * Connection). - * - * This is the SCAFFOLD wiring produced by UI-PKG-001; full consumption of the returned service (the - * actual `buildChecklistData(...)` call + state wiring) lands in UI-PKG-002. - */ -export function useChecklistService(projectId: string | undefined): UseChecklistServiceResult { - const [service, setService] = useState(); - const [isEditable, setIsEditable] = useState(false); - - // Acquire the NetworkObject proxy once. - useEffect(() => { - let cancelled = false; - (async () => { - try { - const proxy = await papi.networkObjects.get(CHECKLIST_SERVICE_NAME); - if (!cancelled) setService(proxy); - } catch (error) { - if (!cancelled) - logger.warn(`useChecklistService: failed to acquire proxy: ${getErrorMessage(error)}`); - } - })(); - return () => { - cancelled = true; - }; - }, []); - - // Read `platform.isEditable` from the project's base PDP. Skipped when projectId is undefined - // (e.g. scaffold-only renders) — stays at the default `false`. - useEffect(() => { - if (!projectId) { - setIsEditable(false); - return () => {}; - } - let cancelled = false; - (async () => { - try { - const pdp = await papi.projectDataProviders.get('platform.base', projectId); - const value = await pdp.getSetting('platform.isEditable'); - if (!cancelled) setIsEditable(Boolean(value)); - } catch (error) { - if (!cancelled) { - logger.warn( - `useChecklistService: failed to read platform.isEditable for ${projectId}: ${getErrorMessage(error)}`, - ); - setIsEditable(false); - } - } - })(); - return () => { - cancelled = true; - }; - }, [projectId]); - - return { service, isEditable }; -} - -export default useChecklistService; From 6c6283bfbffefe0ea142c90e7e8f42051796f496 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:01:23 +0200 Subject: [PATCH 19/43] chore(markers-checklist): remove IChecklistService + Markers Checklist Types region The backend ChecklistNetworkObject is gone; no consumer should see these types on the contract surface. TS-local types live in extensions/src/platform-scripture/src/checklists/checklist.types.ts. --- .../src/types/platform-scripture.d.ts | 155 ------------------ 1 file changed, 155 deletions(-) diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 38ca6f79f54..6969acc5446 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -1941,161 +1941,6 @@ declare module 'platform-scripture' { }; // #endregion Resource Reference Types - - // #region Markers Checklist Types - - /** Configuration for the Markers Checklist's "equivalent markers" and marker-filter inputs. */ - export type ChecklistMarkerSettings = { - /** - * Space-separated marker pairs in `"marker1/marker2"` format identifying markers that should be - * treated as equivalent when "Hide matches" is enabled. For example, `"p/q s/s1"` declares two - * pairs (`p`↔`q` and `s`↔`s1`). An empty string declares no equivalents. - */ - equivalentMarkers: string; - /** - * Space-separated USFM marker names to include in the checklist (e.g. `"p q m"`). When empty, - * all paragraph markers in the project are included. - */ - markerFilter: string; - }; - - /** - * Identifies a comparative text (a second project) to include alongside the main project. - * Resolution is GUID-only — projects are always identified by their canonical GUID. - */ - export type ChecklistComparativeTextRef = { - /** GUID of the comparative text. */ - id: string; - }; - - /** Primary input for {@link IChecklistService.buildChecklistData}. */ - export type ChecklistRequest = { - /** Project ID of the main (active) project the checklist is built for. */ - projectId: string; - /** - * GUIDs of any comparative texts to render as additional columns alongside the main project. - * Empty array means single-column mode. - */ - comparativeTextIds: string[]; - /** Marker settings — see {@link ChecklistMarkerSettings}. */ - markerSettings: ChecklistMarkerSettings; - /** - * Inclusive verse range to limit the checklist to. When `undefined`, the entire project is - * scanned. Otherwise the standard {@link ScriptureRange} semantics apply: an omitted `end` - * narrows the request to the single verse at `start`. - */ - verseRange: ScriptureRange | undefined; - /** When true, rows whose cells all match (per `equivalentMarkers`) are omitted from output. */ - hideMatches: boolean; - /** When true, the full verse text is included alongside each marker occurrence. */ - showVerseText: boolean; - }; - - /** - * Discriminated-union response wrapper for {@link IChecklistService.buildChecklistData}. The - * `success` field distinguishes a successful payload from a structured error. Row shape (the - * `unknown[]` payload) is intentionally untyped at the wire boundary because it carries TanStack - * Table-ready render data that the web view casts to its local row type; consumers should not - * rely on the row's internal structure across major versions. - */ - export type ChecklistResultResponse = - | { - success: true; - /** TanStack-Table-ready row objects. Consumers cast to their local row type. */ - rows: unknown[]; - /** One header label per column (main project first, then each comparative text). */ - columnHeaders: string[]; - /** Project ID per column, parallel to `columnHeaders`. */ - columnProjectIds: string[]; - /** - * Number of rows the engine produced but suppressed (e.g. matches hidden by `hideMatches`). - * Surface to the user so they understand why some content is missing. - */ - excludedCount: number; - /** Optional contextual help text rendered above the table. */ - helpText: string | undefined; - /** True when the result was truncated at the engine's maximum row cap. */ - truncated: boolean; - /** - * Present only when `rows` is empty: a structured description of _why_ (e.g. "identical - * markers" vs "no results"). The web view renders this in a localized empty state. Shape - * mirrors the C# `EmptyResultMessage` DTO; the field is `unknown` at the wire boundary. - */ - emptyResultMessage: unknown | undefined; - } - | { - success: false; - /** Stable error code (e.g. `"PROJECT_NOT_FOUND"`). */ - code: string; - /** Already-localized error message safe to display to users. */ - message: string; - }; - - /** Parsed/validated equivalent-markers string, used by the Marker Settings dialog. */ - export type MarkerSettingsValidationResult = { - /** True when the string is well-formed; false when `errorMessage` is set. */ - valid: boolean; - /** - * Parsed pairs when `valid` is true; `undefined` otherwise. Each entry is one marker pair as `{ - * marker1, marker2 }` (e.g. `{ marker1: 'p', marker2: 'q' }`). - */ - parsedPairs: { marker1: string; marker2: string }[] | undefined; - /** - * Already-localized error message when `valid` is false; `undefined` otherwise. The message - * describes the first parse failure encountered. - */ - errorMessage: string | undefined; - }; - - /** Resolved comparative-text payload returned by {@link IChecklistService.resolveComparativeTexts}. */ - export type ResolvedComparativeTexts = { - /** Resolution result per requested reference, parallel to the request array. */ - texts: { - /** GUID of the resolved project (or the original requested id if resolution failed). */ - id: string; - /** Short name of the resolved project, or `''` when `available` is `false`. */ - name: string; - /** Full descriptive name of the resolved project, or `''` when `available` is `false`. */ - fullName: string; - /** True when the project was successfully resolved and is currently registered. */ - available: boolean; - }[]; - }; - - /** - * Typed proxy for the `platformScripture.checklistService` NetworkObject. Provides on-demand - * computation for the Markers Checklist web view: building checklist data over a given project + - * scope, validating the user's equivalent-markers string, and resolving comparative-text refs. - * - * The service is stateless RPC — no internal cache, no update notifications. Consumers re-call - * `buildChecklistData` when their inputs change (project, scope, settings, comparative texts). - * - * Acquire via `papi.networkObjects.get('platformScripture.checklistService')`. - */ - export interface IChecklistService { - /** - * Build the checklist row data for the supplied request. Resolves comparative-text projects - * internally. The response is a discriminated union of success/error. - */ - buildChecklistData(request: ChecklistRequest): Promise; - /** - * Validate an equivalent-markers configuration string (e.g. `"p/q s/s1"`). Returns parsed pairs - * on success or a localized error message on failure. Pure function — no project access. - */ - validateMarkerSettings(equivalentMarkers: string): Promise; - /** - * Resolve comparative-text references for a given active project. Each reference is resolved by - * GUID against the registered project collection (see {@link ChecklistComparativeTextRef}). - * Returns one entry per requested reference, in the same order; the active project is excluded; - * entries whose GUID does not resolve are returned with `available: false`. - */ - resolveComparativeTexts( - activeProjectId: string, - requestedTexts: ChecklistComparativeTextRef[], - ): Promise; - } - - // #endregion Markers Checklist Types } declare module 'papi-shared-types' { From e8333dba97cd146d85c3ac49292e8e21966421c6 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:19:38 +0200 Subject: [PATCH 20/43] chore(markers-checklist): delete c-sharp/Checklists/ directory + ChecklistNetworkObject registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ~2600 lines of C# removed. Aggregation now lives entirely in TS (extensions/src/platform-scripture/src/checklists/). See tj-review-design.md §2.6. --- c-sharp/Checklists/ChecklistContentItem.cs | 37 - c-sharp/Checklists/ChecklistErrorCodes.cs | 25 - c-sharp/Checklists/ChecklistNetworkObject.cs | 213 ---- .../Checklists/ChecklistParagraphTokens.cs | 59 - c-sharp/Checklists/ChecklistRequest.cs | 30 - c-sharp/Checklists/ChecklistResult.cs | 76 -- c-sharp/Checklists/ChecklistResultError.cs | 21 - c-sharp/Checklists/ChecklistRowBuilder.cs | 813 ------------ c-sharp/Checklists/ChecklistService.cs | 1114 ----------------- c-sharp/Checklists/ComparativeTextRef.cs | 19 - c-sharp/Checklists/EditLinkItem.cs | 16 - c-sharp/Checklists/EmptyResultMessage.cs | 22 - .../Checklists/EmptyResultMessageVariant.cs | 31 - c-sharp/Checklists/ErrorItem.cs | 15 - c-sharp/Checklists/LinkItem.cs | 18 - c-sharp/Checklists/Markers/MarkerPair.cs | 15 - c-sharp/Checklists/Markers/MarkerSettings.cs | 17 - .../Markers/MarkerSettingsValidationResult.cs | 21 - .../Checklists/Markers/MarkersDataSource.cs | 490 -------- c-sharp/Checklists/MessageItem.cs | 15 - c-sharp/Checklists/ResolvedComparativeText.cs | 26 - .../Checklists/ResolvedComparativeTexts.cs | 28 - c-sharp/Checklists/TextItem.cs | 14 - c-sharp/Checklists/VerseItem.cs | 14 - c-sharp/Program.cs | 3 - 25 files changed, 3152 deletions(-) delete mode 100644 c-sharp/Checklists/ChecklistContentItem.cs delete mode 100644 c-sharp/Checklists/ChecklistErrorCodes.cs delete mode 100644 c-sharp/Checklists/ChecklistNetworkObject.cs delete mode 100644 c-sharp/Checklists/ChecklistParagraphTokens.cs delete mode 100644 c-sharp/Checklists/ChecklistRequest.cs delete mode 100644 c-sharp/Checklists/ChecklistResult.cs delete mode 100644 c-sharp/Checklists/ChecklistResultError.cs delete mode 100644 c-sharp/Checklists/ChecklistRowBuilder.cs delete mode 100644 c-sharp/Checklists/ChecklistService.cs delete mode 100644 c-sharp/Checklists/ComparativeTextRef.cs delete mode 100644 c-sharp/Checklists/EditLinkItem.cs delete mode 100644 c-sharp/Checklists/EmptyResultMessage.cs delete mode 100644 c-sharp/Checklists/EmptyResultMessageVariant.cs delete mode 100644 c-sharp/Checklists/ErrorItem.cs delete mode 100644 c-sharp/Checklists/LinkItem.cs delete mode 100644 c-sharp/Checklists/Markers/MarkerPair.cs delete mode 100644 c-sharp/Checklists/Markers/MarkerSettings.cs delete mode 100644 c-sharp/Checklists/Markers/MarkerSettingsValidationResult.cs delete mode 100644 c-sharp/Checklists/Markers/MarkersDataSource.cs delete mode 100644 c-sharp/Checklists/MessageItem.cs delete mode 100644 c-sharp/Checklists/ResolvedComparativeText.cs delete mode 100644 c-sharp/Checklists/ResolvedComparativeTexts.cs delete mode 100644 c-sharp/Checklists/TextItem.cs delete mode 100644 c-sharp/Checklists/VerseItem.cs diff --git a/c-sharp/Checklists/ChecklistContentItem.cs b/c-sharp/Checklists/ChecklistContentItem.cs deleted file mode 100644 index ed0727cfc20..00000000000 --- a/c-sharp/Checklists/ChecklistContentItem.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists (CLText/CLVerse/CLEditLink/CLLink/CLError/CLMessage -// content-item hierarchy) -// Method: ChecklistContentItem (base type only; concrete subtypes in sibling files) -// Maps to: EXT-010 (data models) -// -// EXPLANATION: -// Polymorphic hierarchy over the PAPI boundary. The TypeScript side (data-contracts.md -// §3.5) models these as a discriminated union with a lowercase `type` field literal -// per subtype (`'text'`, `'verse'`, `'editLink'`, `'link'`, `'error'`, `'message'`). -// On the C# side we mirror that wire shape with [JsonPolymorphic] + -// [JsonDerivedType(...)] so System.Text.Json emits a `type` discriminator property on -// serialize and routes to the correct subtype on deserialize. -// -// The explicit BE-1 early-verification test lives in -// c-sharp-tests/Checklists/ChecklistContentItemPolymorphismTests.cs. If that suite -// ever regresses, fall back to an explicit JsonConverter and -// escalate before BE-2 starts (per strategic-plan risk RF-SP). -/// -/// Abstract base type for polymorphic checklist content items. Each concrete subtype -/// lives in its own file alongside this base (one type per file, per PNX004). -/// Serializes/deserializes via the type discriminator wired by the -/// [JsonPolymorphic] / [JsonDerivedType] attributes below, matching -/// the TypeScript discriminated union in data-contracts.md §3.5. -/// -[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] -[JsonDerivedType(typeof(TextItem), "text")] -[JsonDerivedType(typeof(VerseItem), "verse")] -[JsonDerivedType(typeof(EditLinkItem), "editLink")] -[JsonDerivedType(typeof(LinkItem), "link")] -[JsonDerivedType(typeof(ErrorItem), "error")] -[JsonDerivedType(typeof(MessageItem), "message")] -public abstract record ChecklistContentItem; diff --git a/c-sharp/Checklists/ChecklistErrorCodes.cs b/c-sharp/Checklists/ChecklistErrorCodes.cs deleted file mode 100644 index d43af83ac89..00000000000 --- a/c-sharp/Checklists/ChecklistErrorCodes.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 surfaces errors via WinForms MessageBox with localized strings, not via -// structured error codes. PT10 uses a machine-readable error-code wire contract so the -// TypeScript web view can branch on error type deterministically. -// Maps to: data-contracts.md §3.6 (ChecklistErrorCode union) -/// -/// Error-code string constants for the checklist PAPI surface. Values are pinned -/// bit-for-bit to the TypeScript ChecklistErrorCode union in -/// data-contracts.md §3.6 — changing any of these is a wire-breaking change. -/// -public static class ChecklistErrorCodes -{ - public const string ProjectNotFound = "PROJECT_NOT_FOUND"; - public const string InvalidState = "INVALID_STATE"; - public const string InvalidChecklistType = "INVALID_CHECKLIST_TYPE"; - public const string InvalidVerseRange = "INVALID_VERSE_RANGE"; - public const string InvalidVerseRef = "INVALID_VERSE_REF"; - public const string VersificationMismatch = "VERSIFICATION_MISMATCH"; - public const string InvalidSource = "INVALID_SOURCE"; - public const string InvalidMarkerSettings = "INVALID_MARKER_SETTINGS"; - public const string MaxRowsExceeded = "MAX_ROWS_EXCEEDED"; - public const string Cancelled = "CANCELLED"; -} diff --git a/c-sharp/Checklists/ChecklistNetworkObject.cs b/c-sharp/Checklists/ChecklistNetworkObject.cs deleted file mode 100644 index e25479d6158..00000000000 --- a/c-sharp/Checklists/ChecklistNetworkObject.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.NetworkObjects; -using Paranext.DataProvider.Projects; -using Paranext.DataProvider.Services; -using Paratext.Data; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 exposed checklist functionality through the WinForms ChecklistsTool -// (user-facing menu entries + direct in-process calls). PT10 requires the same -// functionality to cross the process boundary as a PAPI network object so -// extensions (e.g., the platform-scripture web view) can consume it via -// `papi.networkObjects.get('platformScripture.checklistService')`. -// Maps to: EXT-014 / CAP-011 / backend-alignment.md §"Network Object" -// -// EXPLANATION: -// Registration shape (alphabetical FunctionNames, NetworkObjectType.OBJECT): -// - buildChecklistData → ChecklistService.BuildChecklistData -// - resolveComparativeTexts → ChecklistService.ResolveComparativeTexts -// - validateMarkerSettings → MarkersDataSource.ValidateMarkerSettings -// The wire contract is specified in data-contracts.md §7.1/§7.2; the canonical -// `RegisterNetworkObjectAsync` pattern comes from -// c-sharp/Projects/ProjectDataProviderFactory.cs:25-46. -// -// Subclasses `NetworkObject` (not `DataProvider`) because the checklist has -// no get/set/subscribe data-type triplet — it is a stateless request/response -// service. No `onDidUpdate` event is emitted; refresh is driven from the -// consumer side via existing scripture-change signals. -/// -/// PAPI network object that exposes the checklist service's three stateless -/// methods (buildChecklistData, resolveComparativeTexts, -/// validateMarkerSettings) to extensions via -/// papi.networkObjects.get<IChecklistService>(...). Per-method -/// pipeline behaviour lives in / -/// ; this class is purely the wire shim. -/// -internal sealed class ChecklistNetworkObject : NetworkObject -{ - // Wire contract — pinned here as a single source of truth so the tuple - // list passed to RegisterNetworkObjectAsync and the FunctionNames array - // inside NetworkObjectCreatedDetails cannot drift apart. Order is - // alphabetical to match data-contracts.md §7.1/§7.2 and the CAP-011 - // acceptance test's ExpectedFunctionNames. - private const string NetworkObjectName = "platformScripture.checklistService"; - private const string BuildMethodName = "buildChecklistData"; - private const string ResolveMethodName = "resolveComparativeTexts"; - private const string ValidateMethodName = "validateMarkerSettings"; - - // Build can traverse many books × multiple comparative projects; 30s matches - // the default PAPI request timeout (see PapiClient._requestTimeout) and is - // explicit here so a regression in request-handler attribution is obvious - // at registration time rather than surfacing as a silent wire truncation. - private const int BUILD_CHECKLIST_TIMEOUT_MS = 30_000; - - public ChecklistNetworkObject(PapiClient papiClient) - : base(papiClient) { } - - /// - /// Registers the checklist network object with PAPI. Calls - /// with the three - /// wire methods in alphabetical order and - /// . Calling twice on the same - /// instance throws (the base class' single-registration guard). - /// - public async Task InitializeAsync() - { - await RegisterNetworkObjectAsync( - NetworkObjectName, - [ - ( - BuildMethodName, - new Func(BuildChecklistData) - ), - ( - ResolveMethodName, - new Func< - string, - IReadOnlyList, - CancellationToken, - ResolvedComparativeTexts - >(ResolveComparativeTexts) - ), - ( - ValidateMethodName, - new Func(ValidateMarkerSettings) - ), - ], - new NetworkObjectCreatedDetails - { - Id = NetworkObjectName, - ObjectType = NetworkObjectType.OBJECT, - FunctionNames = [BuildMethodName, ResolveMethodName, ValidateMethodName], - } - ); - } - - /// - /// PAPI delegate target for buildChecklistData. Routes to the - /// stateless — which - /// itself calls - /// statically against the shared ScrTextCollection. Instance method - /// (rather than static) so it can access to - /// resolve localize keys at the wire boundary. - /// Behaviour lives in ChecklistService; this is a transport shim. - /// Localize keys carried in the result (e.g. - /// for the "identical" variant) are resolved here before the wire - /// response leaves the backend, per the - /// patterns.errorHandling.backendLocalization registry entry. - /// - /// Return type is because this delegate serves the - /// ChecklistResultResponse discriminated union (data-contracts.md §3.1): - /// the success branch returns ; the error branch - /// returns (mapped from the contract-listed - /// exception types). is deliberately - /// NOT caught — it propagates so PAPI can surface cooperative cancellation - /// semantics to the caller (TS-062). - /// - /// - [NetworkTimeout(BUILD_CHECKLIST_TIMEOUT_MS)] - private object BuildChecklistData(ChecklistRequest request, CancellationToken ct) - { - try - { - var result = ChecklistService.BuildChecklistData(request, ct); - return ResolveLocalizeKeys(result); - } - catch (Exception ex) when (ex is ProjectNotFoundException or ArgumentException) - { - // PROJECT_NOT_FOUND covers both the unresolved-GUID case - // (ProjectNotFoundException from ScrTextCollection) and the - // malformed-projectId case (ArgumentException from - // HexId.FromStr / HexToByteArr). From the wire contract's - // perspective, both mean "the active projectId is not a valid - // Scripture project on this machine" (data-contracts.md §4.1 - // error conditions). - return new ChecklistResultError(ChecklistErrorCodes.ProjectNotFound, ex.Message); - } - } - - /// - /// PAPI delegate target for resolveComparativeTexts. Routes to the - /// stateless . - /// Instance method (rather than static) so it can access - /// if this method ever needs to surface localized - /// strings — today none are emitted, so the call is a direct pass-through. - /// - private ResolvedComparativeTexts ResolveComparativeTexts( - string activeProjectId, - IReadOnlyList requestedTexts, - CancellationToken ct - ) - { - return ChecklistService.ResolveComparativeTexts(activeProjectId, requestedTexts, ct); - } - - /// - /// PAPI delegate target for validateMarkerSettings. Routes to the - /// stateless . The - /// service returns a localize key in - /// on failure; we resolve it here before the wire response leaves the - /// backend, per the patterns.errorHandling.backendLocalization - /// registry entry. - /// - private MarkerSettingsValidationResult ValidateMarkerSettings(string equivalentMarkers) - { - var result = MarkersDataSource.ValidateMarkerSettings(equivalentMarkers); - if (result.ErrorMessage is not { } key || !IsLocalizeKey(key)) - return result; - var resolved = LocalizationService.GetLocalizedString( - PapiClient, - key, - MarkersDataSource.InvalidMarkerPairErrorFallback - ); - return result with { ErrorMessage = resolved }; - } - - /// - /// Resolves any localize keys carried inside a - /// before it is serialized over the wire. Today the only such key lives - /// in when Variant is - /// "identical". Returns the same result instance (or a new one with - /// the Message field resolved) to keep the immutable-record - /// contract. - /// - private ChecklistResult ResolveLocalizeKeys(ChecklistResult result) - { - if (result.EmptyResultMessage is not { } empty) - return result; - if (!IsLocalizeKey(empty.Message)) - return result; - - var resolved = LocalizationService.GetLocalizedString( - PapiClient, - empty.Message, - MarkersDataSource.IdenticalMarkersMessageFallback - ); - return result with { EmptyResultMessage = empty with { Message = resolved } }; - } - - /// - /// Lightweight test for "looks like a localize key" — i.e. wrapped in - /// % sentinels per paranext-core convention. Avoids double-resolve - /// when a NetworkObject method is invoked multiple times on the same - /// record (e.g. in test assertions that round-trip). - /// - private static bool IsLocalizeKey(string? value) => - value != null && value.Length >= 2 && value[0] == '%' && value[^1] == '%'; -} diff --git a/c-sharp/Checklists/ChecklistParagraphTokens.cs b/c-sharp/Checklists/ChecklistParagraphTokens.cs deleted file mode 100644 index 911b1513bfa..00000000000 --- a/c-sharp/Checklists/ChecklistParagraphTokens.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Paratext.Data; -using SIL.Scripture; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLDataSource.cs:462-504 (class CLParagraphTokens) -// Maps to: EXT-012 / BHV-119 -// Companion: emitted by ChecklistService.GetTokensForBook (EXT-008 — see -// ChecklistService.cs in this directory). -// -// EXPLANATION: -// PT9's CLParagraphTokens was a mutable class with public fields. PT10 uses -// an immutable positional record. `IsHeading` is a new PT10 field (strategic -// plan CAP-003 contract: "VerseRefStart, Marker, IsHeading, and token -// collection"); PT9 re-checked headingMarkers membership on demand — the -// record flattens that derivation onto the data carrier so downstream cell -// building (CAP-004) can read the flag directly. -/// -/// Paragraph-scoped USFM token bundle produced by -/// . Carries the paragraph's -/// start verse reference, marker, heading-membership flag, and the ordered -/// USFM tokens that constitute the paragraph body. See data-contracts.md -/// §4.1 (BuildChecklistData internal types) and behavior-catalog.md -/// BHV-108 / BHV-119. -/// -internal sealed record ChecklistParagraphTokens( - VerseRef VerseRefStart, - string Marker, - bool IsHeading, - IReadOnlyList Tokens -) -{ - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:498-506 (ReferenceInRange) - // Maps to: EXT-012 / BHV-119 - // - // EXPLANATION: - // VerseRef.AllVerses() expands verse bridges ("3-5") into the individual - // verses so that ANY overlap with the [startRef, endRef] inclusive range - // counts as "in range". Each bound is short-circuited by IsDefault — a - // default VerseRef (VerseRef.IsDefault == true) means "unbounded on this - // side" and the corresponding comparison is treated as satisfied. - /// - /// Returns true when any part of falls within - /// the inclusive range ... - /// Expands verse bridges via AllVerses(); short-circuits when - /// either bound is the default sentinel - /// (unbounded on that side). - /// - public bool ReferenceInRange(VerseRef startRef, VerseRef endRef) - { - return VerseRefStart - .AllVerses() - .Any(vref => - (startRef.IsDefault || vref >= startRef) && (endRef.IsDefault || vref <= endRef) - ); - } -} diff --git a/c-sharp/Checklists/ChecklistRequest.cs b/c-sharp/Checklists/ChecklistRequest.cs deleted file mode 100644 index 63cc31300ec..00000000000 --- a/c-sharp/Checklists/ChecklistRequest.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.Scripture; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 passes project/settings/range through WinForms form fields and direct -// ScrText access. PT10 requires a structured DTO that crosses the PAPI boundary. -// Maps to: data-contracts.md §2.1 (ChecklistRequest) -// -// EXPLANATION: -// The optional requested verse range is carried as a `ScriptureRange` (see -// `ScriptureRange.cs` — one type per file, PNX004). `ScriptureRange` is the single -// canonical structured scripture reference used across the Checklists module (it is -// also the type of the result-side `ChecklistCell.Reference` / `ChecklistRow.FirstRef` -// / `LinkItem.Reference`). -/// -/// Checklist request DTO. Frozen at the PAPI boundary. See data-contracts.md §2.1. -/// -[method: JsonConstructor] -public record ChecklistRequest( - string ProjectId, - IReadOnlyList ComparativeTextIds, - MarkerSettings MarkerSettings, - ScriptureRange? VerseRange, - bool HideMatches, - bool ShowVerseText -); diff --git a/c-sharp/Checklists/ChecklistResult.cs b/c-sharp/Checklists/ChecklistResult.cs deleted file mode 100644 index b18e92ecf75..00000000000 --- a/c-sharp/Checklists/ChecklistResult.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Paranext.DataProvider.Scripture; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLData.cs (top-level result; rows/cells/paragraphs -// carried through CLRow/CLCell/CLParagraph) -// Method: ChecklistResult (CLData), ChecklistRow (CLRow), ChecklistCell (CLCell), -// ChecklistParagraph (CLParagraph) -// Maps to: EXT-010 (data models) -// -// EXPLANATION: -// The four result records colocate in this file per data-contracts.md §3.2 — they are -// exclusively used via ChecklistResult (PNX004 one-type-per-file exception). Each -// carries [method: JsonConstructor] so System.Text.Json uses the primary constructor -// on deserialize (matching the c-sharp/AppInfo.cs precedent for positional records). -/// -/// Top-level checklist result payload. See data-contracts.md §3.1. -/// -[method: JsonConstructor] -public record ChecklistResult( - IReadOnlyList Rows, - IReadOnlyList ColumnHeaders, - IReadOnlyList ColumnProjectIds, - int ExcludedCount, - string? HelpText, - bool Truncated, - EmptyResultMessage? EmptyResultMessage -); - -// Scripture-reference modeling (Reference / FirstRef): these structured fields use the -// canonical platform type (VerseRef Start + optional End, -// serialized via the repo-wide VerseRefConverter) rather than bespoke reference strings — -// one canonical scripture-reference representation everywhere. ScriptureRange is -// bridge-capable: {Start} for a single verse, {Start, End} for a verse bridge. Both fields -// are nullable: a default/empty verse reference is represented as null (the -// empty-placeholder cell from ChecklistRowBuilder, and rows whose cells carry no ref). -/// -/// Single row of the checklist result. See data-contracts.md §3.2. FirstRef is the -/// earliest verse reference across the row's populated cells (a single-verse -/// ), or null when no cell carries a reference. -/// -[method: JsonConstructor] -public record ChecklistRow( - IReadOnlyList Cells, - bool IsMatch, - bool IncludeEditLink, - double Score, - ScriptureRange? FirstRef -); - -/// -/// Per-project cell within a row. See data-contracts.md §3.3. Reference is the cell's -/// verse reference as a bridge-capable , or null for an -/// empty-placeholder cell (a column with no verse at this row, INV-001). The previously -/// separate DisplayedReference string is dropped from the wire — it was pure derived -/// presentation; the client formats a display string from Reference via -/// formatScrRefRange. -/// -[method: JsonConstructor] -public record ChecklistCell( - IReadOnlyList Paragraphs, - ScriptureRange? Reference, - string Language, - string? Error -); - -/// -/// Paragraph container within a cell. The Marker field stores the marker name -/// WITHOUT the backslash prefix per INV-004 (display layer prepends the backslash). -/// See data-contracts.md §3.4. -/// -[method: JsonConstructor] -public record ChecklistParagraph(string Marker, IReadOnlyList Items); diff --git a/c-sharp/Checklists/ChecklistResultError.cs b/c-sharp/Checklists/ChecklistResultError.cs deleted file mode 100644 index cc9d1ae743d..00000000000 --- a/c-sharp/Checklists/ChecklistResultError.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 surfaces errors via WinForms MessageBox; PT10 uses a structured -// wire-level error record over PAPI, served from the discriminated-union -// response defined in data-contracts.md §3.1 (ChecklistResultResponse = -// ChecklistResult | ChecklistResultError). -// Maps to: data-contracts.md §3.1 / §3.6 / §4.1 error conditions, CAP-011 -// structured-error wiring -/// -/// Wire-format error returned by the checklist NetworkObject when a contract-listed -/// exception is caught inside the buildChecklistData delegate target. See -/// ChecklistNetworkObject.BuildChecklistData for the catch-and-convert path, -/// and for the canonical values. -/// -/// Machine-readable code from . -/// Human-readable message for the UI layer to render. -[method: JsonConstructor] -public record ChecklistResultError(string Code, string Message); diff --git a/c-sharp/Checklists/ChecklistRowBuilder.cs b/c-sharp/Checklists/ChecklistRowBuilder.cs deleted file mode 100644 index 199ad7d5dcd..00000000000 --- a/c-sharp/Checklists/ChecklistRowBuilder.cs +++ /dev/null @@ -1,813 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Paranext.DataProvider.Scripture; -using SIL.Scripture; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:1-371 -// Method: CLRowsBuilder.BuildRowsMergingCells (and internal helpers -// BuildReferenceMappings, ExpandGrabCountToAlignCells, AddRowOfGrabbedCells, -// GrabMatchingCellsFromColumns, MergeGrabbedCells, FindInsertionIndex, -// AddIfUnhandled, GetLargestGrabbedVerseRef, GetRefsFromGrabbedCells) -// Maps to: EXT-009 / BHV-109 -// Invariants: INV-001 (N cells per row), INV-006 (MaxCellsToGrab=3), -// INV-007 (common versification — orchestrator pre-normalizes), INV-011 -// (Markers uses merging mode — only public entry is BuildRowsMergingCells). -// -// EXPLANATION (algorithm overview): -// The builder takes per-column lists of ChecklistCell (one list per ScrText in -// the caller's order) and aligns them into rows such that cells sharing a -// normalized verse reference land in the same row. When one column has a verse -// bridge (e.g. "EXO 20:2-5") and another has individual verse cells -// (e.g. "EXO 20:4", "EXO 20:5", "EXO 20:6", ...), adjacent cells are grabbed -// and merged until either the bridges align or MaxCellsToGrab (3) is -// reached per column per row. -// -// Algorithmic phases (per invocation, in order): -// 1. Initialize: build mutable shadow cells + cellRefMap + referenceMap + -// handledCells sets. Re-fuse each cell's structured ScriptureRange into a -// (possibly-bridge) SIL.Scripture.VerseRef so AllVerses() can expand bridges. -// 2. Outer loop: for each column, walk cells in order. -// - GrabMatchingCellsFromColumns collects one cell per later column -// whose normalized verse refs overlap the current cell's. -// - ExpandGrabCountToAlignCells extends the grab set until bridge -// boundaries align (bounded by MaxCellsToGrab). -// - MergeGrabbedCells concatenates paragraphs within each column's -// grabbed set into a single MutableCell (the lead cell). -// - AddRowOfGrabbedCells emits a ChecklistRow: one ChecklistCell per -// column, empty placeholder when no cell was grabbed (INV-001). Rows -// from col 0 are appended; rows from later columns are binary-search -// inserted by FirstRef. -// -// PT10 adaptations from PT9: -// - PT9's mutable CLCell is not available — ChecklistCell is an immutable -// record (INV from CAP-001). We maintain a private MutableCell shadow per -// source cell that accumulates merged state, and project back to -// ChecklistCell records only at row-emission time. -// - PT9 reads versification from the first cell's live VerseRef. PT10's -// ChecklistCell carries its reference as a serialized ScriptureRange; the -// builder does not pin a project versification off it — it defaults to -// ScrVers.English and trusts the orchestrator (CAP-006) to pre-normalize per -// INV-007. This is sufficient for all 20 CAP-005 tests and for the target -// gm-011/012/013 same-versification shapes. - -/// -/// Aligns cells from multiple columns into rows by verse reference. Markers -/// checklist always uses the merging mode (INV-011) — verse bridges in one -/// column are merged with individual verse cells in another column up to -/// cells per column per row (INV-006). -/// -/// See class-level EXPLANATION comment for full algorithm and -/// data-contracts.md §4.1 (BHV-109 within BuildChecklistData), §3.2 -/// (ChecklistRow shape), §3.3 (ChecklistCell shape). -/// -internal static class ChecklistRowBuilder -{ - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:16 - // Invariant: INV-006 - /// - /// Maximum number of cells that can be merged per column per row during - /// verse-bridge alignment (INV-006). PT9's CLRowsBuilder caps the - /// grab at this value to prevent runaway row expansion when a giant - /// bridge in one column would otherwise pull in an unbounded number of - /// adjacent cells from another column. - /// - public const int MaxCellsToGrab = 3; - - /// - /// Aligns per-column cell lists into rows. Always uses merging mode - /// (Markers invariant INV-011). See class-level summary for the full - /// algorithm and data-contracts.md §4.1 for the formal contract. - /// - /// - /// One list per column (active project first, - /// then each comparative text in the caller's order). Cells must already - /// have been produced by ChecklistService.GetCellsForBook (CAP-004). - /// - /// - /// Rows aligned by normalized verse reference, each with exactly - /// columnsList.Count cells (INV-001; missing verses → empty - /// placeholder cells). - /// - public static List BuildRowsMergingCells(List> columnsList) - { - if (columnsList == null || columnsList.Count == 0) - return new List(); - - var builder = new Builder(columnsList); - return builder.Build(); - } - - // === NEW IN PT10 === - // Reason: ChecklistCell is an immutable record (CAP-001). PT9's CLRowsBuilder - // mutates CLCell instances in place during MergeGrabbedCells. To preserve - // PT9's behavior without mutating ChecklistCell, we shadow every source cell - // with a MutableCell that accumulates merged state; ChecklistCell records - // are constructed only at row emission. - // Maps to: Infrastructure for BHV-109 - /// - /// Mutable shadow of a used during alignment to - /// accumulate merged paragraphs and extended verse-reference ranges without - /// mutating the immutable source record. - /// - private sealed class MutableCell - { - public List Paragraphs { get; } - - // StartVerseRef is the cell's reference as a (possibly-bridge) VerseRef — the - // canonical internal form the alignment math operates on (AllVerses() expansion). - // EndVerseRef is the last verse of the cell's range, extended in place when adjacent - // cells are merged. The emitted ChecklistCell.Reference (a structured ScriptureRange) - // is projected from these two at ToChecklistCell() time. - public VerseRef StartVerseRef { get; set; } - public VerseRef EndVerseRef { get; set; } - public string Language { get; } - public string? Error { get; } - - public MutableCell( - List paragraphs, - VerseRef startVerseRef, - VerseRef endVerseRef, - string language, - string? error - ) - { - Paragraphs = paragraphs; - StartVerseRef = startVerseRef; - EndVerseRef = endVerseRef; - Language = language; - Error = error; - } - - /// - /// Projects the current mutable state into an immutable - /// record for row emission. The returned - /// cell's Paragraphs list is the same reference held by this - /// — do not mutate after emission. Reference is - /// built from / as a structured - /// (null when the cell has no verse — the empty - /// placeholder). - /// - public ChecklistCell ToChecklistCell() => - new ChecklistCell( - Paragraphs: Paragraphs, - Reference: ScriptureRange.FromBounds(StartVerseRef, EndVerseRef), - Language: Language, - Error: Error - ); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:12-371 (instance fields + - // all private methods) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // PT9 uses instance fields on CLRowsBuilder to carry state across private - // helpers. Here we encapsulate the same state in a per-call Builder - // instance so the public entry point stays a pure static method and - // concurrent calls are isolated. - /// - /// Per-call state container for the row-alignment algorithm. Mirrors PT9's - /// instance fields scoped to one invocation. - /// - private sealed class Builder - { - private readonly List> _columns; - private readonly List> _mutableCells; - private readonly List _rows = new(); - private ScrVers _versification = ScrVers.English; - private Dictionary[] _referenceMap = null!; - private Dictionary>[] _cellRefMap = null!; - private HashSet[] _handledCells = null!; - - public Builder(List> columnsList) - { - _columns = columnsList; - _mutableCells = new List>(_columns.Count); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:64-91 - // (CLRowsBuilder.BuildRows) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // Outer loop walks every (column, cell) pair. For each pair, - // GrabMatchingCellsFromColumns collects one aligned cell per later - // column; if anything was grabbed, we expand the grab to align bridge - // boundaries (bounded by MaxCellsToGrab), merge paragraphs within - // each column's grabbed set, and emit the row. Always merging mode - // (Markers invariant INV-011). - public List Build() - { - Initialize(); - - for (int currentCol = 0; currentCol < _columns.Count; currentCol++) - { - int columnCellIndex = 0; - while (columnCellIndex < _columns[currentCol].Count) - { - List[] cellsToGrab = GrabMatchingCellsFromColumns( - currentCol, - columnCellIndex - ); - - if ( - cellsToGrab - .Where(colList => colList != null) - .SelectMany(colList => colList) - .Any() - ) - { - ExpandGrabCountToAlignCells(currentCol, cellsToGrab, ref columnCellIndex); - MergeGrabbedCells(currentCol, cellsToGrab); - AddRowOfGrabbedCells(currentCol, cellsToGrab); - } - columnCellIndex++; - } - } - - return _rows; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:97-109 - // (CLRowsBuilder.Initialize) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // PT9 reads default _versification from the first cell's live VerseRef. - // PT10's builder does not pin versification off the cell, so we default - // to ScrVers.English and rely on the orchestrator (CAP-006) to - // pre-normalize per INV-007. We also pre-build the MutableCell shadow - // so Merge operations don't need to touch the immutable records. - private void Initialize() - { - // Build the MutableCell shadow per source cell. (Versification - // defaults to ScrVers.English at field declaration — see class-level - // EXPLANATION; orchestrator pre-normalizes cells before calling.) - foreach (var column in _columns) - { - var mcol = new List(column.Count); - foreach (var cell in column) - { - var parsed = ParseVerseRefs(cell); - mcol.Add( - new MutableCell( - paragraphs: new List(cell.Paragraphs), - startVerseRef: parsed.start, - endVerseRef: parsed.end, - language: cell.Language, - error: cell.Error - ) - ); - } - _mutableCells.Add(mcol); - } - - BuildReferenceMappings(); - - _handledCells = new HashSet[_columns.Count]; - for (int col = 0; col < _columns.Count; col++) - _handledCells[col] = new HashSet(); - } - - // === NEW IN PT10 === - // Reason: ChecklistCell.Reference is a structured ScriptureRange (Start point + - // optional End point) — the bridge is carried as two endpoints, not as a - // bridge-notation string. The alignment math downstream calls - // StartVerseRef.AllVerses() to expand a cell's full verse coverage, so we re-fuse - // the range's endpoints into a single (possibly-bridge) VerseRef here. - // Maps to: Infrastructure for BHV-109 - // - // EXPLANATION: - // For a single-verse cell the range is {Start}; `start` is that point and `end` - // equals it. For a bridge the range is {Start, End}; `start` is rebuilt as a bridge - // VerseRef "first-last" so AllVerses() expands it exactly as it did when the cell - // carried a "EXO 20:2-5" string. Cells with no reference (Reference == null) return - // a default VerseRef pair — they contribute nothing to the ref maps. - /// - /// Derives the start and end for a - /// from its structured . Cells with no - /// reference return default values. - /// - private (VerseRef start, VerseRef end) ParseVerseRefs(ChecklistCell cell) - { - if (cell.Reference is not { } range) - return (new VerseRef(), new VerseRef()); - - VerseRef start = ReconstructVerseRef(range); - if (start.IsDefault) - return (new VerseRef(), new VerseRef()); - - // AllVerses expands bridges; .Last() gives the final verse of a bridge. - VerseRef end; - try - { - end = start.AllVerses(true).Last(); - } - catch - { - end = start; - } - - return (start, end); - } - - // === NEW IN PT10 === - // Reason: a structured ScriptureRange carries a bridge as two endpoint points - // ({Start}, {End}); the alignment math needs a single VerseRef whose AllVerses() - // enumerates every verse the cell covers. - // Maps to: Infrastructure for BHV-109 - // - // EXPLANATION: - // Fuses the range's Start/End back into one VerseRef, versified to the builder's - // _versification (mirroring the old `new VerseRef(string, _versification)` parse). - // When Start and End share a book+chapter and End is later, the verse component - // becomes the bridge form "first-last"; otherwise the cell is a single verse and - // Start's own verse component is used as-is. Input cells from GetCellsForBook are - // always single-chapter, so the cross-chapter fallback is purely defensive. - /// - /// Rebuilds a (possibly-bridge) from a structured - /// so bridge expansion (AllVerses()) works - /// during alignment. Returns a default on malformed input. - /// - private VerseRef ReconstructVerseRef(ScriptureRange range) - { - VerseRef start = range.Start; - string verse = - range.End is { } end - && end.BookNum == start.BookNum - && end.ChapterNum == start.ChapterNum - && end.VerseNum > start.VerseNum - ? $"{start.VerseNum}-{end.VerseNum}" - : start.Verse; - try - { - return new VerseRef(start.Book, start.ChapterNum.ToString(), verse, _versification); - } - catch - { - return new VerseRef(); - } - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:114-137 - // (CLRowsBuilder.BuildReferenceMappings) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // For each column, build two lookup tables: - // - _referenceMap: normalized VerseRef -> index of the first cell - // containing that verse. Used by GrabMatchingCellsFromColumns. - // - _cellRefMap: cell index -> list of every normalized VerseRef - // that cell covers (bridges expanded via AllVerses). - // ChangeVersification is applied to each normalized ref; in practice - // the orchestrator already pre-normalized, so this is a no-op for the - // 20 CAP-005 tests but preserves PT9's semantic for future callers. - private void BuildReferenceMappings() - { - _cellRefMap = new Dictionary>[_columns.Count]; - _referenceMap = new Dictionary[_columns.Count]; - - for (int col = 0; col < _columns.Count; col++) - { - _cellRefMap[col] = new Dictionary>(); - _referenceMap[col] = new Dictionary(); - - for (int cell = 0; cell < _columns[col].Count; cell++) - { - var cellRefs = new List(); - VerseRef cellVerseRef = _mutableCells[col][cell].StartVerseRef; - if (cellVerseRef.IsDefault) - { - _cellRefMap[col][cell] = cellRefs; - continue; - } - foreach (VerseRef vRef in cellVerseRef.AllVerses()) - { - var vrefCopy = vRef; - vrefCopy.ChangeVersification(_versification); - if (!_referenceMap[col].ContainsKey(vrefCopy)) - _referenceMap[col].Add(vrefCopy, cell); - cellRefs.Add(vrefCopy); - } - _cellRefMap[col][cell] = cellRefs; - } - } - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:142-186 - // (CLRowsBuilder.ExpandGrabCountToAlignCells) - // Maps to: EXT-009 / BHV-109 / INV-006 - // - // EXPLANATION: - // Iteratively extends the grabbed-cells set until bridge boundaries - // align across _columns. Each iteration: - // 1. Find the largest (latest) verse ref among grabbed cells, and - // which column it belongs to. - // 2. For the current column: if its next unhandled cell starts at - // or before the largest ref, grab it (and advance the outer - // loop counter). - // 3. For every later column: scan the grabbed verse refs; if a - // ref maps to an unhandled cell in this column, grab it. - // The MaxCellsToGrab check on each column prevents runaway merges - // (INV-006). - private void ExpandGrabCountToAlignCells( - int currentCol, - List[] cellsToGrab, - ref int columnCellIndex - ) - { - bool foundOne; - do - { - foundOne = false; - - VerseRef largestRef = GetLargestGrabbedVerseRef( - currentCol, - cellsToGrab, - out int colWithLargest - ); - - for (int col = currentCol; col < _columns.Count; col++) - { - if (cellsToGrab[col].Count >= MaxCellsToGrab) - continue; // INV-006 guard - - if (col == currentCol) - { - int nextIndex = columnCellIndex + 1; - if ( - col != colWithLargest - && nextIndex < _columns[currentCol].Count - && _cellRefMap[currentCol][nextIndex].Any(v => v <= largestRef) - && AddIfUnhandled(col, nextIndex, cellsToGrab) - ) - { - columnCellIndex = nextIndex; - foundOne = true; - } - continue; - } - - foreach (VerseRef vRef in GetRefsFromGrabbedCells(currentCol, cellsToGrab)) - { - if ( - _referenceMap[col].TryGetValue(vRef, out int cellIndex) - && AddIfUnhandled(col, cellIndex, cellsToGrab) - ) - { - foundOne = true; - break; - } - } - } - } while (foundOne); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:191-204 - // (CLRowsBuilder.GetRefsFromGrabbedCells) - // Maps to: EXT-009 / BHV-109 - private IEnumerable GetRefsFromGrabbedCells( - int currentCol, - List[] cellsToGrab - ) - { - for (int col = currentCol; col < _columns.Count; col++) - { - if (cellsToGrab[col] == null) - continue; - - foreach (int index in cellsToGrab[col]) - { - foreach (VerseRef verseRef in _cellRefMap[col][index]) - yield return verseRef; - } - } - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:209-228 - // (CLRowsBuilder.GetLargestGrabbedVerseRef) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // Scans every grabbed cell's last verse (AllVerses(true).Last()) and - // returns the maximum (latest) ref found, along with which column it - // came from. PT9 uses this to decide which column "leads" the - // alignment and thus which direction to expand. ChangeVersification - // normalizes the ref before comparison (no-op here because cells are - // pre-normalized by the orchestrator). - private VerseRef GetLargestGrabbedVerseRef( - int currentCol, - List[] grabbedCells, - out int colWithLargest - ) - { - VerseRef? largestRef = null; - colWithLargest = -1; - for (int col = currentCol; col < grabbedCells.Length; col++) - { - if (grabbedCells[col] == null) - continue; - for (int cell = 0; cell < grabbedCells[col].Count; cell++) - { - VerseRef cellEnd = _mutableCells[col][grabbedCells[col][cell]].EndVerseRef; - if (cellEnd.IsDefault) - continue; - cellEnd.ChangeVersification(_versification); - if (largestRef == null || largestRef.Value < cellEnd) - { - largestRef = cellEnd; - colWithLargest = col; - } - } - } - - return largestRef ?? new VerseRef(); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:233-253 - // (CLRowsBuilder.MergeGrabbedCells) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION (PT10 adaptation): - // PT9 mutates the source CLCell via MergeWithCell and writes a new - // Reference/DisplayedReference on it. Here we append the grabbed cells' - // paragraphs to the lead MutableCell and extend its EndVerseRef to the - // merged range's end. StartVerseRef stays at the lead cell's start (for - // binary-search ordering); ToChecklistCell() projects {StartVerseRef, - // EndVerseRef} into the emitted cell's ScriptureRange — so a merged cell - // carries the full merged range as its structured Reference. - private void MergeGrabbedCells(int currentCol, List[] cellsToMerge) - { - for (int col = currentCol; col < cellsToMerge.Length; col++) - { - if (cellsToMerge[col] == null || cellsToMerge[col].Count <= 1) - continue; - - int firstCellIndex = cellsToMerge[col][0]; - var lead = _mutableCells[col][firstCellIndex]; - VerseRef mergedEnd = lead.EndVerseRef; - - for (int cellIdx = 1; cellIdx < cellsToMerge[col].Count; cellIdx++) - { - int otherIndex = cellsToMerge[col][cellIdx]; - var other = _mutableCells[col][otherIndex]; - lead.Paragraphs.AddRange(other.Paragraphs); - if ( - !other.EndVerseRef.IsDefault - && (mergedEnd.IsDefault || mergedEnd < other.EndVerseRef) - ) - mergedEnd = other.EndVerseRef; - } - - lead.EndVerseRef = mergedEnd; - } - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:258-279 - // (CLRowsBuilder.GrabMatchingCellsFromColumns) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // For the current (col, cellIndex), gather one cell per later column - // whose verse refs overlap. The current column always gets the - // current cell; later _columns look up each of the current cell's - // normalized refs in their _referenceMap and grab the FIRST - // unhandled match. - private List[] GrabMatchingCellsFromColumns(int currentCol, int masterListCellIndex) - { - List[] cellsToGrab = new List[_columns.Count]; - - for (int col = currentCol; col < _columns.Count; col++) - { - cellsToGrab[col] = new List(); - if (col == currentCol) - { - AddIfUnhandled(col, masterListCellIndex, cellsToGrab); - } - else - { - foreach (VerseRef vRef in _cellRefMap[currentCol][masterListCellIndex]) - { - if ( - _referenceMap[col].TryGetValue(vRef, out int cellIndex) - && AddIfUnhandled(col, cellIndex, cellsToGrab) - ) - break; - } - } - } - - return cellsToGrab; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:285-310 - // (CLRowsBuilder.AddIfUnhandled) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // Adds cellIndex to cellsToGrab[col] at the right position so the - // grabbed list stays in verse-reference order. Marks the cell in - // _handledCells so the same cell is never grabbed twice (this is - // what gives TS-068 duplicate verse refs their own separate _rows). - // The insertion index is the first position i where some verse ref - // in the cell-being-inserted is smaller than any verse ref of - // cellsToGrab[col][i]; otherwise append to end. - private bool AddIfUnhandled(int col, int cellIndex, List[] cellsToGrab) - { - if (_handledCells[col].Contains(cellIndex)) - return false; - - int insertIndex = cellsToGrab[col].Count; // default: append at end - for (int i = 0; i < cellsToGrab[col].Count; i++) - { - int grabbedCell = cellsToGrab[col][i]; - foreach (VerseRef verseRef in _cellRefMap[col][grabbedCell]) - { - if (_cellRefMap[col][cellIndex].Any(vref => vref < verseRef)) - { - insertIndex = i; - break; - } - } - } - - cellsToGrab[col].Insert(insertIndex, cellIndex); - _handledCells[col].Add(cellIndex); - return true; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:315-341 - // (CLRowsBuilder.AddRowOfGrabbedCells) - // Maps to: EXT-009 / BHV-109 / INV-001 - // - // EXPLANATION: - // Emits a single row: - // - For every column: if nothing was grabbed, add an empty - // ChecklistCell placeholder (INV-001: row always has N cells); - // otherwise project the lead MutableCell (index 0) into a - // ChecklistCell. - // - FirstRef: earliest verse reference across all populated - // cells (BHV-111 carry-through). - // - Rows coming from col 0 append to the end; _rows from later - // _columns are binary-search inserted into their correct - // position by FirstRef. - private void AddRowOfGrabbedCells(int currentCol, List[] grabbedCells) - { - var cells = new List(_columns.Count); - VerseRef? earliestRef = null; - - for (int col = 0; col < grabbedCells.Length; col++) - { - if (grabbedCells[col] == null || grabbedCells[col].Count == 0) - { - // Empty placeholder for missing column (INV-001) - cells.Add(EmptyCell()); - continue; - } - - int firstCellIndex = grabbedCells[col][0]; - var mc = _mutableCells[col][firstCellIndex]; - cells.Add(mc.ToChecklistCell()); - - if (!mc.StartVerseRef.IsDefault) - { - // Use the first individual verse of the cell's range (strip - // any bridge notation so FirstRef is always a single verse). - VerseRef firstSingleVerse = mc.StartVerseRef.AllVerses().FirstOrDefault(); - VerseRef candidate = firstSingleVerse.IsDefault - ? mc.StartVerseRef - : firstSingleVerse; - if (earliestRef == null || candidate < earliestRef.Value) - earliestRef = candidate; - } - } - - ScriptureRange? firstRef = earliestRef.HasValue - ? ScriptureRange.FromVerseRef(earliestRef.Value) - : null; - - // VAL-007 cond 2 (row-level signal): mark IncludeEditLink=true when - // the first cell of the row has a reference (a non-null Reference per - // §3.3). TODO (VAL-007): downstream inline emission in - // ChecklistService.ApplyEditLinkGating currently runs per-cell and does - // not consult this row-level flag. Wire the flag into the emission gate - // (or promote the gate to row-level) once chapter-level CanEdit lands - // alongside DEF-BE-001. - bool includeEditLink = cells.Count > 0 && cells[0].Reference is not null; - - var newRow = new ChecklistRow( - Cells: cells, - IsMatch: false, - IncludeEditLink: includeEditLink, - Score: 0.0, - FirstRef: firstRef - ); - - if (currentCol == 0 || _rows.Count == 0) - _rows.Add(newRow); - else - { - int insertIndex = FindInsertionIndex(newRow); - _rows.Insert(insertIndex, newRow); - } - } - - // === NEW IN PT10 === - // Reason: PT9 uses `new CLCell()` which defaults to an empty cell. - // ChecklistCell is a record that requires explicit values. This - // helper centralizes the placeholder shape. - // Maps to: Infrastructure for INV-001 - /// - /// Constructs an empty placeholder cell for a column that has no - /// matching verse at the current row (INV-001). - /// - private static ChecklistCell EmptyCell() => - new ChecklistCell( - Paragraphs: new List(), - Reference: null, - Language: string.Empty, - Error: null - ); - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLRowsBuilder.cs:349-369 - // (CLRowsBuilder.FindInsertionIndex) - // Maps to: EXT-009 / BHV-109 - // - // EXPLANATION: - // Binary search over the in-progress _rows list to find the right - // insertion index for a new row by its FirstRef. When an existing - // row has the same FirstRef, the new row is inserted immediately - // AFTER it (PT9 semantic). - // - // PT9 compares VerseRefs via VerseRef.CompareTo. PT10's ChecklistRow.FirstRef - // is a structured ScriptureRange; we take its Start VerseRef on both sides and - // use the semantic comparator so cross-book/chapter ordering stays correct. - private int FindInsertionIndex(ChecklistRow newRow) - { - int start = 0; - int end = _rows.Count; - VerseRef newRef = FirstRefStartVerse(newRow.FirstRef); - while (true) - { - int indexToCheck = start + ((end - start) >> 1); - VerseRef checkRef = FirstRefStartVerse(_rows[indexToCheck].FirstRef); - int compareValue = CompareVerseRefs(checkRef, newRef); - - if (compareValue > 0) - end = indexToCheck; - else if (compareValue < 0) - start = indexToCheck + 1; - - if (start >= end) - return start; - - if (compareValue == 0) - return indexToCheck + 1; - } - } - - // === NEW IN PT10 === - // Reason: PT10 ChecklistRow.FirstRef is a structured ScriptureRange (per - // data-contracts.md §3.2). FindInsertionIndex compares rows by their first - // verse, which is the range's Start point. - // Maps to: Infrastructure for BHV-109 - private static VerseRef FirstRefStartVerse(ScriptureRange? firstRef) => - firstRef?.Start ?? new VerseRef(); - - // === NEW IN PT10 === - // Reason: VerseRef comparison operators throw when either side is - // default; we need a null-safe comparator for FindInsertionIndex. - // Maps to: Infrastructure for BHV-109 - private static int CompareVerseRefs(VerseRef a, VerseRef b) - { - if (a.IsDefault && b.IsDefault) - return 0; - if (a.IsDefault) - return -1; - if (b.IsDefault) - return 1; - if (a < b) - return -1; - if (a > b) - return 1; - return 0; - } - } -} diff --git a/c-sharp/Checklists/ChecklistService.cs b/c-sharp/Checklists/ChecklistService.cs deleted file mode 100644 index c983ef33d76..00000000000 --- a/c-sharp/Checklists/ChecklistService.cs +++ /dev/null @@ -1,1114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.Projects; -using Paranext.DataProvider.Scripture; -using Paratext.Data; -using PtxUtils; -using SIL.Scripture; - -namespace Paranext.DataProvider.Checklists; - -/// -/// Stateless checklist orchestration service. Hosts the top-level -/// pipeline (CAP-006) together with the -/// USFM token walker (CAP-003, EXT-008) and -/// cell constructor (CAP-004, EXT-011) it -/// drives. Companion type ChecklistParagraphTokens (EXT-012) lives -/// alongside in ChecklistParagraphTokens.cs. Per-method provenance -/// headers (// === PORTED FROM PT9 ===) carry the authoritative -/// source references; contract: data-contracts.md §4.1. -/// -internal static class ChecklistService -{ - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:16 (reportCount=300 - // constant for a different capping concern) plus the PT10 strategic - // addition from EXT-015 (GetChecklistData max-rows cap). - // Maps to: INV-012 / EXT-015 - /// - /// Maximum row count emitted by - /// (INV-012). Rows produced beyond this cap are truncated and - /// is set to true. - /// - private const int MaxRows = 5000; - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:97-185 (CLDataSource.BuildRows) - // plus :334-351 (GetCells start-ref adjustment / book loop) and - // :356-363 (SelectedBooks). - // Maps to: EXT-001 (factory — inlined), EXT-002 (BuildRows), EXT-015 - // (maxRows cap) / BHV-100 / BHV-101 / BHV-118 / BHV-121 - // Invariants: INV-002 (single column IsMatch=true), INV-010 (hideMatches - // + ExcludedCount), INV-012 (max 5000 rows), INV-C15 (ColumnProjectIds - // parallel to ColumnHeaders), VAL-003 (GEN 1:1 -> 1:0 adjustment). - // - // EXPLANATION (pipeline composition): - // - // 0. Resolve main ScrText + comparative ScrTexts via projects. - // 1. Compute [startRef, endRef] (BHV-118): defaults are - // mainScrText.FirstVerseRef() / LastVerseRef() when the request's - // VerseRange is null or its bounds are default VerseRefs. VAL-003 - // adjustment: if start is (GEN 1:1), rewrite to (GEN 1:0) so - // intro material (\ip at verse 0) is included (PT9 CLDataSource - // GetCells lines 344-345). - // 2. Parse marker settings via - // MarkersDataSource.InitializeMarkerMappings(equivalentMarkers, - // markerFilter) — yields (mappings, markerFilter). - // 3. Compute the iteration book list: - // mainScrText.Settings.BooksPresentSet.SelectedBookNumbers - // intersected with [startRef.BookNum..endRef.BookNum] (PT9 - // SelectedBooks lines 356-363). - // 4. For each column (active first, then comparatives) and each - // book, extract paragraphs (CAP-003 GetTokensForBook), build - // cells (CAP-004 GetCellsForBook), and transform each paragraph - // via MarkersDataSource.PostProcessParagraph (BHV-103: prepend - // backslash-marker TextItem; when showVerseText=false, drop the - // rest of the items). CancellationToken is checked at method - // entry AND per book iteration (TS-062; replaces PT9's - // Progress.Mgr.EndProgressIfCancelled). - // 5. Row alignment via CAP-005 BuildRowsMergingCells — always - // merging mode (INV-011 Markers). - // 6. Match detection: single-column rows get IsMatch=true forced - // (INV-002); multi-column rows use MarkersDataSource.HasSameValue - // with the parsed mappings (BHV-104 + INV-005 bidirectional). - // 7. hideMatches filter (INV-010): when hideMatches=true AND - // columns > 1, drop matching rows (backwards iteration for - // index stability — PT9 CLDataSource.cs:134) and track - // ExcludedCount. - // 8. Truncate to 5000 rows (INV-012 / EXT-015). PT10 addition — - // PT9 had no such cap. - // 9. PostProcessRows (BHV-106 / INV-008): produces the - // EmptyResultMessage when the final row list is empty. - // 10. Assemble ChecklistResult with parallel ColumnHeaders / - // ColumnProjectIds (INV-C15). - // - // Inline EditLinkItem emission (CAP-012 / VAL-007 project-level subset) - // lives in ApplyEditLinkGating and is wired into ExtractColumnCells - // per cell. Chapter-level permission (VAL-007 cond 5) is DEFERRED per - // DEF-BE-001 — see deferred-functionality.md. - /// - /// End-to-end orchestrator for the Markers checklist pipeline. Resolves - /// the active project and any comparative texts, extracts per-book marker - /// paragraphs, aligns them into rows, detects matches, optionally hides - /// matching rows, caps at rows, and assembles a - /// . See data-contracts.md §4.1 and - /// strategic-plan-backend.md §CAP-006 for the full contract. - /// - /// Checklist request (project, comparatives, verse range, marker settings). - /// Cancellation token; checked at entry and per book iteration (TS-062). - public static ChecklistResult BuildChecklistData(ChecklistRequest request, CancellationToken ct) - { - // Step 0a: honour pre-cancellation immediately (TS-062). - ct.ThrowIfCancellationRequested(); - - // Step 0b: resolve active ScrText + comparative ScrTexts. A missing - // projectId surfaces as ProjectNotFoundException from - // LocalParatextProjects.GetParatextProject; the wire-level - // PROJECT_NOT_FOUND structured error is produced by the wrapping - // ChecklistNetworkObject.BuildChecklistData delegate, which catches - // ProjectNotFoundException and returns a ChecklistResultError per - // data-contracts.md §3.1 (ChecklistResultResponse union) and §3.6 - // (ChecklistErrorCodes.ProjectNotFound). See TS-070. - ScrText mainScrText = LocalParatextProjects.GetParatextProject(request.ProjectId); - List comparativeScrTexts = request - .ComparativeTextIds.Select(LocalParatextProjects.GetParatextProject) - .ToList(); - List allScrTexts = [mainScrText, .. comparativeScrTexts]; - - // Step 1: compute effective [startRef, endRef] (BHV-118) + VAL-003 adjustment. - (VerseRef startRef, VerseRef endRef) = ResolveVerseRange(mainScrText, request.VerseRange); - startRef = ApplyStartRefIntroAdjustment(startRef); - - // Step 2: parse marker settings (BHV-105 / INV-005 / VAL-001/005/006). - (Dictionary> markerMappings, HashSet markerFilter) = - MarkersDataSource.InitializeMarkerMappings( - request.MarkerSettings.EquivalentMarkers, - request.MarkerSettings.MarkerFilter - ); - - // Step 3: compute the iteration book list. - IReadOnlyList bookNumbers = ResolveBookNumbers(mainScrText, startRef, endRef); - - // Step 4: per-column, per-book cell extraction with - // MarkersDataSource.PostProcessParagraph applied per paragraph. - List> columnsList = allScrTexts - .Select(scrText => - ExtractColumnCells( - scrText, - bookNumbers, - markerFilter, - startRef, - endRef, - request.ShowVerseText, - ct - ) - ) - .ToList(); - - // Step 5: row alignment via CAP-005 (always merging mode — INV-011). - List rows = ChecklistRowBuilder.BuildRowsMergingCells(columnsList); - - // Step 6-7: match detection + hideMatches filter (INV-002, INV-010). - int excludedCount = ApplyMatchDetectionAndFilter( - rows, - markerMappings, - columnCount: allScrTexts.Count, - hideMatches: request.HideMatches - ); - - // Step 8: max-rows cap (INV-012 / EXT-015). PT10 addition. - bool truncated = rows.Count > MaxRows; - if (truncated) - rows = rows.Take(MaxRows).ToList(); - - // Step 9: empty-result message (BHV-106 / INV-008). - IReadOnlyList searchedBookNames = bookNumbers.Select(Canon.BookNumberToId).ToList(); - EmptyResultMessage? emptyResultMessage = MarkersDataSource.PostProcessRows( - rows, - markerFilter, - searchedBookNames - ); - - // Step 10: parallel ColumnHeaders / ColumnProjectIds (INV-C15). - List columnHeaders = allScrTexts.Select(s => s.Name).ToList(); - - var columnProjectIds = new List(1 + request.ComparativeTextIds.Count) - { - request.ProjectId, - }; - columnProjectIds.AddRange(request.ComparativeTextIds); - - return new ChecklistResult( - Rows: rows, - ColumnHeaders: columnHeaders, - ColumnProjectIds: columnProjectIds, - ExcludedCount: excludedCount, - HelpText: null, - Truncated: truncated, - EmptyResultMessage: emptyResultMessage - ); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/ChecklistsTool.cs:132-148 - // (Initialize — comparative-text resolution slice). - // Maps to: CAP-009 / BHV-605 / BHV-310 (backend slice) / INV-014 / - // TS-047 / TS-048 (PTX-23529) - // Contract: data-contracts.md §4.5 (ResolveComparativeTexts) + - // §3.10 (ResolvedComparativeText) + §3.11 (ResolvedComparativeTexts) - // - // EXPLANATION: - // PT9's Initialize slice (lines 139-147) resolved comparative texts via - // GUID-first lookup with a name fallback for legacy mementos that pre-dated - // GUID assignment. - // - // PT10 deviations vs PT9: - // - PT10 is greenfield: every project carries a canonical GUID, so the - // name-fallback PT9 path is unnecessary. ComparativeTextRef now carries - // only `Id` (markers-checklist PR #2254 review). Resolution is - // GUID-only via ScrTextCollection.FindById. - // - PT9 silently dropped unresolvable entries. PT10's §3.11 validation - // rule instead keeps them in the result list with `Available=false` - // so the UI can render a missing-project marker. - // - `HexId.FromStrSafe` replaces direct `HexId.FromStr` so malformed - // GUID strings flow through as "unresolved" rather than throwing. - // - Active-project resolution uses the same - // `LocalParatextProjects.GetParatextProject` helper as - // `BuildChecklistData` (above) — throws `ProjectNotFoundException` - // when the active projectId is not registered, satisfying the - // §4.5 Error Conditions "PROJECT_NOT_FOUND" contract without - // bespoke error construction. - /// - /// Resolves comparative text references to actual project information. - /// Resolution is GUID-only (INV-014). Returns resolved texts with their - /// display names and availability status. See data-contracts.md §4.5. - /// - /// - /// Active project ID; used for self-reference exclusion (INV-014). - /// - /// - /// Per-entry GUIDs to resolve; order is preserved in the output - /// (minus any self-reference entries). - /// - /// - /// Cancellation token; the resolution is an in-memory lookup with no - /// I/O, but we honor pre-cancellation for plumbing symmetry with - /// . - /// - /// - /// A whose Texts list - /// mirrors the order of with any - /// self-reference entries (matching ) - /// omitted. Entries whose GUID does not resolve are retained - /// with Available=false (data-contracts.md §3.10/§3.11). - /// - /// - /// Thrown when is not registered in - /// (§4.5 PROJECT_NOT_FOUND). - /// - /// - /// Thrown when is already cancelled at method entry. - /// - public static ResolvedComparativeTexts ResolveComparativeTexts( - string activeProjectId, - IReadOnlyList requestedTexts, - CancellationToken ct - ) - { - ct.ThrowIfCancellationRequested(); - - // Step 1: resolve active ScrText. On miss, GetParatextProject throws - // ProjectNotFoundException — surfacing the §4.5 PROJECT_NOT_FOUND - // error as a loud failure (not a silent empty result). - ScrText active = LocalParatextProjects.GetParatextProject(activeProjectId); - - // Step 2: per-entry GUID lookup + self-exclusion cascade. - // ResolveSingleComparativeRef returns null to signal "self-reference — - // skip this entry" (INV-014). - var resolved = new List(requestedTexts.Count); - foreach (ComparativeTextRef requested in requestedTexts) - { - ResolvedComparativeText? entry = ResolveSingleComparativeRef(requested, active); - if (entry != null) - resolved.Add(entry); - } - - return new ResolvedComparativeTexts(Texts: resolved); - } - - // Helper for ResolveComparativeTexts — see that method's provenance - // header for the PT9 source and PT10 deviations. Encapsulates the - // per-entry cascade: - // (a) GUID lookup via FindById, - // (b) self-exclusion (INV-014) — returns null to signal "skip", - // (c) emit the ResolvedComparativeText record. - // Returning ResolvedComparativeText? keeps the caller's loop a simple - // "append non-null" shape; a throwing sentinel would be wrong for a - // normal flow-control path. - private static ResolvedComparativeText? ResolveSingleComparativeRef( - ComparativeTextRef requested, - ScrText active - ) - { - // Step 2(a): GUID lookup via ScrTextCollection.FindById. - // HexId.FromStrSafe returns null on malformed input so the unresolved - // path (Available=false) handles malformed GUIDs without throwing. - // When the GUID is well-formed but not registered, FindById itself - // returns null — same outcome. - ScrText? found = HexId.FromStrSafe(requested.Id) is { } guid - ? ScrTextCollection.FindById(guid) - : null; - - // Step 2(b): self-exclusion (INV-014). PT9 pattern: `p != scrText` - // reference equality. Instances registered via - // DummyLocalParatextProjects.FakeAddProject (and the real - // ScrTextCollection) are shared references. - if (found != null && ReferenceEquals(found, active)) - return null; - - // Step 2(c): emit resolved record. Id is always preserved verbatim - // from the input. When resolved, Name/FullName mirror the ScrText; - // when unresolved, Name and FullName are empty strings. - return new ResolvedComparativeText( - Id: requested.Id, - Name: found?.Name ?? string.Empty, - FullName: found?.FullName ?? string.Empty, - Available: found != null - ); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/ChecklistsExtensions.cs:8-21 - // (FirstVerseRef / LastVerseRef on ScrText) - // Maps to: BHV-118 / VAL-003 - // - // EXPLANATION: - // Mirrors PT9's pre-flight that converts the optional ScriptureRange - // carried on the request into a concrete [startRef, endRef] pair, - // honoring the platform ScriptureRange contract: - // - range == null ⇒ whole project (FirstVerseRef..LastVerseRef) - // - range.End == null ⇒ single-verse range at Start - // - range.{Start,End}.IsDefault ⇒ defensive fallback to the project bounds - // for malformed wire payloads (defaulted VerseRef) - // `FirstVerseRef` returns "GEN 1:0" (intro verse) and `LastVerseRef` - // returns the final verse of the final book of the versification. - private static (VerseRef start, VerseRef end) ResolveVerseRange( - ScrText mainScrText, - ScriptureRange? range - ) - { - ScrVers versification = mainScrText.Settings.Versification; - - // FirstVerseRef: first book, chapter 1, verse 0 (intro position). - var firstDefault = new VerseRef(Canon.FirstBook, 1, 0, versification); - - // LastVerseRef: last book's last chapter's last verse. LastChapter / - // LastVerse are versification-aware computed properties that depend - // on the book/chapter already being set, so we seed chapter=1 and - // step upward. - var lastDefault = new VerseRef(Canon.LastBook, 1, 1, versification); - lastDefault.ChapterNum = lastDefault.LastChapter; - lastDefault.VerseNum = lastDefault.LastVerse; - - if (range == null) - return (firstDefault, lastDefault); - - VerseRef start = range.Start.IsDefault ? firstDefault : range.Start; - // Platform ScriptureRange contract: End omitted ⇒ single-verse range at Start. - // A defaulted End (malformed wire payload) falls back to the project bounds. - VerseRef end; - if (range.End == null) - end = start; - else if (range.End.Value.IsDefault) - end = lastDefault; - else - end = range.End.Value; - return (start, end); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:344-345 - // (GetCells): `if (startRefNonNull.ChapterNum == 1 && - // startRefNonNull.VerseNum == 1) startRefNonNull.VerseNum = 0;` - // Maps to: VAL-003 - // - // EXPLANATION: - // When the user-supplied start is (GEN 1:1), silently shift the verse - // to 0 so any intro paragraphs (\ip at verse 0) fall inside - // [startRef, endRef] inclusive. PT9 made this adjustment on the - // working copy of the ref at the cell-extraction gate; we apply it at - // the orchestrator level before cell extraction so every column sees - // the same expanded range. The ChapterNum check means the adjustment - // is ONLY applied at the GEN 1:1 boundary — any other (1:1) such as - // MAT 1:1 does NOT shift (PT9 semantic: the UI only ever passes - // GEN 1:1 as the "book start" sentinel). - // - // NOTE: PT9's condition is strictly `ChapterNum == 1 && VerseNum == 1` - // (no BookNum check) — meaning ANY book's 1:1 shifts to 1:0. This is - // safe because non-Genesis 1:1 starts are legitimate user choices - // where intro material is irrelevant; the test Group C pins the GEN - // case explicitly with `\ip` content. We preserve PT9's semantic. - private static VerseRef ApplyStartRefIntroAdjustment(VerseRef start) - { - if (start.ChapterNum == 1 && start.VerseNum == 1) - { - var adjusted = new VerseRef(start); - adjusted.VerseNum = 0; - return adjusted; - } - return start; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:356-363 - // (CLDataSource.SelectedBooks) - // Maps to: BHV-118 - // - // EXPLANATION: - // Enumerates `baseScrText.Settings.BooksPresentSet.SelectedBookNumbers` - // intersected with `[startRef.BookNum..endRef.BookNum]`. PT9's range filter - // is inclusive on both sides. - // - // Note: data-contracts.md §2.1 intentionally omits a request-level - // BookNumbers override (removed as speculative/unused — see revise round 1 - // T-R-1 action 4). The iteration book list is derived entirely from the - // active project's BooksPresentSet filtered by the verse range. - private static IReadOnlyList ResolveBookNumbers( - ScrText mainScrText, - VerseRef startRef, - VerseRef endRef - ) - { - return mainScrText - .Settings.BooksPresentSet.SelectedBookNumbers.Where(bookNum => - bookNum >= startRef.BookNum && bookNum <= endRef.BookNum - ) - .ToList(); - } - - // === NEW IN PT10 === - // Reason: PT9 mutated CLParagraph.Items in place via - // PostProcessParagraph (CLParagraphCellsDataSource.cs:221-226). PT10 - // ChecklistCell / ChecklistParagraph are immutable records, so we - // project the cell by rebuilding its Paragraphs with the MarkersDataSource - // post-processor applied to each. - // Maps to: Infrastructure for BHV-103 - /// - /// Rebuilds with each paragraph passed through - /// (which prepends - /// the backslash-marker TextItem at position 0 per INV-004). When - /// is false, the remainder of each - /// paragraph's items is dropped; when true, they are preserved after - /// the marker item. - /// - private static ChecklistCell ApplyPostProcessParagraph(ChecklistCell cell, bool showVerseText) - { - var newParagraphs = new List(cell.Paragraphs.Count); - foreach (ChecklistParagraph paragraph in cell.Paragraphs) - newParagraphs.Add(MarkersDataSource.PostProcessParagraph(paragraph, showVerseText)); - return cell with { Paragraphs = newParagraphs }; - } - - // === NEW IN PT10 === - // Maps to: BHV-100 / BHV-101 / BHV-118 — CAP-006 orchestration Step 4. - // - // EXPLANATION: - // Per-column slice of the BuildChecklistData pipeline: derive the - // stylesheet-scoped marker sets once per column, then iterate the - // selected books, extract paragraphs (CAP-003), construct cells - // (CAP-004), and apply MarkersDataSource.PostProcessParagraph per - // paragraph (BHV-103). Cancellation is checked per book so long-running - // multi-book iterations (INV-012 scenario) remain interruptible. - /// - /// Extracts the list of s for a single - /// project/column across every book in . - /// Paragraphs are post-processed through - /// to enforce the - /// backslash-marker TextItem prefix (INV-004) and the - /// gate on trailing items (BHV-103). - /// - private static List ExtractColumnCells( - ScrText scrText, - IReadOnlyList bookNumbers, - HashSet markerFilter, - VerseRef startRef, - VerseRef endRef, - bool showVerseText, - CancellationToken ct - ) - { - ScrStylesheet stylesheet = scrText.DefaultStylesheet; - HashSet paragraphMarkers = MarkersDataSource.ParagraphMarkers( - stylesheet, - markerFilter - ); - HashSet headingMarkers = MarkersDataSource.HeadingMarkers(stylesheet); - HashSet nonHeadingMarkers = MarkersDataSource.NonHeadingParagraphMarkers( - stylesheet - ); - - var columnCells = new List(); - foreach (int bookNum in bookNumbers) - { - ct.ThrowIfCancellationRequested(); - - List paragraphs = GetTokensForBook( - scrText, - bookNum, - paragraphMarkers, - headingMarkers, - nonHeadingMarkers - ); - - List cells = GetCellsForBook( - scrText, - bookNum, - startRef, - endRef, - paragraphs - ); - - foreach (ChecklistCell cell in cells) - { - ChecklistCell postProcessed = ApplyPostProcessParagraph(cell, showVerseText); - columnCells.Add(ApplyEditLinkGating(postProcessed, scrText)); - } - } - return columnCells; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/ChecklistsTool.cs SetCellEditability - // (project-level portion only; chapter-level DEFERRED per DEF-BE-001). - // Maps to: EXT-016 (project-level portion) / BHV-114 (emission sub-behavior) - // / VAL-007 (conds 1-4; cond 5 DEFERRED). - // - // EXPLANATION: - // PT9's SetCellEditability gated CLEditLink emission on five AND-conditions - // (business-rules.md §VAL-007): - // (1) row has cells, - // (2) first cell has non-default VerseRef, - // (3) row.IncludeEditLink is true, - // (4) scrText.Settings.Editable is true, - // (5) scrText.Permissions.CanEdit(bookNum, chapterNum) returns true. - // - // PT10 mapping: - // - (1)/(3) are structurally satisfied here: we iterate per cell post - // row-building, so every cell we see already belongs to a row that - // exists. PT10 folds the "IncludeEditLink" flag into the per-cell - // iteration (every qualifying cell emits exactly one link). - // - (2) maps to `cell.Reference is not null` — BuildCLCell leaves - // Reference null when `vref.IsDefault`, so a null Reference IS the - // PT10 signal that the cell has a default VerseRef. - // - (4) maps directly to `scrText.Settings.Editable`. - // - (5) is DEFERRED: paranext-core does not yet expose a chapter-level - // CanEdit(bookNum, chapterNum) API. See DEF-BE-001. Revisit when the - // trigger API becomes available. - // - // Paragraph placement: appends the EditLinkItem to the LAST paragraph's - // Items list. PT9's CLEditLink appeared at the end of a cell's rendered - // content; keeping the link inside an existing paragraph preserves the - // cell-shape (paragraph count) invariants that CAP-006 tests exercise. - /// - /// Emits an for when - /// VAL-007 project-level conditions hold: the cell has a non-default - /// reference (non-null ) AND - /// scrText.Settings.Editable == true. The link carries the - /// cell's BookNum/ChapterNum/VerseNum read from the start of - /// . Chapter-level permission - /// (CanEdit(bookNum, chapterNum)) is intentionally NOT checked — - /// deferred per DEF-BE-001. - /// - private static ChecklistCell ApplyEditLinkGating(ChecklistCell cell, ScrText scrText) - { - // Gate (4): project-level editability. - if (!scrText.Settings.Editable) - return cell; - - // Gate (2): non-default VerseRef. BuildCLCell leaves Reference null - // for default refs. - if (cell.Reference is null) - return cell; - - // Defensive: if a cell somehow has zero paragraphs, there's no place - // to append the link. (Not expected in practice.) - if (cell.Paragraphs.Count == 0) - return cell; - - // TODO: create tracking issue — chapter-level permission - // (see deferred-functionality.md; tracked at - // https://github.com/paranext/paranext-core/issues/TBD). - // PT9 also gated on scrText.Permissions.CanEdit(bookNum, chapterNum). - // paranext-core lacks that API today; revisit when it lands. - - // The structured Reference already carries the start VerseRef — no string - // parsing (and so no parse-failure path) needed any more. - VerseRef vref = cell.Reference.Start; - var editLink = new EditLinkItem(vref.BookNum, vref.ChapterNum, vref.VerseNum); - - ChecklistParagraph lastParagraph = cell.Paragraphs[^1]; - var updatedItems = new List(lastParagraph.Items.Count + 1); - updatedItems.AddRange(lastParagraph.Items); - updatedItems.Add(editLink); - - var updatedParagraphs = new List(cell.Paragraphs); - updatedParagraphs[^1] = lastParagraph with { Items = updatedItems }; - - return cell with - { - Paragraphs = updatedParagraphs, - }; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:134-153 (BuildRows - // backwards-iteration hideMatches drop) + :156-162 (single-column - // IsMatch=true force). - // Maps to: INV-002 / INV-010 / BHV-104 - // - // EXPLANATION: - // Mutates in place with two branches: - // - // - Single-column (columnCount <= 1): nothing to compare against, so - // force IsMatch=true on every row (INV-002). hideMatches is a - // no-op in this branch — PT9 CLDataSource.cs:156-162. - // - // - Multi-column: backwards-iterate (index stability under - // RemoveAt) and compute HasSameValue for each row. When - // hideMatches is true, drop matching rows and accumulate - // excludedCount; otherwise annotate each row with its IsMatch - // verdict. PT9 CLDataSource.cs:134-153. - // - // Returns the number of rows that were dropped (always 0 in the - // single-column branch). - private static int ApplyMatchDetectionAndFilter( - List rows, - Dictionary> markerMappings, - int columnCount, - bool hideMatches - ) - { - if (columnCount <= 1) - { - for (int i = 0; i < rows.Count; i++) - rows[i] = rows[i] with { IsMatch = true }; - return 0; - } - - int excludedCount = 0; - for (int i = rows.Count - 1; i >= 0; i--) - { - bool isMatch = MarkersDataSource.HasSameValue(rows[i], markerMappings); - if (isMatch && hideMatches) - { - rows.RemoveAt(i); - excludedCount++; - } - else - { - rows[i] = rows[i] with { IsMatch = isMatch }; - } - } - return excludedCount; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:50-91 - // (CLParagraphCellsDataSource.GetTokensForBook) - // Maps to: EXT-008 / BHV-108 / INV-009 - // - // EXPLANATION: - // The loop walks every UsfmToken for the book, maintaining a - // ScrParserState that tracks the current NoteTag, CharTag, ParaTag, - // VerseRef, and ParaStart flags. Four ordered gates decide whether a - // token participates: - // - // 1. NoteTag != null -> skip (inside \f / \fe / \x) - // 2. CharTag.Marker == "fig" -> skip (figure description / metadata) - // 3. ParaStart -> "close" the current paragraph by - // clearing the accumulator, so tokens - // for an undesired paragraph that - // follows will not leak into the - // previous desired paragraph. - // 4. !filter.Contains(Marker) -> drop entirely (skip to next token). - // - // After the gates, if ParaStart is true (and we got past gate 4), a new - // ChecklistParagraphTokens is constructed whose VerseRefStart comes from - // FindVerseRefForParagraph (handles heading forward-scan for INV-009). - // Every surviving token is appended to the currently-open paragraph's - // Tokens list. - // - // PT10 deviations vs PT9: - // - The PT9 `desiredMarkers != null` null-guard is dropped; the PT10 - // parameter is non-nullable. - // - `IsHeading` is computed at record-construction time - // (headingMarkers.Contains(Marker)); PT9 re-checked on demand. - // - The Tokens list is built mutably during the loop and exposed - // through the record's IReadOnlyList contract (List - // covariantly implements IReadOnlyList) — no post-copy needed. - /// - /// Walks all s for a book via - /// scrText.Parser.GetUsfmTokens(bookNum) and emits one - /// per qualifying paragraph start. - /// Skip conditions: state.NoteTag != null and - /// state.CharTag?.Marker == "fig". Filter: only paragraphs whose - /// marker is in are emitted; - /// an empty filter accepts NOTHING (caller supplies the fallback full - /// set when no user filter is active). Heading markers - /// () receive the verse reference of - /// the next non-heading paragraph (INV-009). - /// - public static List GetTokensForBook( - ScrText scrText, - int bookNum, - HashSet paragraphMarkersFilter, - HashSet headingMarkers, - HashSet nonHeadingParagraphMarkers - ) - { - List tokens = scrText.Parser.GetUsfmTokens(bookNum); - - var results = new List(); - List? currentTokens = null; - var state = new ScrParserState( - scrText, - new VerseRef(bookNum, 1, 0, scrText.Settings.Versification) - ); - - for (int i = 0; i < tokens.Count; ++i) - { - state.UpdateState(tokens, i); - - // Gate 1: inside a note -> skip. - if (state.NoteTag != null) - continue; - - // Gate 2: figure token -> skip. - if (state.CharTag != null && state.CharTag.Marker == "fig") - continue; - - // Gate 3: entering a new paragraph -> close the accumulator so - // any tokens that survive gate 4 below land in a FRESH paragraph - // (not the previous one). PT9 used `paragraphTokens = null`; we - // do the same. - if (state.ParaStart) - currentTokens = null; - - // Gate 4: paragraph marker filter. PT9: `desiredMarkers != null - // && !desiredMarkers.Contains(...)` -> continue. PT10's parameter - // is non-nullable, so the null-guard is dropped. - if (state.ParaTag != null && !paragraphMarkersFilter.Contains(state.ParaTag.Marker)) - continue; - - if (state.ParaStart) - { - // State.ParaTag is non-null here because gate 4 consumed the - // null check; ParaStart implies a paragraph tag has been - // parsed. Build the new paragraph entry. - string marker = state.ParaTag!.Marker; - currentTokens = new List(); - results.Add( - new ChecklistParagraphTokens( - VerseRefStart: FindVerseRefForParagraph( - headingMarkers, - nonHeadingParagraphMarkers, - state.VerseRef, - tokens, - i - ), - Marker: marker, - IsHeading: headingMarkers.Contains(marker), - Tokens: currentTokens - ) - ); - } - - currentTokens?.Add(tokens[i]); - } - - return results; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:105-135 - // (CLParagraphCellsDataSource.FindVerseRefForParagraph) - // Maps to: EXT-008 / INV-009 / FB-35863 - // - // EXPLANATION: - // Rules (order preserved from PT9): - // - // (a) If the paragraph marker is a HEADING marker, forward-scan from - // position i upward looking for the NEXT non-heading paragraph - // marker. Two caveats ported verbatim from PT9: - // - skip "b" (blank-line paragraph — not a real content header) - // - skip any marker starting with "i" (introductory: \ib, \ip, - // \im, ...) — these aren't considered "the next content - // paragraph" either. - // If the scan hits a "c" chapter marker first, STOP and return the - // input vref unchanged. This is FB-35863: a section heading that - // appears BEFORE a chapter boundary (user error) must not pull - // forward into the next chapter. - // - // (b) After the heading scan (or if the paragraph wasn't a heading), - // look one token past the paragraph-start: if it's a \v verse - // token, copy its verse number into the returned VerseRef's - // Verse component (handles verse bridges naturally — UsfmToken.Data - // carries "3-5" as-is, which VerseRef.Verse accepts). - // - // The PT9 code shadows the parameter `vrefIn` by making a local copy - // `vref = new VerseRef(vrefIn)`; preserved to avoid mutating a shared - // state object. - /// - /// Computes the assigned to a paragraph start at - /// token index . Heading markers (per - /// ) forward-scan to the next non-heading - /// paragraph to inherit its verse reference (INV-009); the scan is - /// bounded by chapter (\c) markers (FB-35863). Non-heading - /// paragraphs fall through to the post-scan step which, if the very next - /// token is \v, copies that token's - /// into the returned component (handles - /// verse bridges like "3-5" verbatim). - /// - private static VerseRef FindVerseRefForParagraph( - HashSet headingMarkers, - HashSet nonHeadingParagraphMarkers, - VerseRef vrefIn, - List tokens, - int i - ) - { - var vref = new VerseRef(vrefIn); - - // (a) Heading markers: forward-scan for the next non-heading paragraph. - if (headingMarkers.Contains(tokens[i].Marker)) - { - for (; i < tokens.Count; ++i) - { - if ( - nonHeadingParagraphMarkers.Contains(tokens[i].Marker) - && tokens[i].Marker != "b" - && !tokens[i].Marker.StartsWith('i') - ) - { - break; - } - - if (tokens[i].Marker == "c") - // FB-35863: heading before chapter boundary — keep the - // heading's current vref; don't leak into chapter N+1. - return vref; - } - } - - if (i + 1 >= tokens.Count) - return vref; - - // (b) If the very next token is a verse number, it IS this - // paragraph's reference. - if (tokens[i + 1].Marker == "v") - vref.Verse = tokens[i + 1].Data; - - return vref; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:191-216 - // (CLDataSource.GetCellsForBook) - // Maps to: EXT-011 / BHV-114 - // - // EXPLANATION: - // Two-step reduction from paragraph-token bundles to cells: - // - // 1. Range filter — paragraphs whose VerseRefStart falls OUTSIDE the - // [startRef, endRef] inclusive range are dropped via - // ChecklistParagraphTokens.ReferenceInRange (BHV-119, CAP-003-owned). - // PT9 also had an up-front `GetDesiredMarkers` filter; CAP-003's - // GetTokensForBook already pre-filters on the paragraph marker - // (paragraphMarkersFilter argument), so there's no second marker - // gate here. - // - // 2. Per-paragraph cell build via BuildCLCell, with same-reference - // merge: when two adjacent paragraphs share a VerseRef (CompareTo - // == 0), the new cell's paragraphs are appended to the previous - // cell instead of producing a second cell (PT9 - // AddContentToCurrentCell + MergeWithCell). - // - // PT10 deviations vs PT9: - // - Stateless: no ChecklistType dispatch, no virtual overrides, - // no instance fields. - // - showVerseText is NOT a parameter here: CAP-006's ExtractColumnCells - // passes the flag directly into ApplyPostProcessParagraph per cell, so - // GetCellsForBook has no use for it. PT9's CLDataSource interleaved - // the two concerns; PT10 separates them cleanly. - // - EditLinkItem is NOT emitted at this layer (VAL-007). CAP-012 owns - // inline emission; CAP-004 only ensures the cell STRUCTURE is ready - // for an EditLinkItem to be appended (Paragraphs[*].Items is a - // concrete mutable List). - // - Merge bookkeeping: PT9's CLCell carried its VerseRef internally - // so AddContentToCurrentCell could `cells[^1].VerseRef.CompareTo(...)`. - // PT10's ChecklistCell record exposes `Reference` as a structured - // `ScriptureRange`, so we maintain a parallel list of VerseRefs during - // construction to drive the merge comparison without unpacking it. - /// - /// Iterates (emitted by - /// ), filters by - /// ChecklistParagraphTokens.ReferenceInRange(startRef, endRef), and - /// constructs a list whose content items are - /// produced by walking each paragraph's USFM tokens: - /// - /// (RTL - /// prefix applied when scrText.RightToLeft); - /// ; - /// Paragraphs sharing a VerseRef merge into one cell - /// (PT9 AddContentToCurrentCell). - /// - /// CAP-004 does NOT emit ; CAP-012 owns inline - /// emission under VAL-007. See data-contracts.md §4.1 (BHV-114 within - /// BuildChecklistData), §3.3–§3.5. - /// - public static List GetCellsForBook( - ScrText scrText, - int bookNum, - VerseRef startRef, - VerseRef endRef, - List paragraphs - ) - { - var cells = new List(); - var cellVrefs = new List(); - - foreach (ChecklistParagraphTokens paragraph in paragraphs) - { - if (!paragraph.ReferenceInRange(startRef, endRef)) - continue; - - (ChecklistCell cell, VerseRef cellVref) = BuildCLCell(scrText, bookNum, paragraph); - - // PT9 AddContentToCurrentCell (CLDataSource.cs:226-231): when the - // new cell's VerseRef equals the previous cell's VerseRef - // (CompareTo == 0), merge paragraphs into the previous cell - // instead of appending a new one. PT9 also merged `Error` / - // `HasError` here; PT10 cells don't carry a running Error flag - // at this stage (error population is a later-stage concern), so - // only the Paragraphs merge is needed. - if (cells.Count > 0 && cellVrefs[^1].CompareTo(cellVref) == 0) - { - var previous = cells[^1]; - var mergedParagraphs = new List(previous.Paragraphs); - mergedParagraphs.AddRange(cell.Paragraphs); - cells[^1] = previous with { Paragraphs = mergedParagraphs }; - } - else - { - cells.Add(cell); - cellVrefs.Add(cellVref); - } - } - - return cells; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLDataSource.cs:316-320 (CreateCell) + - // :365-433 (BuildCLCell) - // Maps to: EXT-011 / BHV-114 - // - // EXPLANATION: - // Builds a single ChecklistCell from one paragraph-token bundle. The - // walk mirrors PT9 CLDataSource.BuildCLCell verbatim, minus the - // ChecklistType-specific branches (Verses-checklist "section-only - // marker", RelativelyLongVerses / RelativelyShortVerses / LongSentences - // "skip marker", CrossReferences "fig_ref" attribute branch) — none of - // those apply to the Markers checklist and none are covered by CAP-004 - // RED tests. - // - // Five steps (line numbers reference PT9 CLDataSource.cs): - // - // 1. :367-368 — copy paragraph.VerseRefStart into a local `vref` and - // force it into scrText's versification (FB-11372 / INV-007 prep). - // Using `new VerseRef(...)` avoids mutating the caller's reference. - // - // 2. :371 — language lookup via - // `scrText.GetJoinedText(bookNum).Settings.LanguageID.Id` - // (FB-11372 — engine name, not the scrText.Language raw property). - // Wrap in try/catch with a fallback to - // `scrText.Settings.LanguageID?.Id ?? string.Empty`: DummyScrText's - // GetJoinedText may not return a fully-populated wrapper in tests, - // and cells don't assert on an exact Language value at CAP-004. - // - // 3. :373-377 — create the cell and its (single) owning ChecklistParagraph. - // PT10 records are immutable, so we build Items up mutably and then - // construct the paragraph + cell records at the end. - // - // 4. :379-428 — token walk with a fresh ScrParserState. Three token - // types are emitted: - // - UsfmTokenType.Paragraph → sets paragraph marker (NOT a content item). - // PT9 has multiple branches here keyed on ChecklistType; Markers - // falls into the "else-if" branch that unconditionally writes - // the marker (ChecklistType is not RelativelyLongVerses, - // RelativelyShortVerses, LongSentences, or Verses). - // - UsfmTokenType.Text → TextItem. PT9 line 408 applies the RTL - // prefix: `scrText.RightToLeft ? StringUtils.rtlMarker + Text : Text`. - // PT9 line 409 attaches the active CharTag.Marker as - // CharacterStyle (empty string when no char tag is active — - // PT10 uses null for "no character style" but the empty-string - // precedent is preserved to match PT9 behaviour; tests accept - // either because they pin only the non-empty "em" case). - // - UsfmTokenType.Verse → VerseItem with token.Data (handles verse - // bridges verbatim — "4-6" passes through unchanged). - // - // 5. PT9 line 430 calls `PostProcessParagraph(cell, state.VerseRef, - // paragraph)`; CAP-004 does NOT — that's a CAP-006 orchestration - // concern (see plan Decisions Made). CAP-006's ExtractColumnCells - // applies MarkersDataSource.PostProcessParagraph directly with the - // orchestrator's showVerseText flag, so BuildCLCell does not carry - // that argument. - // - // Return tuple: the cell AND its live VerseRef (so GetCellsForBook can - // drive the same-reference merge via VerseRef.CompareTo). - /// - /// Constructs a (with a single - /// ) from a - /// bundle. Returns the cell - /// alongside the live so - /// can drive the same-reference merge. - /// - private static (ChecklistCell Cell, VerseRef CellVref) BuildCLCell( - ScrText scrText, - int bookNum, - ChecklistParagraphTokens paragraphTokens - ) - { - // Step 1: PT9 :367-368 — copy + force versification. - var vref = new VerseRef(paragraphTokens.VerseRefStart); - vref.Versification = scrText.Settings.Versification; - - // Step 2: PT9 :371 — FB-11372 language lookup with DummyScrText-safe fallback. - // The chained access `GetJoinedText(bookNum).Settings.LanguageID.Id` can - // surface a NullReferenceException when the joined-text wrapper is not - // fully populated (DummyScrText returns `this`, so its Settings/LanguageID - // may be uninitialized in some test scenarios). Narrow the catch to that - // concrete case so other exceptions (e.g. I/O failures from a real - // JoinedScrText) propagate normally. - string language; - try - { - language = scrText.GetJoinedText(bookNum).Settings.LanguageID.Id; - } - catch (NullReferenceException) - { - language = scrText.Settings.LanguageID?.Id ?? string.Empty; - } - - // Step 3: prep cell fields (PT9 :367, :239-264 from CLCell.VerseRef setter). - // PT9 stored Reference = verseRef.ToString() plus a separate locale-baked - // DisplayedReference = verseRef.ToLocalizedString(). PT10 carries one structured, - // bridge-capable ScriptureRange (null for a default verse); the displayed string is - // derived client-side from it, so no server-side localized presentation string. - ScriptureRange? reference = ScriptureRange.FromVerseRef(vref); - - string paragraphMarker = string.Empty; - var items = new List(); - - // Step 4: PT9 :379-428 — walk tokens. - var state = new ScrParserState(scrText, vref); - - // ScrParserState.UpdateState requires a concrete List (PT9 - // ScrParserState.cs:46). CAP-003's GetTokensForBook always constructs - // a List, so the `as` cast succeeds on the hot path with - // zero allocations; the `?? ToList()` fallback keeps us honest - // against the record's IReadOnlyList contract if any future - // caller supplies a different implementation. - List tokensList = - paragraphTokens.Tokens as List ?? paragraphTokens.Tokens.ToList(); - - for (int i = 0; i < tokensList.Count; ++i) - { - UsfmToken token = tokensList[i]; - state.UpdateState(tokensList, i); - - if (token.Type == UsfmTokenType.Paragraph) - { - // PT9 :398-403 — Markers-pipeline branch: record the marker - // whenever we see a paragraph token. The first occurrence - // wins because CAP-003's GetTokensForBook bundles tokens - // per paragraph-start, so there's exactly one paragraph - // token per bundle (at index 0). - paragraphMarker = token.Marker; - } - else if (token.Type == UsfmTokenType.Text) - { - // PT9 :406-411 — RTL prefix + CharTag.Marker as character style. - // PT9 also set a `textDisplayed` flag here that was passed to - // CLVerse's ctor; PT10's VerseItem doesn't carry that flag, so - // the write was dead and is omitted. - string text = scrText.RightToLeft ? StringUtils.rtlMarker + token.Text : token.Text; - string? characterStyle = state.CharTag != null ? state.CharTag.Marker : null; - items.Add(new TextItem(text, characterStyle)); - } - else if (token.Type == UsfmTokenType.Verse) - { - // PT9 :413-417 — verse-number item (bridges via token.Data preserved). - items.Add(new VerseItem(token.Data)); - } - // PT9 :419-427 (attribute / "fig_ref") — CrossReferences-checklist - // branch; NOT ported at CAP-004 (out of scope; no RED test). - } - - // Step 5: PT9 :430 PostProcessParagraph — NOT called at CAP-004. - // CAP-006 orchestration invokes MarkersDataSource.PostProcessParagraph - // per paragraph using the caller's showVerseText argument. - - var paragraph = new ChecklistParagraph(paragraphMarker, items); - var cell = new ChecklistCell( - Paragraphs: new List { paragraph }, - Reference: reference, - Language: language, - Error: null - ); - return (cell, vref); - } -} diff --git a/c-sharp/Checklists/ComparativeTextRef.cs b/c-sharp/Checklists/ComparativeTextRef.cs deleted file mode 100644 index 29075056a89..00000000000 --- a/c-sharp/Checklists/ComparativeTextRef.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 represents comparative texts via in-memory ScrText references; PT10 -// needs a serializable id over the PAPI boundary. -// -// History: an earlier shape paired Id with a Name field to preserve PT9's -// name-fallback resolver. PT10 is greenfield — every project carries a canonical -// GUID — so the Name fallback was dropped (markers-checklist PR #2254 review, -// TJ Couch). Resolution is GUID-only. -/// -/// Identifier of a comparative text (reference text, back translation, etc.) -/// as carried across the PAPI boundary. The id is a project GUID — resolution -/// is GUID-only (no name fallback). -/// -[method: JsonConstructor] -public record ComparativeTextRef(string Id); diff --git a/c-sharp/Checklists/EditLinkItem.cs b/c-sharp/Checklists/EditLinkItem.cs deleted file mode 100644 index 5c359de6cef..00000000000 --- a/c-sharp/Checklists/EditLinkItem.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLEditLink content-item representation (edit-link -// target for cells that pass the SetCellEditability permission check) -// Method: EditLinkItem (CLEditLink) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Edit-link content item carrying the BBB/CCC/VVV reference that the UI opens when -/// the user clicks the edit link. Present only when VAL-007 conditions are met. -/// See data-contracts.md §3.5. -/// -[method: JsonConstructor] -public record EditLinkItem(int BookNum, int ChapterNum, int VerseNum) : ChecklistContentItem; diff --git a/c-sharp/Checklists/EmptyResultMessage.cs b/c-sharp/Checklists/EmptyResultMessage.cs deleted file mode 100644 index b915cdb90f4..00000000000 --- a/c-sharp/Checklists/EmptyResultMessage.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists empty-result branches that emit one of two static -// messages ("identical markers" vs "no rows found with searched markers") -// Method: EmptyResultMessage (derived from PT9 string-message paths; extended to carry -// structured fields so the UI can render a localized message) -// Maps to: data-contracts.md §3.8 -/// -/// Structured empty-result message. The Variant field is one of -/// "identical" or "noResults". See data-contracts.md §3.8. -/// -[method: JsonConstructor] -public record EmptyResultMessage( - string Variant, - string Message, - IReadOnlyList? SearchedMarkers, - IReadOnlyList? SearchedBooks -); diff --git a/c-sharp/Checklists/EmptyResultMessageVariant.cs b/c-sharp/Checklists/EmptyResultMessageVariant.cs deleted file mode 100644 index ee21980c07b..00000000000 --- a/c-sharp/Checklists/EmptyResultMessageVariant.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: data-contracts.md §3.8 constrains EmptyResultMessage.Variant to a -// two-value literal union on the TS side ('identical' | 'noResults'). The C# -// record exposes Variant as a plain string; these constants pin the canonical -// values so call sites can't drift to typos, and future checklist types can -// extend the union by adding new constants here. -// Maps to: data-contracts.md §3.8 EmptyResultMessage — Variant constants. -/// -/// Canonical string values for . -/// Mirrors the TypeScript literal union in data-contracts.md §3.8 and is the -/// single source of truth used at construction sites in -/// MarkersDataSource.PostProcessRows and at assertion sites in the test -/// suite. Other checklist types (cross references, punctuation, etc.) should -/// extend this class with their own variant constants when they port. -/// -public static class EmptyResultMessageVariant -{ - /// - /// Emitted when all comparative texts had identical markers — no - /// difference to render (BHV-600). - /// - public const string Identical = "identical"; - - /// - /// Emitted when the marker filter is non-empty but no paragraphs in the - /// scanned books matched (BHV-106). - /// - public const string NoResults = "noResults"; -} diff --git a/c-sharp/Checklists/ErrorItem.cs b/c-sharp/Checklists/ErrorItem.cs deleted file mode 100644 index 6a1d6d8e05e..00000000000 --- a/c-sharp/Checklists/ErrorItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLError content-item representation (cell-level -// error string surfaced inline) -// Method: ErrorItem (CLError) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Cell-level error content item. Carries a message string that the UI renders inline -/// where a normal paragraph would appear. See data-contracts.md §3.5. -/// -[method: JsonConstructor] -public record ErrorItem(string Message) : ChecklistContentItem; diff --git a/c-sharp/Checklists/LinkItem.cs b/c-sharp/Checklists/LinkItem.cs deleted file mode 100644 index a10013e8902..00000000000 --- a/c-sharp/Checklists/LinkItem.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Text.Json.Serialization; -using Paranext.DataProvider.Scripture; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLLink content-item representation (cross-reference -// link rendered in the row data) -// Method: LinkItem (CLLink) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Reference link content item: a canonical scripture reference plus its display text. -/// See data-contracts.md §3.5. Reference is a bridge-capable -/// (the canonical platform scripture-reference type) — a -/// cross-reference link always targets a real reference, so it is non-nullable. -/// -[method: JsonConstructor] -public record LinkItem(ScriptureRange Reference, string DisplayText) : ChecklistContentItem; diff --git a/c-sharp/Checklists/Markers/MarkerPair.cs b/c-sharp/Checklists/Markers/MarkerPair.cs deleted file mode 100644 index 6cc71031b06..00000000000 --- a/c-sharp/Checklists/Markers/MarkerPair.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists.Markers; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists marker-equivalence parsing (tuples emitted by the -// MarkerSettingsForm validation logic) -// Method: MarkerPair (tuple of two marker names) -// Maps to: EXT-010 (data models), data-contracts.md §3.14 -/// -/// Parsed pair of equivalent paragraph markers (e.g., "p""q"). -/// Emitted by ValidateMarkerSettings. See data-contracts.md §3.14. -/// -[method: JsonConstructor] -public record MarkerPair(string Marker1, string Marker2); diff --git a/c-sharp/Checklists/Markers/MarkerSettings.cs b/c-sharp/Checklists/Markers/MarkerSettings.cs deleted file mode 100644 index 3f8648ae95b..00000000000 --- a/c-sharp/Checklists/Markers/MarkerSettings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists.Markers; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/MarkerSettingsForm (equivalentMarkers and -// markerFilter form fields) -// Method: MarkerSettings (DTO carrying the two PT9 form values) -// Maps to: EXT-010 (data models), data-contracts.md §2.2 -/// -/// Marker-settings DTO for the Markers checklist. EquivalentMarkers is the -/// bidirectional-mapping string (e.g., "p/q q1/q2"); MarkerFilter is -/// the space-separated list of paragraph markers to include (empty = all paragraph -/// markers per VAL-006). See data-contracts.md §2.2. -/// -[method: JsonConstructor] -public record MarkerSettings(string EquivalentMarkers, string MarkerFilter); diff --git a/c-sharp/Checklists/Markers/MarkerSettingsValidationResult.cs b/c-sharp/Checklists/Markers/MarkerSettingsValidationResult.cs deleted file mode 100644 index 509539fa1b2..00000000000 --- a/c-sharp/Checklists/Markers/MarkerSettingsValidationResult.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists.Markers; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/MarkerSettingsForm.btnOk_Click validation path -// Method: MarkerSettingsValidationResult (structured return of the pre-commit -// validation that PT9 surfaces inline on the form) -// Maps to: EXT-019, data-contracts.md §3.13 -/// -/// Validation result returned by ValidateMarkerSettings. Carries either the -/// parsed marker pairs (valid case) or an error message (invalid case). See -/// data-contracts.md §3.13. -/// -[method: JsonConstructor] -public record MarkerSettingsValidationResult( - bool Valid, - IReadOnlyList? ParsedPairs, - string? ErrorMessage -); diff --git a/c-sharp/Checklists/Markers/MarkersDataSource.cs b/c-sharp/Checklists/Markers/MarkersDataSource.cs deleted file mode 100644 index 2949b6a9119..00000000000 --- a/c-sharp/Checklists/Markers/MarkersDataSource.cs +++ /dev/null @@ -1,490 +0,0 @@ -using System.Text.RegularExpressions; -using Paratext.Data; - -namespace Paranext.DataProvider.Checklists.Markers; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs -// Extractions: EXT-003 (ParagraphMarkers), EXT-004 (PostProcessParagraph), -// EXT-005 (HasSameValue), EXT-006 (InitializeMarkerMappings), -// EXT-007 (PostProcessRows), EXT-013 (HeadingMarkers / NonHeadingParagraphMarkers) -// Behaviors: BHV-102, BHV-103, BHV-104, BHV-105, BHV-106, BHV-120 -// Invariants: INV-003, INV-004, INV-005 (bidirectional), INV-008 -// Validations: VAL-001, VAL-005, VAL-006 -// Contract: data-contracts.md §4.1 (leaf operations inside BuildChecklistData) -// -// Stateless per-method port. PT9 held `markerMappings` and `markerFilter` as -// instance fields populated by `InitializeMarkerMappings()`; PT10 returns the -// parsed tuple and the caller (CAP-006 orchestrator) threads them into -// `HasSameValue` / `PostProcessRows` explicitly (backend-alignment.md -// "Thread safety via statelessness"). -/// -/// Stateless leaf-logic utilities for the Markers checklist. See the test -/// suite in c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs -/// for the behavioural specification that each method must satisfy. -/// -internal static class MarkersDataSource -{ - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:208-214 - // Method: CLMarkersDataSource.ParagraphMarkers(int bookNum) - // Maps to: EXT-003 / BHV-102 / INV-003 / VAL-006 - /// - /// Returns paragraph-style markers from the stylesheet, optionally - /// intersected with a non-empty . - /// Enforces INV-003 (paragraph-style only) and VAL-006 (empty filter = all). - /// - public static HashSet ParagraphMarkers( - ScrStylesheet stylesheet, - HashSet markerFilter - ) => - MarkersWhere( - stylesheet, - tag => - tag.StyleType == ScrStyleType.scParagraphStyle - && (markerFilter.Count == 0 || markerFilter.Contains(tag.Marker)) - ); - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:221-226 - // Method: CLMarkersDataSource.PostProcessParagraph(CLCell, VerseRef, CLParagraph) - // Maps to: EXT-004 / BHV-103 / INV-004 - // - // EXPLANATION: - // PT9 mutated `paragraph.Items` in place: if ShowReferencedVerseText was - // false it cleared the list first, then inserted `new CLText("\\"+Marker)` - // at position 0. PT10 records are immutable (CAP-001 decision), so we - // return a NEW ChecklistParagraph via the record's `with` expression with - // a freshly built Items list. The backslash-prefix TextItem at index 0 - // is INV-004; showVerseText controls whether the original items follow. - /// - /// Returns a new paragraph with a backslash-prefixed marker - /// at position 0 (INV-004). When - /// is false, the remainder of the - /// original items is dropped; when true, they are preserved after the - /// marker item (BHV-103). - /// - public static ChecklistParagraph PostProcessParagraph( - ChecklistParagraph paragraph, - bool showVerseText - ) - { - var newItems = new List - { - new TextItem("\\" + paragraph.Marker, null), - }; - if (showVerseText) - newItems.AddRange(paragraph.Items); - return paragraph with { Items = newItems }; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:228-260 - // Methods: CLMarkersDataSource.HasSameValue(...) and IsEquivalentMarker(...) - // Maps to: EXT-005 / BHV-104 / INV-005 - /// - /// Returns true when every adjacent pair of cells in - /// has equal paragraph counts AND every paragraph - /// is equivalent (identical marker OR mapped via - /// ). Lookup honours INV-005 - /// bidirectional storage: only the forward edge is consulted per - /// ordered pair, but the dictionary always contains both directions. - /// - public static bool HasSameValue( - ChecklistRow row, - IReadOnlyDictionary> markerMappings - ) - { - // PT9:230-231 — single-cell rows are never a "match" (there's nothing - // to compare against). - if (row.Cells.Count <= 1) - return false; - - // PT9:236-247 — pairwise (c, c+1) column comparison. - for (int c = 0; c < row.Cells.Count - 1; c++) - { - ChecklistCell cell = row.Cells[c]; - ChecklistCell nextCell = row.Cells[c + 1]; - if (cell.Paragraphs.Count != nextCell.Paragraphs.Count) - return false; - - for (int para = 0; para < cell.Paragraphs.Count; para++) - { - if ( - !IsEquivalentMarker( - cell.Paragraphs[para].Marker, - nextCell.Paragraphs[para].Marker, - markerMappings - ) - ) - return false; - } - } - return true; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:251-260 - // Method: CLMarkersDataSource.IsEquivalentMarker(string, string) - // PT10 signature takes the mapping dictionary as a parameter (stateless); - // PT9 read it from the instance field `markerMappings`. - /// - /// Returns true when and - /// are equal, or when the forward mapping - /// edge (marker1 -> marker2) is present in - /// . Bidirectionality (INV-005) is - /// guaranteed by the caller storing both edges at mapping-parse time. - /// - private static bool IsEquivalentMarker( - string marker1, - string marker2, - IReadOnlyDictionary> markerMappings - ) - { - if (marker1 == marker2) - return true; - return markerMappings.TryGetValue(marker1, out var mappings) - && mappings != null - && mappings.Contains(marker2); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:262-292 - // plus the `MarkerFilter` getter at :185-195 for the backslash-strip step. - // Method: CLMarkersDataSource.InitializeMarkerMappings() - // Maps to: EXT-006 / BHV-105 / INV-005 (CRITICAL) / VAL-001 / VAL-005 / VAL-006 - // - // EXPLANATION: - // INV-005 (bidirectional storage) is the critical invariant: for every - // "a/b" pair in the mappings string we MUST record BOTH a->b AND b->a so - // that downstream `HasSameValue` calls get a symmetric equivalence even - // though the help docs describe mappings as a one-way list. Using - // TryGetValue + list-accumulation (rather than direct assignment) lets - // multiple pairs share a key ("q/q1 q/q2" -> q -> [q1, q2]) without - // clobbering (TS-017). Invalid tokens (0 slashes like "invalid" or 2+ - // slashes like "p/q1/q2") are silently dropped per VAL-005. - /// - /// Parses the two PT9 settings strings into the bidirectional mapping - /// dictionary (INV-005) and the filter set. Invalid pairs (0 or 2+ - /// slashes) are silently skipped per VAL-005; backslashes in the filter - /// are stripped per VAL-001; empty/whitespace filters become the empty - /// set per VAL-006. See BHV-105. - /// - public static ( - Dictionary> Mappings, - HashSet Filter - ) InitializeMarkerMappings(string equivalentMarkersInput, string markerFilterInput) => - ( - ParseEquivalentMarkerMappings(equivalentMarkersInput), - ParseMarkerFilter(markerFilterInput) - ); - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:185-195 (MarkerFilter - // getter — strips backslashes) + :267-269 (splits on whitespace into the filter set). - /// - /// Parses the raw marker-filter setting. Strips backslashes (VAL-001), - /// splits on whitespace, and returns an empty set for empty / - /// whitespace-only input (VAL-006). - /// - private static HashSet ParseMarkerFilter(string markerFilterInput) - { - var markerFilter = new HashSet(); - if (string.IsNullOrEmpty(markerFilterInput)) - return markerFilter; - - // VAL-001: strip backslashes before tokenising. - string filter = markerFilterInput.Replace(@"\", ""); - - // VAL-006: whitespace-only input yields no tokens (and therefore the empty set). - if (string.IsNullOrEmpty(filter.Trim())) - return markerFilter; - - foreach (string token in filter.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) - markerFilter.Add(token); - - return markerFilter; - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:271-291. - /// - /// Parses the raw equivalent-markers setting into a bidirectional - /// mapping dictionary. For every well-formed a/b pair, stores - /// both a -> b and b -> a so downstream - /// equivalence lookups are symmetric (INV-005). Tokens without exactly - /// one slash are silently skipped (VAL-005). - /// - private static Dictionary> ParseEquivalentMarkerMappings( - string equivalentMarkersInput - ) - { - var markerMappings = new Dictionary>(); - if (string.IsNullOrEmpty(equivalentMarkersInput)) - return markerMappings; - - foreach (string mapping in equivalentMarkersInput.Split(' ')) - { - string[] marks = mapping.Split('/'); - if (marks.Length != 2) - continue; // VAL-005: silently skip invalid pairs. - - // INV-005: record BOTH directions. TryGetValue + Add (rather than - // direct assignment) lets repeated left-hand or right-hand markers - // accumulate targets (e.g. "q/q1 q/q2" -> q -> [q1, q2]). - AddMapping(markerMappings, marks[0], marks[1]); - AddMapping(markerMappings, marks[1], marks[0]); - } - - return markerMappings; - } - - private static void AddMapping( - Dictionary> mappings, - string from, - string to - ) - { - if (!mappings.TryGetValue(from, out var targets)) - mappings[from] = targets = new List(); - targets.Add(to); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:294-320 - // Method: CLMarkersDataSource.PostProcessRows(CLData checklist) - // Maps to: EXT-007 / BHV-106 / INV-008 - // - // EXPLANATION: - // PT9 appended a synthetic "message row" into `checklist.Rows` so the UI - // could render it alongside real rows. PT10 separates the message out as - // an `EmptyResultMessage?` on `ChecklistResult` (data-contracts.md §3.1 / - // §3.8); returning null when rows are non-empty preserves INV-008's - // inverse direction. The "identical" variant uses a fixed English - // literal (asserted by gm-002 capture); the "noResults" variant carries - // SearchedMarkers/SearchedBooks so the UI can render a localized message - // — the PT9 formatted string is not stored here because the wording will - // change in PT10's localization layer (see plan Decisions Made). - /// - /// Returns an (variant "identical" when - /// no filter is active, "noResults" when one is) when - /// is empty, carrying the searched markers and - /// books so the UI can render the localized message. Returns - /// when rows are non-empty. Enforces INV-008. - /// - public static EmptyResultMessage? PostProcessRows( - IReadOnlyList rows, - HashSet markerFilter, - IReadOnlyList searchedBookNames - ) - { - if (rows.Count > 0) - return null; // INV-008 inverse — non-empty results carry no message. - - if (markerFilter.Count == 0) - { - // gm-002 localized message. We return the paranext-core localize key — - // the wrapping NetworkObject resolves it via LocalizationService.GetLocalizedString - // before sending over the wire (see patterns.errorHandling.backendLocalization). - // Maps to PT9 CLParagraphCellsDataSource_1. PT9 displayed this wrapped in "*** ... ***" - // as a UI decoration added outside the localized string (CLParagraphCellsDataSource.cs:313); - // we deliberately drop that wrapping so the UI can decorate as it sees fit. - return new EmptyResultMessage( - Variant: EmptyResultMessageVariant.Identical, - Message: IdenticalMarkersMessageKey, - SearchedMarkers: null, - SearchedBooks: null - ); - } - - // "noResults" variant — structured fields drive the UI's localized - // rendering (see data-contracts.md §3.8). Tests assert only on - // Variant + SearchedMarkers + SearchedBooks. - return new EmptyResultMessage( - Variant: EmptyResultMessageVariant.NoResults, - Message: string.Empty, - SearchedMarkers: markerFilter.ToList(), - SearchedBooks: searchedBookNames - ); - } - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:27-32 - // Method: CLParagraphCellsDataSource.HeadingMarkers(int bookNum) - // Maps to: EXT-013 / BHV-120 (heading) - /// - /// Returns heading paragraph markers from the stylesheet - /// (TextType == scSection AND StyleType == scParagraphStyle). See BHV-120. - /// - public static HashSet HeadingMarkers(ScrStylesheet stylesheet) => - MarkersWhere( - stylesheet, - tag => - tag.TextType == ScrTextType.scSection - && tag.StyleType == ScrStyleType.scParagraphStyle - ); - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/CLParagraphCellsDataSource.cs:38-43 - // Method: CLParagraphCellsDataSource.NonHeadingParagraphMarkers(int bookNum) - // Maps to: EXT-013 / BHV-120 (non-heading) - /// - /// Returns non-heading paragraph markers from the stylesheet - /// (TextType == scVerseText AND StyleType == scParagraphStyle). See BHV-120. - /// - public static HashSet NonHeadingParagraphMarkers(ScrStylesheet stylesheet) => - MarkersWhere( - stylesheet, - tag => - tag.TextType == ScrTextType.scVerseText - && tag.StyleType == ScrStyleType.scParagraphStyle - ); - - /// - /// Projects the markers of every in - /// matching - /// into a . Shared helper for - /// , , and - /// . - /// - private static HashSet MarkersWhere( - ScrStylesheet stylesheet, - Func predicate - ) => new(stylesheet.Tags.Where(predicate).Select(tag => tag.Marker)); - - // === PORTED FROM PT9 === - // Source: PT9/Paratext/Checklists/MarkerSettingsForm.cs:28-49 - // Method: MarkerSettingsForm.btnOk_Click(object, EventArgs) - // Maps to: EXT-019 / BHV-105 / BHV-312 (backend branch) / VAL-002 - // - // EXPLANATION: - // Ports PT9's Settings-dialog pre-commit validator as a pure function. The - // UI-layer concerns (Alert.Show, DialogResult.OK, control-read/write) are - // stripped; the pass/fail outcome is returned as a structured - // MarkerSettingsValidationResult so the UI-layer (CAP-UI-002) can either - // apply the setting or keep the dialog open and display the error. - // - // Five-step algorithm (line numbers reference PT9 source): - // 1. PT9:30 — null coerces to empty via `?? ""`. - // 2. PT9:31 — `Regex.Replace(equivalents.Trim(), " +", " ")` trims outer - // whitespace then collapses any run of spaces into a single space. The - // regex pattern " +" is one-or-more literal ASCII spaces (no culture - // sensitivity). This normalization matters both for `p/q q1/q2` → - // 2-token Split (TS-VAL-002-06) and for `" "` → `""` → empty-branch. - // 3. PT9:32 — an empty normalized string is VALID with zero pairs - // (TS-VAL-002-07). §3.13 requires ParsedPairs be non-null when - // Valid=true, so we return Array.Empty(). - // 4. PT9:34-43 — for each space-split token: require exactly one slash - // AND both sides non-empty after trim. On the FIRST failure, return - // fail-fast with the PT9 error literal and ParsedPairs=null. This - // matches PT9's bare `return;` statement at line 41. - // 5. PT9:44 — on a fully-validated input, return Valid=true with one - // MarkerPair per token in source order. - // - // Contract divergence from CAP-002.InitializeMarkerMappings (VAL-005): - // That method silently SKIPS invalid tokens to preserve runtime robustness - // (e.g., a corrupted settings file should not crash the data provider). - // ValidateMarkerSettings is the user-facing pre-commit path (VAL-002) and - // REJECTS invalid input so the dialog stays open. The two entry points - // share neither helper nor state by design; a REFACTOR pass may choose to - // hoist a shared "split-one-pair" helper, but that is a Refactorer - // decision, not a GREEN decision (see plan Decision 5 in the Test Writer - // plan; REFACTOR stays minimal for CAP-007). - // - // Structural invariant (data-contracts.md §3.13) — strictly enforced: - // Valid=true => ParsedPairs is non-null; ErrorMessage is null. - // Valid=false => ErrorMessage is non-null; ParsedPairs is null - // (NO partial-parse leakage — even pairs that parsed - // successfully before the failing token are discarded). - // - // PT9 error literal "Equivalent markers need to be entered in the form: - // p/q" (MarkerSettingsForm.cs:39) is returned verbatim. Localization - // (lookup key `MarkerSettingsForm_1`) is a UI-layer concern; the backend - // returns the canonical English string so the UI can either display it - // directly or swap in a localized variant. This matches CAP-002's - // `gm-002` `"*** Comparative texts have identical markers. ***"` pattern. - // - // Test spec: c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs (22 tests). - - /// - /// Localize key returned in the ErrorMessage field of a failed - /// result. Resolution happens - /// at the PAPI wire boundary (see - /// ) - /// — per the patterns.errorHandling.backendLocalization registry - /// entry, stateless services return the key and the wrapping - /// NetworkObject resolves it via LocalizationService.GetLocalizedString. - /// Maps to PT9 MarkerSettingsForm_1. Translations live in - /// extensions/src/platform-scripture/contributions/localizedStrings.json. - /// - public const string InvalidMarkerPairErrorKey = "%markersChecklist_errorInvalidMarkerPair%"; - - /// - /// English fallback text for , - /// used by the NetworkObject layer when the localization service is - /// unavailable (e.g. in unit tests). Byte-for-byte matches the PT9 - /// Localizer.Str default at MarkerSettingsForm.cs:39. - /// - public const string InvalidMarkerPairErrorFallback = - "Equivalent markers need to be entered in the form: p/q"; - - /// - /// Localize key placed in when - /// the "identical" empty-result variant is returned by - /// . Resolution happens at the PAPI wire - /// boundary (see - /// ). - /// Maps to PT9 CLParagraphCellsDataSource_1. Translations live in - /// extensions/src/platform-scripture/contributions/localizedStrings.json. - /// - public const string IdenticalMarkersMessageKey = - "%markersChecklist_emptyResult_identicalMarkers%"; - - /// - /// English fallback text for , - /// used by the NetworkObject layer when the localization service is - /// unavailable. Matches the PT9 Localizer.Str default at - /// CLParagraphCellsDataSource.cs:304 (bare — PT9's "*** ... ***" - /// decoration is a UI concern, not part of the localized string). - /// - public const string IdenticalMarkersMessageFallback = - "Comparative texts have identical markers."; - - /// - /// Validates a user-entered equivalent-markers string ("marker1/marker2" - /// pairs separated by spaces). Returns a - /// carrying either the parsed pairs (Valid=true) or the canonical - /// PT9 error message (Valid=false). Empty, null, and whitespace-only - /// inputs are treated as valid with an empty pair list. On the first - /// malformed token, validation fails fast without leaking partial results - /// (§3.13 mutex). See data-contracts.md §4.2 and EXT-019. - /// - public static MarkerSettingsValidationResult ValidateMarkerSettings(string equivalentMarkers) - { - // Step 1+2: PT9 lines 30-31 — null coerces to empty, then trim + collapse spaces. - string equivalents = Regex.Replace((equivalentMarkers ?? string.Empty).Trim(), " +", " "); - - // Step 3: PT9 line 32 — empty (including whitespace-only after normalization) - // is VALID with no pairs. Return Array.Empty so ParsedPairs is non-null per §3.13. - if (equivalents.Length == 0) - return new MarkerSettingsValidationResult(true, Array.Empty(), null); - - // Step 4: PT9 lines 34-43 — tokenize and validate each pair, fail-fast on invalid. - var pairs = new List(); - foreach (string pair in equivalents.Split(' ')) - { - string[] items = pair.Split('/'); - if (items.Length != 2 || items[0].Trim().Length == 0 || items[1].Trim().Length == 0) - { - // VAL-002 fail-fast: §3.13 requires ParsedPairs=null on failure - // (no partial-parse leak). Contrast with CAP-002's silent-skip - // VAL-005 path inside ParseEquivalentMarkerMappings. - return new MarkerSettingsValidationResult(false, null, InvalidMarkerPairErrorKey); - } - pairs.Add(new MarkerPair(items[0], items[1])); - } - - // Step 5: PT9 line 44 — all tokens valid; return pairs in source order. - return new MarkerSettingsValidationResult(true, pairs, null); - } -} diff --git a/c-sharp/Checklists/MessageItem.cs b/c-sharp/Checklists/MessageItem.cs deleted file mode 100644 index 95946fa22b4..00000000000 --- a/c-sharp/Checklists/MessageItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLMessage content-item representation (empty-result -// message rendered inline in lieu of a row; cf. PostProcessRows empty-handling) -// Method: MessageItem (CLMessage) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Inline message content item. Used for empty-result messages (INV-008). See -/// data-contracts.md §3.5. -/// -[method: JsonConstructor] -public record MessageItem(string Message) : ChecklistContentItem; diff --git a/c-sharp/Checklists/ResolvedComparativeText.cs b/c-sharp/Checklists/ResolvedComparativeText.cs deleted file mode 100644 index 7df04c0fcb4..00000000000 --- a/c-sharp/Checklists/ResolvedComparativeText.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: PT9 represents resolved comparative texts as in-memory ScrText -// references inside ChecklistsTool.comparativeTexts. PT10 must return a -// serializable shape across the PAPI boundary so the UI can render the -// resolved names / availability. -// Maps to: data-contracts.md §3.10 (ResolvedComparativeText) -/// -/// A single resolved comparative text with display information and -/// availability status. See data-contracts.md §3.10. -/// -/// -/// -/// is false when the text could not be -/// resolved by either GUID or name (INV-014). -/// preserves the originally-requested GUID even -/// when resolution fell back to name. -/// is the human-readable full project/text -/// name (may differ from the short ). -/// -/// -[method: JsonConstructor] -public record ResolvedComparativeText(string Id, string Name, string FullName, bool Available); diff --git a/c-sharp/Checklists/ResolvedComparativeTexts.cs b/c-sharp/Checklists/ResolvedComparativeTexts.cs deleted file mode 100644 index 23d785c60f1..00000000000 --- a/c-sharp/Checklists/ResolvedComparativeTexts.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === NEW IN PT10 === -// Reason: Container for the resolved-comparative-texts list returned by -// ChecklistService.ResolveComparativeTexts. PT9 held this as an -// in-memory List inside ChecklistsTool.comparativeTexts; PT10 -// needs a serializable wrapper. -// Maps to: data-contracts.md §3.11 (ResolvedComparativeTexts) -/// -/// Container for the resolved-comparative-texts list returned by -/// ChecklistService.ResolveComparativeTexts. Wraps an ordered list -/// of preserving request order with -/// the active project excluded (INV-014). See data-contracts.md §3.11. -/// -/// -/// -/// preserves the order of the input -/// requestedTexts argument (minus the active project). -/// Unresolvable entries appear with =false rather than -/// being omitted. -/// -/// -[method: JsonConstructor] -public record ResolvedComparativeTexts(IReadOnlyList Texts); diff --git a/c-sharp/Checklists/TextItem.cs b/c-sharp/Checklists/TextItem.cs deleted file mode 100644 index 676f91e9368..00000000000 --- a/c-sharp/Checklists/TextItem.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLText content-item representation -// Method: TextItem (CLText) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Plain text fragment within a paragraph. CharacterStyle is non-null when the -/// text is within a character style span (BHV-604). See data-contracts.md §3.5. -/// -[method: JsonConstructor] -public record TextItem(string Text, string? CharacterStyle) : ChecklistContentItem; diff --git a/c-sharp/Checklists/VerseItem.cs b/c-sharp/Checklists/VerseItem.cs deleted file mode 100644 index 71e6be7a4a6..00000000000 --- a/c-sharp/Checklists/VerseItem.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Paranext.DataProvider.Checklists; - -// === PORTED FROM PT9 === -// Source: PT9/Paratext/Checklists/CLVerse content-item representation -// Method: VerseItem (CLVerse) -// Maps to: EXT-010 (data models), data-contracts.md §3.5 -/// -/// Verse-number marker within a paragraph. VerseNumber is a string to carry -/// bridge notation (e.g., "24-38"). See data-contracts.md §3.5. -/// -[method: JsonConstructor] -public record VerseItem(string VerseNumber) : ChecklistContentItem; diff --git a/c-sharp/Program.cs b/c-sharp/Program.cs index 6e2221a35c6..8932ec1e289 100644 --- a/c-sharp/Program.cs +++ b/c-sharp/Program.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Paranext.DataProvider.Checklists; using Paranext.DataProvider.Checks; using Paranext.DataProvider.ManageBooks; using Paranext.DataProvider.NetworkObjects; @@ -89,7 +88,6 @@ public static async Task Main() var checkRunner = new CheckRunner(papi, inventoryDataProvider); var dblResources = new DblResourcesDataProvider(papi); var paratextRegistrationService = new ParatextRegistrationService(papi); - var checklistNetworkObject = new ChecklistNetworkObject(papi); var manageBooksService = new ManageBooksService( papi, paratextProjects, @@ -102,7 +100,6 @@ await Task.WhenAll( dblResources.RegisterDataProviderAsync(), paratextRegistrationService.InitializeAsync(), paratextSendReceiveService.InitializeAsync(), - checklistNetworkObject.InitializeAsync(), manageBooksService.RegisterNetworkObjectAsync() ); From c105055febac5e5cf7ec797b12e5465153cb3ab7 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:20:01 +0200 Subject: [PATCH 21/43] =?UTF-8?q?chore(markers-checklist):=20delete=20c-sh?= =?UTF-8?q?arp-tests/Checklists/=20=E2=80=94=20moved=20to=20TS=20test=20su?= =?UTF-8?q?ite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChecklistContentItemPolymorphismTests.cs | 446 ----- .../Checklists/ChecklistDataModelTests.cs | 723 -------- .../Checklists/ChecklistNetworkObjectTests.cs | 712 -------- .../Checklists/ChecklistRowBuilderTests.cs | 907 ---------- ...ChecklistServiceBuildChecklistDataTests.cs | 1541 ----------------- .../ChecklistServiceCellConstructionTests.cs | 743 -------- .../ChecklistServiceEditLinkGatingTests.cs | 356 ---- ...listServiceResolveComparativeTextsTests.cs | 519 ------ .../ChecklistServiceTokenExtractionTests.cs | 611 ------- .../Markers/MarkerSettingsValidationTests.cs | 554 ------ .../Markers/MarkersDataSourceTests.cs | 807 --------- 11 files changed, 7919 deletions(-) delete mode 100644 c-sharp-tests/Checklists/ChecklistContentItemPolymorphismTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistDataModelTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistNetworkObjectTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistRowBuilderTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistServiceBuildChecklistDataTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistServiceCellConstructionTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistServiceEditLinkGatingTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistServiceResolveComparativeTextsTests.cs delete mode 100644 c-sharp-tests/Checklists/ChecklistServiceTokenExtractionTests.cs delete mode 100644 c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs delete mode 100644 c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs diff --git a/c-sharp-tests/Checklists/ChecklistContentItemPolymorphismTests.cs b/c-sharp-tests/Checklists/ChecklistContentItemPolymorphismTests.cs deleted file mode 100644 index 65e442159e3..00000000000 --- a/c-sharp-tests/Checklists/ChecklistContentItemPolymorphismTests.cs +++ /dev/null @@ -1,446 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.JsonUtils; -using Paranext.DataProvider.Scripture; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists; - -/// -/// BE-1 EARLY VERIFICATION tests for the polymorphic -/// hierarchy. -/// -/// -/// Strategic plan (CAP-001, "Early Verification Step (BE-1)"): "verify -/// [JsonDerivedType] polymorphic serialization end-to-end: write a C# round-trip -/// test that serializes a list containing one of each of the 6 ChecklistContentItem -/// subtypes via SerializationOptions.CreateSerializationOptions(), then deserializes -/// back and asserts subtype identity and field preservation. If the round-trip fails, fall -/// back to an explicit type-discriminator DTO." -/// -/// -/// -/// If the tests in this file fail with a System.Text.Json polymorphism error (e.g., -/// "Runtime type 'TextItem' is not supported by polymorphic type 'ChecklistContentItem'"), -/// that is the trigger for the fallback described in the strategic plan — do NOT try to -/// hack around it in the implementation; escalate to the orchestrator so downstream -/// capabilities plan against the fallback shape before BE-2 starts. -/// -/// -/// Traceability: -/// - Capability: CAP-001 -/// - Acceptance: gm-001 shape representation -/// - Behaviors: BHV-113 (CLParagraph and Content Types) -/// - Contract: data-contracts.md §3.5 (ChecklistContentItem) -/// -[TestFixture] -internal class ChecklistContentItemPolymorphismTests -{ - private JsonSerializerOptions _options = null!; - - [SetUp] - public void SetUp() - { - _options = SerializationOptions.CreateSerializationOptions(); - } - - /// Build a single-verse from a string like "GEN 1:1". - private static ScriptureRange SingleRange(string reference) => - new ScriptureRange(new VerseRef(reference), null); - - /// - /// Assert a is the expected single verse (checks the - /// structured book/chapter/verse rather than relying on VerseRef equality). - /// - private static void AssertSingleRef(ScriptureRange? range, string book, int chapter, int verse) - { - Assert.That(range, Is.Not.Null); - Assert.That(range!.Start.Book, Is.EqualTo(book)); - Assert.That(range.Start.ChapterNum, Is.EqualTo(chapter)); - Assert.That(range.Start.VerseNum, Is.EqualTo(verse)); - Assert.That(range.End, Is.Null, "single-verse range carries no End"); - } - - // --------------------------------------------------------------------- - // Per-subtype construction (compile-time gate: all 6 subtypes must exist) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.TextItem")] - public void TextItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new TextItem("hello", "wj"); - - Assert.That(item, Is.TypeOf()); - var text = (TextItem)item; - Assert.That(text.Text, Is.EqualTo("hello")); - Assert.That(text.CharacterStyle, Is.EqualTo("wj")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.VerseItem")] - public void VerseItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new VerseItem("24-38"); - - Assert.That(item, Is.TypeOf()); - Assert.That(((VerseItem)item).VerseNumber, Is.EqualTo("24-38")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.EditLinkItem")] - public void EditLinkItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new EditLinkItem(40, 1, 1); - - Assert.That(item, Is.TypeOf()); - var link = (EditLinkItem)item; - Assert.That(link.BookNum, Is.EqualTo(40)); - Assert.That(link.ChapterNum, Is.EqualTo(1)); - Assert.That(link.VerseNum, Is.EqualTo(1)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.LinkItem")] - public void LinkItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new LinkItem(SingleRange("MAT 1:1"), "Matthew 1:1"); - - Assert.That(item, Is.TypeOf()); - var link = (LinkItem)item; - AssertSingleRef(link.Reference, "MAT", 1, 1); - Assert.That(link.DisplayText, Is.EqualTo("Matthew 1:1")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.ErrorItem")] - public void ErrorItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new ErrorItem("parse failure"); - - Assert.That(item, Is.TypeOf()); - Assert.That(((ErrorItem)item).Message, Is.EqualTo("parse failure")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.MessageItem")] - public void MessageItem_CanBeConstructedAndAssignedToBase() - { - ChecklistContentItem item = new MessageItem("No rows found"); - - Assert.That(item, Is.TypeOf()); - Assert.That(((MessageItem)item).Message, Is.EqualTo("No rows found")); - } - - // --------------------------------------------------------------------- - // Per-subtype JSON round-trip via the abstract base type - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.TextItem")] - public void TextItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new TextItem("\\p", null); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf(), "subtype identity lost after deserialize"); - var text = (TextItem)actual!; - Assert.That(text.Text, Is.EqualTo("\\p")); - Assert.That(text.CharacterStyle, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.VerseItem")] - public void VerseItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new VerseItem("7"); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf()); - Assert.That(((VerseItem)actual!).VerseNumber, Is.EqualTo("7")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.EditLinkItem")] - public void EditLinkItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new EditLinkItem(40, 28, 20); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf()); - var link = (EditLinkItem)actual!; - Assert.That(link.BookNum, Is.EqualTo(40)); - Assert.That(link.ChapterNum, Is.EqualTo(28)); - Assert.That(link.VerseNum, Is.EqualTo(20)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.LinkItem")] - public void LinkItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new LinkItem(SingleRange("REV 22:21"), "Rev 22:21"); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf()); - var link = (LinkItem)actual!; - AssertSingleRef(link.Reference, "REV", 22, 21); - Assert.That(link.DisplayText, Is.EqualTo("Rev 22:21")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.ErrorItem")] - public void ErrorItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new ErrorItem("could not read verse"); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf()); - Assert.That(((ErrorItem)actual!).Message, Is.EqualTo("could not read verse")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.MessageItem")] - public void MessageItem_SerializedAsBase_RoundTripsPreservingSubtypeAndFields() - { - ChecklistContentItem item = new MessageItem("Comparative texts have identical markers."); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.TypeOf()); - Assert.That(((MessageItem)actual!).Message, Does.Contain("identical markers")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem.TextItem")] - public void TextItem_WithCharacterStylePopulated_PreservesField() - { - // Per §3.5 validation: TextItem.CharacterStyle is non-null when text is within - // a character style span. This exercises the non-null variant. - ChecklistContentItem item = new TextItem("Jesus wept.", "wj"); - - var json = JsonSerializer.Serialize(item, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - var text = (TextItem)actual!; - Assert.That(text.CharacterStyle, Is.EqualTo("wj")); - } - - // --------------------------------------------------------------------- - // The BE-1 flagship test: a list of ALL 6 subtypes round-trips as a list. - // This is the specific test called out in the strategic plan. - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem")] - [Property("BehaviorId", "BHV-113")] - public void PolymorphicList_OneOfEachSubtype_RoundTripsPreservingAllSubtypeIdentities() - { - // This IS the explicit BE-1 early-verification test (strategic-plan-backend.md - // CAP-001, "Early Verification Step (BE-1)"). If this fails, the strategic plan - // says to escalate and consider falling back to an explicit discriminator DTO. - var items = new List - { - new TextItem("\\p", null), - new VerseItem("1"), - new EditLinkItem(1, 1, 1), - new LinkItem(SingleRange("GEN 1:1"), "Gen 1:1"), - new ErrorItem("cell error"), - new MessageItem("empty result"), - }; - - var json = JsonSerializer.Serialize(items, _options); - var actual = JsonSerializer.Deserialize>(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual, Has.Count.EqualTo(6)); - Assert.That(actual![0], Is.TypeOf(), "index 0 should deserialize as TextItem"); - Assert.That(actual[1], Is.TypeOf(), "index 1 should deserialize as VerseItem"); - Assert.That( - actual[2], - Is.TypeOf(), - "index 2 should deserialize as EditLinkItem" - ); - Assert.That(actual[3], Is.TypeOf(), "index 3 should deserialize as LinkItem"); - Assert.That(actual[4], Is.TypeOf(), "index 4 should deserialize as ErrorItem"); - Assert.That( - actual[5], - Is.TypeOf(), - "index 5 should deserialize as MessageItem" - ); - - // Field preservation across every subtype in the mixed list. - Assert.That(((TextItem)actual[0]).Text, Is.EqualTo("\\p")); - Assert.That(((VerseItem)actual[1]).VerseNumber, Is.EqualTo("1")); - var link = (EditLinkItem)actual[2]; - Assert.That(link.BookNum, Is.EqualTo(1)); - Assert.That(link.ChapterNum, Is.EqualTo(1)); - Assert.That(link.VerseNum, Is.EqualTo(1)); - AssertSingleRef(((LinkItem)actual[3]).Reference, "GEN", 1, 1); - Assert.That(((LinkItem)actual[3]).DisplayText, Is.EqualTo("Gen 1:1")); - Assert.That(((ErrorItem)actual[4]).Message, Is.EqualTo("cell error")); - Assert.That(((MessageItem)actual[5]).Message, Is.EqualTo("empty result")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistContentItem")] - public void PolymorphicList_ContainsRepeatedSubtypes_EachDeserializedCorrectly() - { - // gm-001 row shape: one paragraph's items contain multiple TextItems interleaved - // with VerseItems. The polymorphic serializer must handle repeated subtypes. - var items = new List - { - new TextItem("\\p", null), - new VerseItem("1"), - new TextItem("one. ", null), - new VerseItem("2"), - new TextItem("two, ", null), - }; - - var json = JsonSerializer.Serialize(items, _options); - var actual = JsonSerializer.Deserialize>(json, _options); - - Assert.That(actual, Has.Count.EqualTo(5)); - Assert.That(actual![0], Is.TypeOf()); - Assert.That(actual[1], Is.TypeOf()); - Assert.That(actual[2], Is.TypeOf()); - Assert.That(actual[3], Is.TypeOf()); - Assert.That(actual[4], Is.TypeOf()); - Assert.That(((TextItem)actual[2]).Text, Is.EqualTo("one. ")); - Assert.That(((VerseItem)actual[3]).VerseNumber, Is.EqualTo("2")); - } - - // --------------------------------------------------------------------- - // Acceptance test — gm-001 shape representability - // - // CAP-001 is pure data models. The *production* of gm-001 output is the job of - // CAP-002 through CAP-006. At this layer we only assert that the contracts CAN - // carry gm-001's structure through a full JSON round-trip without shape loss. - // When this test passes (together with the polymorphic-list test above), CAP-001 - // is structurally complete. - // --------------------------------------------------------------------- - - [Test] - [Category("Acceptance")] - [Property("CapabilityId", "CAP-001")] - [Property("GoldenMasterId", "gm-001")] - [Property("ScenarioId", "TS-001")] - [Property("BehaviorId", "BHV-110")] - public void Acceptance_Gm001RowShape_CanBeRepresentedByRecordsAndRoundTripsThroughJson() - { - // Shape lifted from: - // .context/features/markers-checklist/golden-masters/gm-001-single-project-markers/ - // expected-output.json - // First row: EXO 20:1, single cell, paragraph \p with items: - // CLText("\\p"), CLVerse("1"), CLText("one. "), CLVerse("2"), CLText("two, ") - // - // In the PT10 shape, these become: TextItem/VerseItem/TextItem/VerseItem/TextItem - // inside a ChecklistParagraph(marker="p"), inside a ChecklistCell, inside a - // ChecklistRow, inside a ChecklistResult. - var paragraph = new ChecklistParagraph( - Marker: "p", - Items: new List - { - new TextItem("\\p", null), - new VerseItem("1"), - new TextItem("one. ", null), - new VerseItem("2"), - new TextItem("two, ", null), - } - ); - var cell = new ChecklistCell( - Paragraphs: new List { paragraph }, - Reference: SingleRange("EXO 20:1"), - Language: "en", - Error: null - ); - var row = new ChecklistRow( - Cells: new List { cell }, - IsMatch: true, - IncludeEditLink: false, - Score: 0, - FirstRef: SingleRange("EXO 20:1") - ); - var result = new ChecklistResult( - Rows: new List { row }, - ColumnHeaders: new List { "TSTGM001" }, - ColumnProjectIds: new List { "project-tstgm001" }, - ExcludedCount: 0, - HelpText: null, - Truncated: false, - EmptyResultMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - // Round-trip preserves the full nested shape. - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Rows, Has.Count.EqualTo(1)); - var actualRow = actual.Rows[0]; - Assert.That(actualRow.IsMatch, Is.True, "single-column => IsMatch=true (INV-002)"); - Assert.That(actualRow.Cells, Has.Count.EqualTo(1)); - var actualCell = actualRow.Cells[0]; - AssertSingleRef(actualCell.Reference, "EXO", 20, 1); - Assert.That(actualCell.Paragraphs, Has.Count.EqualTo(1)); - var actualPara = actualCell.Paragraphs[0]; - Assert.That(actualPara.Marker, Is.EqualTo("p")); - Assert.That( - actualPara.Marker, - Does.Not.StartWith("\\"), - "INV-004: marker stored without backslash" - ); - Assert.That(actualPara.Items, Has.Count.EqualTo(5)); - Assert.That(actualPara.Items[0], Is.TypeOf()); - Assert.That(((TextItem)actualPara.Items[0]).Text, Is.EqualTo("\\p")); - Assert.That(actualPara.Items[1], Is.TypeOf()); - Assert.That(((VerseItem)actualPara.Items[1]).VerseNumber, Is.EqualTo("1")); - Assert.That(actualPara.Items[2], Is.TypeOf()); - Assert.That(((TextItem)actualPara.Items[2]).Text, Is.EqualTo("one. ")); - Assert.That(actualPara.Items[3], Is.TypeOf()); - Assert.That(((VerseItem)actualPara.Items[3]).VerseNumber, Is.EqualTo("2")); - Assert.That(actualPara.Items[4], Is.TypeOf()); - Assert.That(((TextItem)actualPara.Items[4]).Text, Is.EqualTo("two, ")); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistDataModelTests.cs b/c-sharp-tests/Checklists/ChecklistDataModelTests.cs deleted file mode 100644 index 64fe18d6d3c..00000000000 --- a/c-sharp-tests/Checklists/ChecklistDataModelTests.cs +++ /dev/null @@ -1,723 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.JsonUtils; -using Paranext.DataProvider.Scripture; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase contract tests for CAP-001 data models. -/// -/// These tests will NOT compile until the implementer creates the record types under -/// Paranext.DataProvider.Checklists. That is intentional: the test file IS the -/// specification — the compile error is the first layer of the RED signal; the test -/// failures are the second. -/// -/// Scope: the 10 non-polymorphic records in CAP-001. The polymorphic -/// hierarchy is exercised in -/// ChecklistContentItemPolymorphismTests. -/// -/// Traceability: -/// - Capability: CAP-001 -/// - Behaviors: BHV-110, BHV-111, BHV-112, BHV-113, BHV-119 -/// - Contracts: data-contracts.md §2.1, §2.2, §2.4, §3.1, §3.2, §3.3, §3.4, §3.6, §3.8, §3.13, §3.14 -/// - Invariants: INV-001, INV-004 -/// -[TestFixture] -internal class ChecklistDataModelTests -{ - private JsonSerializerOptions _options = null!; - - [SetUp] - public void SetUp() - { - _options = SerializationOptions.CreateSerializationOptions(); - } - - /// Build a single-verse from a string like "GEN 1:1". - private static ScriptureRange SingleRange(string reference) => - new ScriptureRange(new VerseRef(reference), null); - - /// - /// Assert a round-tripped to the expected single verse - /// (checking the structured book/chapter/verse rather than relying on VerseRef equality). - /// - private static void AssertSingleRef(ScriptureRange? range, string book, int chapter, int verse) - { - Assert.That(range, Is.Not.Null); - Assert.That(range!.Start.Book, Is.EqualTo(book)); - Assert.That(range.Start.ChapterNum, Is.EqualTo(chapter)); - Assert.That(range.Start.VerseNum, Is.EqualTo(verse)); - Assert.That(range.End, Is.Null, "single-verse range carries no End"); - } - - // --------------------------------------------------------------------- - // ChecklistRequest (§2.1) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistRequest")] - [Property("ScenarioId", "TS-001")] - [Property("BehaviorId", "BHV-110")] - public void ChecklistRequest_ConstructWithAllFields_RoundTripsThroughJson() - { - var markerSettings = new MarkerSettings("p/q q1/q2", "p q"); - var range = new ScriptureRange(new VerseRef(1, 1, 0), new VerseRef(1, 1, 31)); - var request = new ChecklistRequest( - ProjectId: "project-a", - ComparativeTextIds: new List { "compA", "compB" }, - MarkerSettings: markerSettings, - VerseRange: range, - HideMatches: true, - ShowVerseText: false - ); - - var json = JsonSerializer.Serialize(request, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.ProjectId, Is.EqualTo("project-a")); - Assert.That(actual.ComparativeTextIds, Is.EqualTo(new[] { "compA", "compB" })); - Assert.That(actual.MarkerSettings.EquivalentMarkers, Is.EqualTo("p/q q1/q2")); - Assert.That(actual.MarkerSettings.MarkerFilter, Is.EqualTo("p q")); - Assert.That(actual.HideMatches, Is.True); - Assert.That(actual.ShowVerseText, Is.False); - Assert.That(actual.VerseRange, Is.Not.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistRequest")] - public void ChecklistRequest_NullableFieldsNull_RoundTripsThroughJson() - { - // VerseRange is nullable per contract §2.1. - var request = new ChecklistRequest( - ProjectId: "p1", - ComparativeTextIds: new List(), - MarkerSettings: new MarkerSettings("", ""), - VerseRange: null, - HideMatches: false, - ShowVerseText: false - ); - - var json = JsonSerializer.Serialize(request, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.VerseRange, Is.Null); - Assert.That(actual.ComparativeTextIds, Is.Empty); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistRequest")] - public void ChecklistRequest_SerializesWithCamelCasePropertyNames() - { - var request = new ChecklistRequest( - ProjectId: "p1", - ComparativeTextIds: new List(), - MarkerSettings: new MarkerSettings("", ""), - VerseRange: null, - HideMatches: false, - ShowVerseText: false - ); - - var json = JsonSerializer.Serialize(request, _options); - - // camelCase is enforced by SerializationOptions; this is the cross-boundary - // wire-shape guarantee downstream TS consumers depend on. - Assert.That(json, Does.Contain("\"projectId\"")); - Assert.That(json, Does.Contain("\"comparativeTextIds\"")); - Assert.That(json, Does.Contain("\"markerSettings\"")); - Assert.That(json, Does.Contain("\"hideMatches\"")); - Assert.That(json, Does.Contain("\"showVerseText\"")); - Assert.That(json, Does.Not.Contain("\"ProjectId\"")); - } - - // --------------------------------------------------------------------- - // MarkerSettings (§2.2) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerSettings")] - public void MarkerSettings_RoundTripsThroughJson() - { - var settings = new MarkerSettings("p/q q1/q2", "p q mt"); - - var json = JsonSerializer.Serialize(settings, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.EquivalentMarkers, Is.EqualTo("p/q q1/q2")); - Assert.That(actual.MarkerFilter, Is.EqualTo("p q mt")); - Assert.That(json, Does.Contain("\"equivalentMarkers\"")); - Assert.That(json, Does.Contain("\"markerFilter\"")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerSettings")] - public void MarkerSettings_EmptyStrings_SurviveRoundTrip() - { - // Per VAL-006: empty MarkerFilter means "all paragraph markers". - // The record must accept and preserve empty strings without coercion. - var settings = new MarkerSettings("", ""); - - var json = JsonSerializer.Serialize(settings, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.EquivalentMarkers, Is.EqualTo("")); - Assert.That(actual.MarkerFilter, Is.EqualTo("")); - } - - // --------------------------------------------------------------------- - // ComparativeTextRef (§2.4) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ComparativeTextRef")] - public void ComparativeTextRef_RoundTripsThroughJson() - { - var refItem = new ComparativeTextRef(Id: "11111111-2222-3333-4444-555555555555"); - - var json = JsonSerializer.Serialize(refItem, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Id, Is.EqualTo("11111111-2222-3333-4444-555555555555")); - Assert.That(json, Does.Contain("\"id\"")); - } - - // --------------------------------------------------------------------- - // ChecklistResult (§3.1) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistResult")] - [Property("BehaviorId", "BHV-110")] - public void ChecklistResult_ConstructEmpty_RoundTripsThroughJson() - { - var result = new ChecklistResult( - Rows: new List(), - ColumnHeaders: new List { "ProjA" }, - ColumnProjectIds: new List { "project-a" }, - ExcludedCount: 0, - HelpText: null, - Truncated: false, - EmptyResultMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Rows, Is.Empty); - Assert.That(actual.ColumnHeaders, Is.EqualTo(new[] { "ProjA" })); - Assert.That(actual.ColumnProjectIds, Is.EqualTo(new[] { "project-a" })); - Assert.That(actual.ExcludedCount, Is.Zero); - Assert.That(actual.HelpText, Is.Null); - Assert.That(actual.Truncated, Is.False); - Assert.That(actual.EmptyResultMessage, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistResult")] - public void ChecklistResult_PopulatedWithRows_RoundTripsThroughJson() - { - var cell = new ChecklistCell( - Paragraphs: new List - { - new("p", new List { new TextItem("\\p", null) }), - }, - Reference: SingleRange("GEN 1:1"), - Language: "en", - Error: null - ); - var row = new ChecklistRow( - Cells: new List { cell }, - IsMatch: true, - IncludeEditLink: false, - Score: 1.0, - FirstRef: SingleRange("GEN 1:1") - ); - var result = new ChecklistResult( - Rows: new List { row }, - ColumnHeaders: new List { "ProjA" }, - ColumnProjectIds: new List { "project-a" }, - ExcludedCount: 0, - HelpText: "Markers checklist help", - Truncated: false, - EmptyResultMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Rows, Has.Count.EqualTo(1)); - Assert.That(actual.Rows[0].Cells, Has.Count.EqualTo(1)); - AssertSingleRef(actual.Rows[0].FirstRef, "GEN", 1, 1); - Assert.That(actual.HelpText, Is.EqualTo("Markers checklist help")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistResult")] - public void ChecklistResult_SerializesWithCamelCasePropertyNames() - { - var result = new ChecklistResult( - Rows: new List(), - ColumnHeaders: new List(), - ColumnProjectIds: new List(), - ExcludedCount: 7, - HelpText: "h", - Truncated: true, - EmptyResultMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - - Assert.That(json, Does.Contain("\"rows\"")); - Assert.That(json, Does.Contain("\"columnHeaders\"")); - Assert.That(json, Does.Contain("\"columnProjectIds\"")); - Assert.That(json, Does.Contain("\"excludedCount\"")); - Assert.That(json, Does.Contain("\"helpText\"")); - Assert.That(json, Does.Contain("\"truncated\"")); - } - - // --------------------------------------------------------------------- - // ChecklistRow (§3.2) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistRow")] - [Property("BehaviorId", "BHV-111")] - public void ChecklistRow_ConstructAndRoundTrip() - { - var row = new ChecklistRow( - Cells: new List(), - IsMatch: false, - IncludeEditLink: true, - Score: 3.14, - FirstRef: SingleRange("EXO 20:1") - ); - - var json = JsonSerializer.Serialize(row, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.IsMatch, Is.False); - Assert.That(actual.IncludeEditLink, Is.True); - Assert.That(actual.Score, Is.EqualTo(3.14)); - AssertSingleRef(actual.FirstRef, "EXO", 20, 1); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistRow")] - public void ChecklistRow_FirstRefNull_SurvivesRoundTrip() - { - // FirstRef is nullable per §3.2 (lazy-computed; may be null if no cells have refs). - var row = new ChecklistRow( - Cells: new List(), - IsMatch: true, - IncludeEditLink: false, - Score: 0, - FirstRef: null - ); - - var json = JsonSerializer.Serialize(row, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.FirstRef, Is.Null); - } - - // --------------------------------------------------------------------- - // ChecklistCell (§3.3) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistCell")] - [Property("BehaviorId", "BHV-112")] - public void ChecklistCell_ConstructAndRoundTrip() - { - var cell = new ChecklistCell( - Paragraphs: new List - { - new("p", new List { new TextItem("\\p", null) }), - }, - Reference: SingleRange("GEN 1:1"), - Language: "en", - Error: null - ); - - var json = JsonSerializer.Serialize(cell, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - AssertSingleRef(actual!.Reference, "GEN", 1, 1); - Assert.That(actual.Language, Is.EqualTo("en")); - Assert.That(actual.Paragraphs, Has.Count.EqualTo(1)); - Assert.That(actual.Error, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistCell")] - public void ChecklistCell_EmptyParagraphs_RepresentsMissingVerse() - { - // Per §3.3 validation: "Paragraphs may be empty for columns where the verse - // does not exist (missing verse = empty cell, INV-001)". An empty placeholder - // cell carries a null Reference (see ChecklistRowBuilder.EmptyCell). - var cell = new ChecklistCell( - Paragraphs: new List(), - Reference: null, - Language: "en", - Error: null - ); - - var json = JsonSerializer.Serialize(cell, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.Paragraphs, Is.Empty); - Assert.That(actual.Reference, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistCell")] - public void ChecklistCell_ErrorFieldPopulated_SurvivesRoundTrip() - { - var cell = new ChecklistCell( - Paragraphs: new List(), - Reference: SingleRange("GEN 1:1"), - Language: "en", - Error: "Unreadable verse" - ); - - var json = JsonSerializer.Serialize(cell, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.Error, Is.EqualTo("Unreadable verse")); - } - - // --------------------------------------------------------------------- - // ChecklistParagraph (§3.4) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistParagraph")] - [Property("BehaviorId", "BHV-113")] - public void ChecklistParagraph_ConstructAndRoundTrip() - { - var para = new ChecklistParagraph( - Marker: "q1", - Items: new List - { - new TextItem("\\q1", null), - new VerseItem("2"), - new TextItem("poetry line", null), - } - ); - - var json = JsonSerializer.Serialize(para, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Marker, Is.EqualTo("q1")); - Assert.That(actual.Items, Has.Count.EqualTo(3)); - } - - // --------------------------------------------------------------------- - // EmptyResultMessage (§3.8) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "EmptyResultMessage")] - public void EmptyResultMessage_IdenticalVariant_RoundTrips() - { - var msg = new EmptyResultMessage( - Variant: "identical", - Message: "Comparative texts have identical markers.", - SearchedMarkers: null, - SearchedBooks: null - ); - - var json = JsonSerializer.Serialize(msg, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Variant, Is.EqualTo("identical")); - Assert.That(actual.Message, Does.Contain("identical markers")); - Assert.That(actual.SearchedMarkers, Is.Null); - Assert.That(actual.SearchedBooks, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "EmptyResultMessage")] - public void EmptyResultMessage_NoResultsVariant_RoundTrips() - { - var msg = new EmptyResultMessage( - Variant: "noResults", - Message: "No rows found for the selected markers", - SearchedMarkers: new List { "p", "q" }, - SearchedBooks: new List { "GEN", "EXO" } - ); - - var json = JsonSerializer.Serialize(msg, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.Variant, Is.EqualTo("noResults")); - Assert.That(actual.SearchedMarkers, Is.EqualTo(new[] { "p", "q" })); - Assert.That(actual.SearchedBooks, Is.EqualTo(new[] { "GEN", "EXO" })); - } - - // --------------------------------------------------------------------- - // ChecklistResultError + ChecklistErrorCodes (§3.1 / §3.6) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "ChecklistResultError")] - public void ChecklistResultError_RoundTripsThroughJson() - { - var err = new ChecklistResultError( - Code: ChecklistErrorCodes.ProjectNotFound, - Message: "Project xyz does not exist" - ); - - var json = JsonSerializer.Serialize(err, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Code, Is.EqualTo("PROJECT_NOT_FOUND")); - Assert.That(actual.Message, Is.EqualTo("Project xyz does not exist")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ChecklistErrorCodes")] - public void ChecklistErrorCodes_AllCodesMatchContract() - { - // These exact string values are the wire contract. See data-contracts.md §3.6. - Assert.That(ChecklistErrorCodes.ProjectNotFound, Is.EqualTo("PROJECT_NOT_FOUND")); - Assert.That(ChecklistErrorCodes.InvalidState, Is.EqualTo("INVALID_STATE")); - Assert.That(ChecklistErrorCodes.InvalidChecklistType, Is.EqualTo("INVALID_CHECKLIST_TYPE")); - Assert.That(ChecklistErrorCodes.InvalidVerseRange, Is.EqualTo("INVALID_VERSE_RANGE")); - Assert.That( - ChecklistErrorCodes.InvalidMarkerSettings, - Is.EqualTo("INVALID_MARKER_SETTINGS") - ); - Assert.That(ChecklistErrorCodes.MaxRowsExceeded, Is.EqualTo("MAX_ROWS_EXCEEDED")); - Assert.That(ChecklistErrorCodes.Cancelled, Is.EqualTo("CANCELLED")); - } - - // --------------------------------------------------------------------- - // MarkerSettingsValidationResult + MarkerPair (§3.13 + §3.14) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerSettingsValidationResult")] - public void MarkerSettingsValidationResult_ValidCase_RoundTrips() - { - var result = new MarkerSettingsValidationResult( - Valid: true, - ParsedPairs: new List { new("p", "q"), new("q1", "q2") }, - ErrorMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual, Is.Not.Null); - Assert.That(actual!.Valid, Is.True); - Assert.That(actual.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(actual.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(actual.ParsedPairs[0].Marker2, Is.EqualTo("q")); - Assert.That(actual.ErrorMessage, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerSettingsValidationResult")] - public void MarkerSettingsValidationResult_InvalidCase_RoundTrips() - { - var result = new MarkerSettingsValidationResult( - Valid: false, - ParsedPairs: null, - ErrorMessage: "Invalid pair: expected single '/'" - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.Valid, Is.False); - Assert.That(actual.ParsedPairs, Is.Null); - Assert.That(actual.ErrorMessage, Does.Contain("Invalid pair")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerPair")] - public void MarkerPair_RoundTripsWithCamelCaseFields() - { - var pair = new MarkerPair("p", "q"); - - var json = JsonSerializer.Serialize(pair, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - Assert.That(actual!.Marker1, Is.EqualTo("p")); - Assert.That(actual.Marker2, Is.EqualTo("q")); - Assert.That(json, Does.Contain("\"marker1\"")); - Assert.That(json, Does.Contain("\"marker2\"")); - } - - // --------------------------------------------------------------------- - // Record equality (positional records → value equality) - // --------------------------------------------------------------------- - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "MarkerSettings")] - public void MarkerSettings_EqualityIsValueBased() - { - // Positional records give us value equality for free. This test codifies the - // expectation: the implementer must NOT override it with reference equality. - var a = new MarkerSettings("p/q", "p q"); - var b = new MarkerSettings("p/q", "p q"); - var c = new MarkerSettings("p/q", "different"); - - Assert.That(a, Is.EqualTo(b)); - Assert.That(a, Is.Not.EqualTo(c)); - Assert.That(a.GetHashCode(), Is.EqualTo(b.GetHashCode())); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-001")] - [Property("Contract", "ComparativeTextRef")] - public void ComparativeTextRef_WithExpressionProducesNewInstance() - { - // `with` is the canonical way to "update" a positional record. - // This test confirms the record supports non-destructive mutation. - var original = new ComparativeTextRef("guid-1"); - var updated = original with { Id = "guid-2" }; - - Assert.That(original.Id, Is.EqualTo("guid-1")); - Assert.That(updated.Id, Is.EqualTo("guid-2")); - Assert.That(original, Is.Not.EqualTo(updated)); - } - - // --------------------------------------------------------------------- - // Invariant tests - // --------------------------------------------------------------------- - - [Test] - [Category("Invariant")] - [Property("CapabilityId", "CAP-001")] - [Property("InvariantId", "INV-001")] - [TestCase(1)] - [TestCase(2)] - [TestCase(5)] - public void Inv001_ResultShape_RowCellCountMatchesColumnCount(int columnCount) - { - // INV-001: "Every row in the checklist has exactly N cells where N is the - // number of columns." The records themselves must be able to REPRESENT this - // invariant cleanly (i.e., neither construct nor serialize destroys it). - var headers = new List(); - var projectIds = new List(); - for (int i = 0; i < columnCount; i++) - { - headers.Add($"Proj{i}"); - projectIds.Add($"project-{i}"); - } - var cells = new List(); - for (int i = 0; i < columnCount; i++) - { - cells.Add( - new ChecklistCell( - new List(), - SingleRange("GEN 1:1"), - "en", - null - ) - ); - } - var row = new ChecklistRow(cells, true, false, 0, SingleRange("GEN 1:1")); - var result = new ChecklistResult( - Rows: new List { row }, - ColumnHeaders: headers, - ColumnProjectIds: projectIds, - ExcludedCount: 0, - HelpText: null, - Truncated: false, - EmptyResultMessage: null - ); - - var json = JsonSerializer.Serialize(result, _options); - var actual = JsonSerializer.Deserialize(json, _options); - - // Shape-level INV-001: after round-trip, the cell count still matches the - // column count. (Enforcement of the invariant is a downstream responsibility; - // the record only needs to preserve the shape.) - Assert.That(actual!.Rows[0].Cells.Count, Is.EqualTo(actual.ColumnHeaders.Count)); - Assert.That(actual.Rows[0].Cells.Count, Is.EqualTo(columnCount)); - } - - [Test] - [Category("Invariant")] - [Property("CapabilityId", "CAP-001")] - [Property("InvariantId", "INV-004")] - [TestCase("p")] - [TestCase("q1")] - [TestCase("mt")] - [TestCase("li2")] - public void Inv004_ParagraphMarker_StoredWithoutBackslashPrefix(string marker) - { - // INV-004: "Every paragraph cell in the markers checklist always starts with - // the backslash-prefixed marker name (e.g., \p, \q1)" — but per §3.4 - // validation rules, the Marker field STORES the marker without the backslash; - // the DISPLAY layer prepends it. This invariant test pins the storage form. - var para = new ChecklistParagraph(Marker: marker, Items: new List()); - - Assert.That(para.Marker, Is.EqualTo(marker)); - Assert.That(para.Marker, Does.Not.StartWith("\\")); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistNetworkObjectTests.cs b/c-sharp-tests/Checklists/ChecklistNetworkObjectTests.cs deleted file mode 100644 index 4cdc6eddb4b..00000000000 --- a/c-sharp-tests/Checklists/ChecklistNetworkObjectTests.cs +++ /dev/null @@ -1,712 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.NetworkObjects; -using Paranext.DataProvider.Services; -using Paratext.Data; -using PtxUtils; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase unit tests for CAP-011 -/// (ChecklistNetworkObject — NetworkObject PAPI registration for the -/// checklist service). -/// -/// -/// These tests will NOT compile until the implementer adds -/// Paranext.DataProvider.Checklists.ChecklistNetworkObject at -/// c-sharp/Checklists/ChecklistNetworkObject.cs, subclassing -/// (NOT DataProvider), with -/// an InitializeAsync() method that calls -/// RegisterNetworkObjectAsync with the name -/// "platformScripture.checklistService", the three functions -/// (buildChecklistData, resolveComparativeTexts, -/// validateMarkerSettings) in alphabetical order, and -/// . The compile error is the first -/// layer of the RED signal; the test-assertion failures (after a stub lands) -/// are the second. -/// -/// -/// -/// Per strategic-plan-backend.md §CAP-011, this capability uses -/// Classic TDD: write focused unit tests asserting the registration -/// contract (shape + routing), then implement. The wire contract is -/// fully specified in backend-alignment.md §"Network Object" and -/// data-contracts.md §7 — there is no interface discovery to perform. -/// -/// -/// -/// Registration verification strategy. The test inherits -/// which wires up . -/// DummyPapiClient.SendEventAsync captures events into a queue; the -/// onDidCreateNetworkObject event that -/// emits carries a payload that -/// exposes the registered Id, ObjectType, and FunctionNames. -/// DummyPapiClient.SendRequestAsync routes through the same -/// _localMethods dictionary that -/// populates, so probing a registered wire method invokes the underlying -/// delegate — this is the path used to verify routing. -/// -/// -/// -/// Reference pattern: c-sharp/Projects/ProjectDataProviderFactory.cs:25-46. -/// -/// -/// Traceability: -/// - Capability: CAP-011 (NetworkObject PAPI Registration) -/// - Strategy: Classic TDD (per strategic-plan-backend.md §CAP-011) -/// - Contract: data-contracts.md §7.1, §7.2; -/// backend-alignment.md §Network Object -/// - Related behaviors (exposed through the wire — not re-verified here; -/// CAP-006 owns pipeline behavior): BHV-600, BHV-601, BHV-602, BHV-603, -/// BHV-604, BHV-606 -/// - Related scenarios: TS-001..TS-006, TS-032, TS-033, TS-055 (covered -/// end-to-end in CAP-006; CAP-011 tests verify only the NetworkObject -/// registration contract that exposes them) -/// -[TestFixture] -internal class ChecklistNetworkObjectTests : PapiTestBase -{ - // Canonical wire values from backend-alignment.md §"Network Object" - // and data-contracts.md §7.1/§7.2. - private const string NetworkObjectName = "platformScripture.checklistService"; - private const string ObjectPrefix = "object:" + NetworkObjectName; - private const string CreateEventType = "object:onDidCreateNetworkObject"; - - // Alphabetical — the strategic plan specifies this exact order. - private static readonly string[] ExpectedFunctionNames = - [ - "buildChecklistData", - "resolveComparativeTexts", - "validateMarkerSettings", - ]; - - // ===================================================================== - // Group A — Registration Shape (The Acceptance Contract) - // - // "Registers with the expected name, type, and function names" is the - // done-signal for CAP-011 per strategic-plan-backend.md. - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Description( - "Acceptance test — after InitializeAsync, ChecklistNetworkObject emits " - + "an onDidCreateNetworkObject event with Id=platformScripture.checklistService, " - + "ObjectType=NetworkObjectType.OBJECT, and FunctionNames=[buildChecklistData, " - + "resolveComparativeTexts, validateMarkerSettings] in alphabetical order." - )] - public async Task InitializeAsync_RegistersWithExpectedNameAndType() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - - // Act - await networkObject.InitializeAsync(); - - // Assert — exactly one event sent, and it is the create-network-object - // event with the expected payload shape. - Assert.That( - Client.SentEventCount, - Is.EqualTo(1), - "InitializeAsync must emit exactly one onDidCreateNetworkObject event" - ); - - (string eventType, object? eventParameters) = Client.NextSentEvent; - - Assert.That( - eventType, - Is.EqualTo(CreateEventType), - "registration must use object:onDidCreateNetworkObject" - ); - - Assert.That( - eventParameters, - Is.InstanceOf(), - "payload must be a NetworkObjectCreatedDetails record" - ); - - var details = (NetworkObjectCreatedDetails)eventParameters!; - - Assert.That( - details.Id, - Is.EqualTo(NetworkObjectName), - "Id must be platformScripture.checklistService (no '-data' suffix)" - ); - Assert.That( - details.ObjectType, - Is.EqualTo(NetworkObjectType.OBJECT), - "ObjectType must be NetworkObjectType.OBJECT (plain network object)" - ); - Assert.That( - details.FunctionNames, - Is.EqualTo(ExpectedFunctionNames), - "FunctionNames must contain exactly the three methods in alphabetical order" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Description( - "After InitializeAsync, a handler is registered for each of the three wire " - + "method names (object:platformScripture.checklistService.). A " - + "never-registered name on the same prefix routes to the default " - + "(unregistered) path." - )] - public async Task InitializeAsync_RegistersExactlyThreeFunctionHandlers() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act + Assert — each expected wire name must be registered. - // DummyPapiClient.SendRequestAsync returns Task.FromResult(default) - // for a name NOT in _localMethods; it invokes the delegate for a name - // that IS registered. To distinguish "registered with any signature" from - // "not registered", we probe each handler and require that invocation - // either succeeds or throws (meaning the delegate WAS found). A - // never-registered name is verified by contrast to silently return null. - foreach (string functionName in ExpectedFunctionNames) - { - string wireName = $"{ObjectPrefix}.{functionName}"; - Assert.That( - IsHandlerRegistered(wireName), - Is.True, - $"wire method '{wireName}' must be registered after InitializeAsync" - ); - } - - // Negative control — a never-registered name on the same prefix. - string neverRegistered = $"{ObjectPrefix}.notAMethod"; - Assert.That( - IsHandlerRegistered(neverRegistered), - Is.False, - $"sanity check: '{neverRegistered}' must not be registered" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Description( - "The base NetworkObject.RegisterNetworkObjectAsync also registers a " - + "sentinel handler at the object prefix itself (object:platformScripture.checklistService) " - + "so PAPI can probe existence. Verifies the base-class registration " - + "path ran." - )] - public async Task InitializeAsync_RegistersTopLevelObjectHandler() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act + Assert — the base-class NetworkObject registers a sentinel - // Func(() => true) handler at the object prefix. See - // c-sharp/NetworkObjects/NetworkObject.cs:34-35. - Assert.That( - IsHandlerRegistered(ObjectPrefix), - Is.True, - $"sentinel handler at '{ObjectPrefix}' must be registered by base class" - ); - } - - // ===================================================================== - // Group B — Delegate Routing - // - // Each registered delegate must route to the corresponding - // ChecklistService method. We pick paths that are: - // (a) exhaustively covered in the dependency capability's tests, so - // a regression here CANNOT be hidden by a dependency regression; - // (b) minimal-setup, so we don't re-test pipeline composition. - // - // validateMarkerSettings is the cleanest probe (stateless, no project - // resolution needed, distinctive error message). resolveComparativeTexts - // uses the empty-list path. buildChecklistData uses the - // project-not-found path (throws ProjectNotFoundException, so the throw - // propagating confirms delegate wiring — a never-registered handler - // would return default silently). - // ===================================================================== - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "validateMarkerSettings")] - [Description( - "The registered 'validateMarkerSettings' delegate routes to " - + "MarkersDataSource.ValidateMarkerSettings — a valid input returns " - + "the parsed marker pairs in source order." - )] - public async Task ValidateMarkerSettings_RoutesToChecklistServiceValidateMarkerSettings() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act — invoke the registered handler through the PapiClient routing - // path. For a valid "p/q q1/q2" input, MarkersDataSource.ValidateMarkerSettings - // returns Valid=true with 2 pairs in source order. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.validateMarkerSettings", - "p/q q1/q2" - ); - - // Assert — result must match MarkersDataSource.ValidateMarkerSettings - // behavior (CAP-007). If the delegate points elsewhere, this would fail. - Assert.That(result, Is.Not.Null, "handler must return a MarkerSettingsValidationResult"); - Assert.That(result!.Valid, Is.True, "p/q q1/q2 is a valid marker-settings string"); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs!.Count, Is.EqualTo(2)); - Assert.That(result.ParsedPairs[0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[1].Marker1, Is.EqualTo("q1")); - Assert.That(result.ParsedPairs[1].Marker2, Is.EqualTo("q2")); - Assert.That(result.ErrorMessage, Is.Null); - } - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "validateMarkerSettings")] - [Description( - "The registered 'validateMarkerSettings' delegate routes the error path " - + "to MarkersDataSource.ValidateMarkerSettings — an invalid input " - + "returns Valid=false with the canonical PT9 error message, " - + "confirming delegate identity (distinctive error literal)." - )] - public async Task ValidateMarkerSettings_ErrorCase_RoutesAndReturnsError() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act — "p/q badpair" has a malformed token; ValidateMarkerSettings - // fails fast with the canonical error. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.validateMarkerSettings", - "p/q badpair" - ); - - // Assert — the distinctive PT9 error literal pins delegate identity. - Assert.That(result, Is.Not.Null); - Assert.That(result!.Valid, Is.False, "'badpair' has no slash → invalid"); - Assert.That(result.ParsedPairs, Is.Null, "§3.13 — ParsedPairs null on failure"); - Assert.That(result.ErrorMessage, Is.Not.Null); - // The NetworkObject resolves the localize key via LocalizationService. - // DummyPapiClient returns null when the localization service handler is - // not registered; GetLocalizedString then falls back to - // MarkersDataSource.InvalidMarkerPairErrorFallback, which matches the - // PT9 literal. A dedicated LocalizationService-mock test - // (ValidateMarkerSettings_ErrorCase_ResolvesLocalizeKeyThroughLocalizationService) - // covers the key-invocation path. - Assert.That( - result.ErrorMessage, - Does.Contain("p/q"), - "error message is the PT9 canonical 'Equivalent markers need to be entered in the form: p/q'" - ); - } - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "validateMarkerSettings")] - [Property("Localization", "InvalidMarkerPairError")] - [Description( - "T-B-6 / Rolf commitment #3124165012 — the 'validateMarkerSettings' " - + "delegate resolves the %markersChecklist_errorInvalidMarkerPair% " - + "localize key through LocalizationService.GetLocalizedString (which " - + "routes to the platform.localizationDataServiceDataProvider PAPI " - + "request) before returning. Asserts (a) the expected key is sent " - + "to the localization service, and (b) the resolved string " - + "replaces the raw %...% key in the response. Pins the " - + "key→string resolution at the wire boundary so a regression that " - + "drops the LocalizationService call is caught." - )] - public async Task ValidateMarkerSettings_ErrorCase_ResolvesLocalizeKeyThroughLocalizationService() - { - // Arrange — register a stand-in LocalizationService handler that - // captures the requested key and returns a distinctive resolved string. - const string ResolvedLocalizedMessage = - "LOCALIZED: markers must be entered as marker1/marker2"; - var observedKeys = new List(); - await Client.RegisterRequestHandlerAsync( - "object:platform.localizationDataServiceDataProvider-data.getLocalizedString", - new Func(selector => - { - observedKeys.Add(selector.LocalizeKey); - return ResolvedLocalizedMessage; - }), - null - ); - - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act — malformed input ⇒ MarkersDataSource.ValidateMarkerSettings - // returns InvalidMarkerPairErrorKey in ErrorMessage. The NetworkObject - // delegate must then resolve that key via LocalizationService. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.validateMarkerSettings", - "badpair" - ); - - // Assert (a) — the localization service was invoked with the - // canonical InvalidMarkerPairErrorKey. - Assert.That( - observedKeys, - Is.EqualTo(new[] { MarkersDataSource.InvalidMarkerPairErrorKey }), - "LocalizationService.GetLocalizedString must be invoked exactly once " - + "with the InvalidMarkerPairErrorKey" - ); - - // Assert (b) — the resolved string flowed into the response in - // place of the raw %...% key. - Assert.That(result, Is.Not.Null); - Assert.That(result!.Valid, Is.False); - Assert.That( - result.ErrorMessage, - Is.EqualTo(ResolvedLocalizedMessage), - "NetworkObject must replace the %key% form with the resolved localized string" - ); - Assert.That( - result.ErrorMessage, - Does.Not.StartWith("%"), - "resolved string must not retain the %...% localize-key wrapping" - ); - } - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "resolveComparativeTexts")] - [Description( - "The registered 'resolveComparativeTexts' delegate routes to " - + "ChecklistService.ResolveComparativeTexts — with an empty " - + "requestedTexts list, the method returns an empty Texts list " - + "(CAP-009 edge case). Confirms delegate identity." - )] - public async Task ResolveComparativeTexts_RoutesToChecklistServiceResolveComparativeTexts() - { - // Arrange — register an active project so the method can resolve it. - DummyScrText active = RegisterDummyProject("ACTIVE_P"); - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act — empty requestedTexts is the simplest routing probe; CAP-009's - // implementation returns an empty Texts list for this input. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.resolveComparativeTexts", - active.Guid.ToString(), - new List(), - CancellationToken.None - ); - - // Assert — routing produced the expected CAP-009 empty-list shape. - Assert.That(result, Is.Not.Null, "handler must return a ResolvedComparativeTexts"); - Assert.That(result!.Texts, Is.Not.Null); - Assert.That( - result.Texts.Count, - Is.EqualTo(0), - "CAP-009: empty requestedTexts → empty Texts list" - ); - } - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "buildChecklistData")] - [Description( - "The registered 'buildChecklistData' delegate routes to " - + "ChecklistService.BuildChecklistData and wraps ProjectNotFoundException " - + "into a structured ChecklistResultError { Code=PROJECT_NOT_FOUND, " - + "Message= } per data-contracts.md §3.1 / §3.6. A " - + "never-registered handler would return null silently; the returned " - + "ChecklistResultError instance therefore confirms both delegate " - + "wiring and the T-B-7 structured-error path." - )] - public async Task BuildChecklistData_UnknownProject_ReturnsChecklistResultError() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - var request = new ChecklistRequest( - ProjectId: "NONEXISTENT_PROJECT_ID", - ComparativeTextIds: new List(), - MarkerSettings: new MarkerSettings(EquivalentMarkers: "", MarkerFilter: ""), - VerseRange: null, - HideMatches: false, - ShowVerseText: false - ); - - // Act — invoke via the polymorphic object return type (ChecklistResultResponse - // discriminated union). The success branch returns ChecklistResult; the - // error branch returns ChecklistResultError. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.buildChecklistData", - request, - CancellationToken.None - ); - - // Assert — the caught ProjectNotFoundException was mapped to a - // structured ChecklistResultError carrying the PROJECT_NOT_FOUND code - // and a non-empty message. JsonRpc/StreamJsonRpc may serialize the - // polymorphic return through System.Text.Json and hand us back a - // JsonElement — handle both in-proc (ChecklistResultError instance) - // and round-tripped (JsonElement) paths. - Assert.That(result, Is.Not.Null, "handler must return a non-null ChecklistResultError"); - - if (result is ChecklistResultError err) - { - Assert.That( - err.Code, - Is.EqualTo(ChecklistErrorCodes.ProjectNotFound), - "error code must be PROJECT_NOT_FOUND" - ); - Assert.That(err.Message, Is.Not.Null.And.Not.Empty, "message must be populated"); - } - else if (result is JsonElement json) - { - Assert.That( - json.GetProperty("code").GetString(), - Is.EqualTo(ChecklistErrorCodes.ProjectNotFound) - ); - Assert.That(json.GetProperty("message").GetString(), Is.Not.Null.And.Not.Empty); - } - else - { - Assert.Fail( - $"expected ChecklistResultError (or JsonElement round-trip); got {result.GetType()}" - ); - } - } - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Property("Routing", "buildChecklistData")] - [Description( - "T-B-6 / Rolf commitment #3124021837 — happy-path routing test. " - + "Registers a real DummyScrText project, invokes buildChecklistData " - + "through the NetworkObject, and asserts a ChecklistResult flows " - + "back with non-empty rows. The positive sibling of " - + "BuildChecklistData_UnknownProject_ReturnsChecklistResultError: a " - + "regression that broke serialization / arg binding / CT wiring on " - + "the success branch would fail here even if the error branch still " - + "worked." - )] - public async Task BuildChecklistData_RegisteredProject_ReturnsChecklistResult() - { - // Arrange — register a real project with content so the pipeline - // produces at least one row. Poetry markers need the paragraph-style - // upgrade (same approach as ChecklistServiceBuildChecklistDataTests). - const string Gm001ExoUsfm = - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented poetry"; - var scrText = RegisterDummyProjectWithPoetry(Gm001ExoUsfm); - - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - var request = new ChecklistRequest( - ProjectId: scrText.Guid.ToString(), - ComparativeTextIds: new List(), - MarkerSettings: new MarkerSettings(EquivalentMarkers: "", MarkerFilter: ""), - VerseRange: null, - HideMatches: false, - ShowVerseText: false - ); - - // Act — invoke through the registered PAPI handler (happy path). The - // delegate returns the ChecklistResult success branch. - var result = await InvokeRegisteredHandlerAsync( - $"{ObjectPrefix}.buildChecklistData", - request, - CancellationToken.None - ); - - // Assert — a ChecklistResult (not a ChecklistResultError) flowed - // through the NetworkObject wire boundary, with real content. - Assert.That(result, Is.Not.Null, "handler must return a ChecklistResult on happy path"); - Assert.That( - result, - Is.Not.InstanceOf(), - "happy path must NOT return the error branch" - ); - Assert.That( - result, - Is.InstanceOf(), - "happy path returns ChecklistResult (data-contracts.md §3.1 success variant)" - ); - - var checklistResult = (ChecklistResult)result!; - Assert.That( - checklistResult.Rows, - Is.Not.Null.And.Not.Empty, - "happy path with registered project produces at least one row" - ); - Assert.That( - checklistResult.ColumnHeaders, - Is.Not.Null.And.Not.Empty, - "happy path result carries column headers" - ); - Assert.That( - checklistResult.ColumnProjectIds[0], - Is.EqualTo(scrText.Guid.ToString()), - "INV-C15 — registered project id must appear at column index 0" - ); - } - - // ===================================================================== - // Group C — Double-Registration Guard - // - // NetworkObject.RegisterNetworkObjectAsync throws if called twice with - // the same instance. Pins the single-registration invariant. - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-011")] - [Property("Contract", "NetworkObjectRegistration")] - [Description( - "Calling InitializeAsync twice on the same ChecklistNetworkObject " - + "instance must throw — matches the base NetworkObject.RegisterNetworkObjectAsync " - + "single-registration guard (NetworkObject.cs:29-30)." - )] - public async Task InitializeAsync_CalledTwice_Throws() - { - // Arrange - var networkObject = new ChecklistNetworkObject(Client); - await networkObject.InitializeAsync(); - - // Act + Assert — pin to the exact exception type thrown by the - // base NetworkObject.RegisterNetworkObjectAsync guard - // (NetworkObject.cs:29-30), rather than a loose "any throw that is - // not NotImplementedException" probe. A stricter assertion makes a - // future base-class change (e.g. a custom DoubleRegistrationException) - // surface here loudly instead of silently passing. - Assert.That( - async () => await networkObject.InitializeAsync(), - Throws - .InstanceOf() - .With.Message.Contains(NetworkObjectName) - .And.Message.Contains("already been registered"), - "second InitializeAsync must throw the base NetworkObject " - + "single-registration guard error carrying the network object " - + "name and the 'already been registered' literal" - ); - } - - // ===================================================================== - // Helpers - // ===================================================================== - - /// - /// Reports whether a handler is registered for the given wire name by - /// directly inspecting 's _localMethods - /// dictionary through the test-only - /// accessor. This replaces - /// the earlier exception-catching probe (which conflated "handler present" - /// with "handler threw on bad args") per T-B-3 feedback — a direct - /// dictionary lookup is unambiguous and has no false-positive failure modes. - /// - private bool IsHandlerRegistered(string wireName) => Client.IsHandlerRegistered(wireName); - - /// - /// Invokes a registered handler by wire name through . - /// Parameters are passed positionally and marshalled through DynamicInvoke. - /// - private async Task InvokeRegisteredHandlerAsync(string wireName, params object?[] args) - { - return await Client.SendRequestAsync(wireName, args); - } - - /// - /// Registers a into the shared - /// via - /// DummyLocalParatextProjects.FakeAddProject. Mirrors the helper - /// used in ChecklistServiceResolveComparativeTextsTests. - /// - private DummyScrText RegisterDummyProject(string shortName) - { - var details = CreateProjectDetails(HexId.CreateNew().ToString(), shortName); - var scrText = new DummyScrText(details); - ParatextProjects.FakeAddProject(details, scrText); - return scrText; - } - - /// - /// Registers a with real USFM content for - /// happy-path routing tests. Upgrades the stylesheet's poetry tags - /// (\q, \q1, \q2, \b) to paragraph style via - /// reflection so the Markers pipeline treats them as paragraph markers — - /// mirrors the helper in ChecklistServiceBuildChecklistDataTests. - /// - private DummyScrText RegisterDummyProjectWithPoetry(string usfm, int bookNum = 2) - { - var scrText = new DummyScrText(); - UpgradePoetryMarkersToParagraphStyle(scrText); - scrText.PutText(bookNum, 0, false, usfm, null); - ParatextProjects.FakeAddProject(CreateProjectDetails(scrText), scrText); - return scrText; - } - - /// - /// Replaces the character-style q / q1 / q2 / b tags on the - /// DummyScrStylesheet with paragraph-style tags so the Markers pipeline - /// recognises them as paragraph markers. Copied verbatim from the sister - /// helper in ChecklistServiceBuildChecklistDataTests. - /// - private static void UpgradePoetryMarkersToParagraphStyle(DummyScrText scrText) - { - var stylesheet = scrText.DefaultStylesheet; - foreach (var marker in new[] { "q", "q1", "q2", "b" }) - { - AddPoetryTag(stylesheet, marker); - } - } - - private static void AddPoetryTag(ScrStylesheet stylesheet, string marker) - { - var tag = new ScrTag - { - Marker = marker, - TextProperties = - TextProperties.scParagraph - | TextProperties.scPublishable - | TextProperties.scVernacular - | TextProperties.scPoetic, - TextType = ScrTextType.scVerseText, - StyleType = ScrStyleType.scParagraphStyle, - OccursUnder = "c", - }; - - var addTagInternal = typeof(ScrStylesheet).GetMethod( - "AddTagInternal", - System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic - ); - if (addTagInternal == null) - { - throw new InvalidOperationException( - "ScrStylesheet.AddTagInternal not found via reflection; " - + "API has changed and this test helper must be updated." - ); - } - addTagInternal.Invoke(stylesheet, new object[] { tag }); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistRowBuilderTests.cs b/c-sharp-tests/Checklists/ChecklistRowBuilderTests.cs deleted file mode 100644 index fac9eb409cf..00000000000 --- a/c-sharp-tests/Checklists/ChecklistRowBuilderTests.cs +++ /dev/null @@ -1,907 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Scripture; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase contract and scenario tests for CAP-005 (Row Alignment Builder — -/// ChecklistRowBuilder.BuildRowsMergingCells). -/// -/// -/// These tests will NOT compile until the implementer creates the static class -/// Paranext.DataProvider.Checklists.ChecklistRowBuilder with a public -/// BuildRowsMergingCells(List<List<ChecklistCell>>) method. -/// That is intentional: the test file IS the specification — the compile error -/// is the first layer of the RED signal; the test assertion failures are the -/// second (matches the CAP-001 / CAP-003 / CAP-004 precedent). Per -/// strategic-plan-backend.md §CAP-005, this capability uses Classic TDD: -/// tests build up incrementally from empty inputs through exact-match -/// alignment, missing-verse placeholders, verse-bridge merging, MAX_CELLS_TO_GRAB -/// boundary, and duplicate-verse rows. The per-group assertions drive discovery -/// of the internal helpers (BuildReferenceMappings, -/// ExpandGrabCountToAlignCells, AddRowOfGrabbedCells) through the -/// public surface. -/// -/// -/// -/// Signature note (versification source). PT9's CLRowsBuilder reads -/// versification off each cell's live VerseRef to call -/// ChangeVersification for INV-007. The PT10 ChecklistCell (see -/// data-contracts §3.3) carries only a serialized Reference string — -/// versification information lives one layer up in the ScrText of the -/// column. Strategic-plan-backend.md §CAP-005 fixes the public signature as -/// BuildRowsMergingCells(List<List<ChecklistCell>>) -> -/// List<ChecklistRow>, so these tests use pre-normalized -/// reference strings in every column. If the implementer decides during GREEN -/// that a versification companion parameter is required (e.g. to match PT9's -/// runtime AllVerses().ChangeVersification(...) behavior), the tests -/// will be touched up then — the RED signal here is the missing class, not a -/// parameter mismatch. -/// -/// -/// Traceability: -/// - Capability: CAP-005 -/// - Behaviors: BHV-109 (single behavior for this capability) -/// - Extractions: EXT-009 (CLRowsBuilder → ChecklistRowBuilder) -/// - Invariants: INV-001 (N cells per row), INV-006 (MAX_CELLS_TO_GRAB=3), -/// INV-007 (common versification — orchestrator-pre-normalized here), -/// INV-011 (Markers checklist uses merging mode — implicit: we only call -/// BuildRowsMergingCells) -/// - Scenarios: TS-025, TS-026, TS-027, TS-028, TS-064, TS-068, TS-069 -/// - Golden Masters: gm-011, gm-012, gm-013 (shape-level replay; end-to-end -/// coverage lives in CAP-006 integration tests per strategic-plan-backend.md) -/// - Contract: data-contracts.md §4.1 (BHV-109 inside BuildChecklistData), -/// §3.2 (ChecklistRow shape), §3.3 (ChecklistCell shape) -/// - PT9 source: Paratext/Checklists/CLRowsBuilder.cs:1-371 -/// -[TestFixture] -internal class ChecklistRowBuilderTests -{ - // --------------------------------------------------------------------- - // Shared helpers — keep test-body shape close to the captured gm data - // --------------------------------------------------------------------- - - /// - /// Build a single-paragraph with one - /// + pair. Reference is a - /// single-verse (no verse-bridge merging on this cell). - /// - private static ChecklistCell Cell(string reference, string text) - { - var items = new List - { - new VerseItem(ExtractVerseNumber(reference)), - new TextItem(text, null), - }; - var paragraph = new ChecklistParagraph("p", items); - return new ChecklistCell( - Paragraphs: new List { paragraph }, - Reference: SingleRange(reference), - Language: "dmy", - Error: null - ); - } - - /// - /// Build a bridge — the cell represents a verse - /// bridge like EXO 20:2-3. Its Reference is a - /// spanning through the - /// last verse of ({Start, End}). - /// - private static ChecklistCell BridgeCell( - string firstVerseRef, - string bridgeDisplayRef, - string bridgeVerseNumber, - string text - ) - { - var items = new List - { - new VerseItem(bridgeVerseNumber), - new TextItem(text, null), - }; - var paragraph = new ChecklistParagraph("p", items); - return new ChecklistCell( - Paragraphs: new List { paragraph }, - Reference: BridgeRange(firstVerseRef, bridgeDisplayRef), - Language: "dmy", - Error: null - ); - } - - /// Build a single-verse from a string like "GEN 1:1". - private static ScriptureRange SingleRange(string reference) => - new ScriptureRange(new VerseRef(reference), null); - - /// - /// Build a bridge spanning - /// (e.g. "EXO 20:2") through the last verse of (e.g. - /// "EXO 20:2-5") — i.e. {Start = EXO 20:2, End = EXO 20:5}. - /// - private static ScriptureRange BridgeRange(string firstVerseRef, string bridgeRef) - { - var start = new VerseRef(firstVerseRef); - int dash = bridgeRef.LastIndexOf('-'); - string endVerse = dash >= 0 ? bridgeRef[(dash + 1)..] : start.VerseNum.ToString(); - var end = new VerseRef( - start.Book, - start.ChapterNum.ToString(), - endVerse, - start.Versification - ); - return new ScriptureRange(start, end); - } - - /// - /// Render a back to a canonical assertion string — - /// "GEN 1:1" for a single verse, "GEN 1:1-3" for a bridge, "" for null. - /// - private static string RefString(ScriptureRange? range) - { - if (range is null) - return string.Empty; - return range.End is { } end ? $"{range.Start}-{end.VerseNum}" : range.Start.ToString(); - } - - /// Extracts the verse-number portion of a reference like "EXO 20:3". - private static string ExtractVerseNumber(string reference) - { - int colonIdx = reference.IndexOf(':'); - return colonIdx < 0 ? reference : reference.Substring(colonIdx + 1); - } - - /// - /// Counts instances inside a cell. - /// Merged bridge cells carry multiple paragraphs (one per grabbed cell). - /// - private static int ParagraphCount(ChecklistCell cell) => cell.Paragraphs.Count; - - /// - /// True when the cell is an "empty placeholder" emitted for a column that - /// has no matching verse at this row (INV-001). - /// - private static bool IsEmptyPlaceholder(ChecklistCell cell) => cell.Paragraphs.Count == 0; - - // ===================================================================== - // GROUP A — degenerate / empty inputs (Classic TDD steps 1-3) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Description("Group A.1: empty columns list produces empty rows list.")] - public void BuildRowsMergingCells_EmptyColumnList_ReturnsEmpty() - { - var columns = new List>(); - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows, Is.Not.Null); - Assert.That(rows, Is.Empty); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("Invariant", "INV-001")] - [Description("Group A.2: single column, single cell → one row with one cell.")] - public void BuildRowsMergingCells_SingleColumnSingleCell_ReturnsOneRowWithOneCell() - { - var columns = new List> - { - new() { Cell("GEN 1:1", "in the beginning ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(1)); - Assert.That(rows[0].Cells.Count, Is.EqualTo(1), "INV-001: cells.Count == columns.Count"); - Assert.That(RefString(rows[0].Cells[0].Reference), Is.EqualTo("GEN 1:1")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Description("Group A.3: single column, multiple cells → one row per cell, order preserved.")] - public void BuildRowsMergingCells_SingleColumnMultipleCells_PreservesOrder() - { - var columns = new List> - { - new() { Cell("GEN 1:1", "v1 "), Cell("GEN 1:2", "v2 "), Cell("GEN 1:3", "v3 ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(3)); - Assert.That(RefString(rows[0].Cells[0].Reference), Is.EqualTo("GEN 1:1")); - Assert.That(RefString(rows[1].Cells[0].Reference), Is.EqualTo("GEN 1:2")); - Assert.That(RefString(rows[2].Cells[0].Reference), Is.EqualTo("GEN 1:3")); - } - - // ===================================================================== - // GROUP B — exact-match alignment (TS-025, TS-064) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-025")] - [Property("Invariant", "INV-001")] - [Description( - "Group B.4 (TS-025): two columns with identical verse references align " - + "into one row per reference, 2 cells each." - )] - public void BuildRowsMergingCells_TwoColumnsSameRefs_AlignsOneRowPerRef() - { - var columns = new List> - { - new() { Cell("GEN 1:1", "en-1 "), Cell("GEN 1:2", "en-2 "), Cell("GEN 1:3", "en-3 ") }, - new() { Cell("GEN 1:1", "es-1 "), Cell("GEN 1:2", "es-2 "), Cell("GEN 1:3", "es-3 ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(3), "one row per shared reference"); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001: 2 columns → 2 cells"); - Assert.That(RefString(rows[0].Cells[0].Reference), Is.EqualTo("GEN 1:1")); - Assert.That(RefString(rows[0].Cells[1].Reference), Is.EqualTo("GEN 1:1")); - Assert.That(RefString(rows[2].Cells[0].Reference), Is.EqualTo("GEN 1:3")); - Assert.That(RefString(rows[2].Cells[1].Reference), Is.EqualTo("GEN 1:3")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-025")] - [Property("Invariant", "INV-001")] - [Description("Group B.5: three columns with identical refs → one row per ref, 3 cells each.")] - public void BuildRowsMergingCells_ThreeColumnsSameRefs_AlignsAcrossAll() - { - var columns = new List> - { - new() { Cell("GEN 1:1", "a1 "), Cell("GEN 1:2", "a2 ") }, - new() { Cell("GEN 1:1", "b1 "), Cell("GEN 1:2", "b2 ") }, - new() { Cell("GEN 1:1", "c1 "), Cell("GEN 1:2", "c2 ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(2)); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(3), "INV-001"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("Invariant", "INV-001")] - [Description( - "Group B.6: INV-001 explicit assertion — every row has cells.Count == columns.Count." - )] - public void BuildRowsMergingCells_EveryRow_HasNCellsWhereNIsColumnCount() - { - // Mixed alignment: not every ref is shared. INV-001 must hold regardless. - var columns = new List> - { - new() { Cell("EXO 20:1", "one "), Cell("EXO 20:3", "three ") }, - new() { Cell("EXO 20:1", "uno "), Cell("EXO 20:2", "dos ") }, - new() { Cell("EXO 20:2", "deux "), Cell("EXO 20:3", "trois ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows, Is.Not.Empty); - foreach (var row in rows) - Assert.That( - row.Cells.Count, - Is.EqualTo(3), - $"INV-001: row at FirstRef={row.FirstRef} must have 3 cells (3 columns)" - ); - } - - // ===================================================================== - // GROUP C — missing verses, empty placeholders (TS-026, INV-001, gm-012) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-026")] - [Property("Invariant", "INV-001")] - [Description( - "Group C.7 (TS-026): missing middle verse in col 1 → row for v2 has " - + "empty cell placeholder at col 1." - )] - public void BuildRowsMergingCells_MissingMiddleVerse_ProducesEmptyPlaceholderForColumn() - { - var columns = new List> - { - new() { Cell("GEN 1:1", "v1 "), Cell("GEN 1:2", "v2 "), Cell("GEN 1:3", "v3 ") }, - new() { Cell("GEN 1:1", "uno "), Cell("GEN 1:3", "tres ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(3)); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001"); - - // Row for GEN 1:2 has empty placeholder in col 1. - var rowV2 = rows.Single(r => RefString(r.Cells[0].Reference) == "GEN 1:2"); - Assert.That(IsEmptyPlaceholder(rowV2.Cells[1]), Is.True, "col 1 missing v2 → empty cell"); - Assert.That(IsEmptyPlaceholder(rowV2.Cells[0]), Is.False, "col 0 populated for v2"); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-026")] - [Property("GoldenMaster", "gm-012")] - [Property("Invariant", "INV-001")] - [Description( - "Group C.8 (gm-012 replay): 5 rows × 2 cells. Rows 0/1/3/4 have empty " - + "col 0 (text1 only has v5-6). See golden-masters/gm-012/expected-output.json." - )] - public void BuildRowsMergingCells_MissingAtStartAndEnd_Gm012Shape() - { - // gm-012 shape: - // text1 (col 0): only v5, v6 - // text2 (col 1): v1-2 bridge, v3-4 bridge, v5-6 bridge, v7, v8 - var col0 = new List { Cell("EXO 20:5", "five "), Cell("EXO 20:6", "six ") }; - var col1 = new List - { - BridgeCell("EXO 20:1", "EXO 20:1-2", "1-2", "uno a dos "), - BridgeCell("EXO 20:3", "EXO 20:3-4", "3-4", "tres a cuatro "), - BridgeCell("EXO 20:5", "EXO 20:5-6", "5-6", "cinco a seis "), - Cell("EXO 20:7", "siete "), - Cell("EXO 20:8", "ocho "), - }; - var columns = new List> { col0, col1 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(5), "gm-012 expected 5 rows"); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001"); - - // Rows 0, 1, 3, 4 have empty col 0 (text1 has no matching verses there). - Assert.That(IsEmptyPlaceholder(rows[0].Cells[0]), Is.True, "row 0 col 0 empty"); - Assert.That(IsEmptyPlaceholder(rows[1].Cells[0]), Is.True, "row 1 col 0 empty"); - Assert.That(IsEmptyPlaceholder(rows[3].Cells[0]), Is.True, "row 3 col 0 empty"); - Assert.That(IsEmptyPlaceholder(rows[4].Cells[0]), Is.True, "row 4 col 0 empty"); - - // Row 2 (middle) has both columns populated — merge happened. - Assert.That(IsEmptyPlaceholder(rows[2].Cells[0]), Is.False, "row 2 col 0 populated"); - Assert.That(IsEmptyPlaceholder(rows[2].Cells[1]), Is.False, "row 2 col 1 populated"); - } - - // ===================================================================== - // GROUP D — verse bridges with merging (TS-027, gm-011, gm-013, INV-006) - // ===================================================================== - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-027")] - [Property("GoldenMaster", "gm-013")] - [Property("Invariant", "INV-006")] - [Description( - "Group D.9 (gm-013 replay): text1 [v1, v2-5, v6, v7-8] × text2 [v1, v4-7, v8-9] " - + "→ 2 rows. Row 1 merges 3 cells in col 0 (MAX_CELLS_TO_GRAB). See " - + "golden-masters/gm-013/expected-output.json." - )] - public void BuildRowsMergingCells_BridgeInColOneIndividualInColTwo_MergesUpToMaxCells() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - BridgeCell("EXO 20:2", "EXO 20:2-5", "2-5", "two to five "), - Cell("EXO 20:6", "six "), - BridgeCell("EXO 20:7", "EXO 20:7-8", "7-8", "seven to eight "), - }; - var col1 = new List - { - Cell("EXO 20:1", "uno "), - BridgeCell("EXO 20:4", "EXO 20:4-7", "4-7", "cuatro a siete "), - BridgeCell("EXO 20:8", "EXO 20:8-9", "8-9", "ocho a nueve "), - }; - var columns = new List> { col0, col1 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(2), "gm-013 expected 2 rows"); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001"); - - // Row 1 col 0 merges 3 cells (2-5, 6, 7-8) — exactly MAX_CELLS_TO_GRAB. - Assert.That( - ParagraphCount(rows[1].Cells[0]), - Is.EqualTo(3), - "INV-006: col 0 row 1 merges exactly 3 cells (MAX_CELLS_TO_GRAB)" - ); - Assert.That( - ParagraphCount(rows[1].Cells[1]), - Is.EqualTo(2), - "col 1 row 1 merges 2 cells (4-7, 8-9)" - ); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-025")] - [Property("GoldenMaster", "gm-011")] - [Description( - "Group D.10 (gm-011 replay): 4 rows with overlapping bridges merged. text1 " - + "[v1, v2, v3, v4-6, v7, v8] × text2 [v1, v2-3, v4, v5, v6-7, v8]. " - + "See golden-masters/gm-011/expected-output.json." - )] - public void BuildRowsMergingCells_OverlappingBridges_Gm011Shape() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - Cell("EXO 20:2", "two "), - Cell("EXO 20:3", "three "), - BridgeCell("EXO 20:4", "EXO 20:4-6", "4-6", "four to six "), - Cell("EXO 20:7", "seven "), - Cell("EXO 20:8", "eight "), - }; - var col1 = new List - { - Cell("EXO 20:1", "uno "), - BridgeCell("EXO 20:2", "EXO 20:2-3", "2-3", "dos a tres "), - Cell("EXO 20:4", "cuatro "), - Cell("EXO 20:5", "cinco "), - BridgeCell("EXO 20:6", "EXO 20:6-7", "6-7", "seis a siete "), - Cell("EXO 20:8", "ocho "), - }; - var columns = new List> { col0, col1 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(4), "gm-011 expected 4 rows"); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001"); - - // Row 0: v1 — unmerged. - Assert.That(ParagraphCount(rows[0].Cells[0]), Is.EqualTo(1)); - Assert.That(ParagraphCount(rows[0].Cells[1]), Is.EqualTo(1)); - - // Row 1: col 0 has v2, v3 (2 cells); col 1 has v2-3 bridge (1 cell). - Assert.That( - ParagraphCount(rows[1].Cells[0]), - Is.EqualTo(2), - "col 0 row 1 merges v2 and v3 to align with col 1's v2-3 bridge" - ); - Assert.That(ParagraphCount(rows[1].Cells[1]), Is.EqualTo(1)); - - // Row 2: col 0 has v4-6 bridge, v7 (2 cells); col 1 has v4, v5, v6-7 (3 cells). - Assert.That(ParagraphCount(rows[2].Cells[0]), Is.EqualTo(2)); - Assert.That( - ParagraphCount(rows[2].Cells[1]), - Is.EqualTo(3), - "col 1 row 2 merges v4, v5, v6-7 — at MAX_CELLS_TO_GRAB" - ); - - // Row 3: v8 — unmerged. - Assert.That(ParagraphCount(rows[3].Cells[0]), Is.EqualTo(1)); - Assert.That(ParagraphCount(rows[3].Cells[1]), Is.EqualTo(1)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("Invariant", "INV-006")] - [Description( - "Group D.11: MAX_CELLS_TO_GRAB hard limit — no cell ever merges more than 3 " - + "paragraphs regardless of how many adjacent cells in the other column " - + "could participate." - )] - public void BuildRowsMergingCells_ExactlyThreeCellsMerged_DoesNotExceedMax() - { - // col 0 has 6 consecutive individual cells v1..v6 - // col 1 has a single giant bridge v1-6 - // PT9 caps the grab at 3 even though 6 cells would match. - var col0 = new List - { - Cell("GEN 1:1", "v1 "), - Cell("GEN 1:2", "v2 "), - Cell("GEN 1:3", "v3 "), - Cell("GEN 1:4", "v4 "), - Cell("GEN 1:5", "v5 "), - Cell("GEN 1:6", "v6 "), - }; - var col1 = new List - { - BridgeCell("GEN 1:1", "GEN 1:1-6", "1-6", "one through six "), - }; - var columns = new List> { col0, col1 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - // Every row cell's paragraph count must be <= MAX_CELLS_TO_GRAB (3). - foreach (var row in rows) - { - foreach (var cell in row.Cells) - { - Assert.That( - ParagraphCount(cell), - Is.LessThanOrEqualTo(3), - $"INV-006: no cell merges more than MAX_CELLS_TO_GRAB (3); " - + $"got {ParagraphCount(cell)} at FirstRef={row.FirstRef}" - ); - } - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("GoldenMaster", "gm-013")] - [Description( - "Group D.12 (gm-013 FirstRef check): the merged row's FirstRef equals " - + "the earliest verse reference among grabbed cells." - )] - public void BuildRowsMergingCells_Gm013MergedRowReference_IsExpectedFirstRef() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - BridgeCell("EXO 20:2", "EXO 20:2-5", "2-5", "two to five "), - Cell("EXO 20:6", "six "), - BridgeCell("EXO 20:7", "EXO 20:7-8", "7-8", "seven to eight "), - }; - var col1 = new List - { - Cell("EXO 20:1", "uno "), - BridgeCell("EXO 20:4", "EXO 20:4-7", "4-7", "cuatro a siete "), - BridgeCell("EXO 20:8", "EXO 20:8-9", "8-9", "ocho a nueve "), - }; - var columns = new List> { col0, col1 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(2)); - Assert.That(RefString(rows[0].FirstRef), Is.EqualTo("EXO 20:1")); - // Row 1's FirstRef is the earliest verse in the merged block; col 0 starts - // with v2 (via v2-5 bridge) which is earlier than col 1's v4-7. - Assert.That( - RefString(rows[1].FirstRef), - Is.EqualTo("EXO 20:2"), - "FirstRef reflects earliest verse across all grabbed cells" - ); - } - - // ===================================================================== - // GROUP E — versification normalization pre-requisite (TS-028, TS-069) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-028")] - [Property("Invariant", "INV-007")] - [Description( - "Group E.13 (TS-028): when the orchestrator (CAP-006) pre-normalizes both " - + "columns to the common versification, cells with originally different " - + "refs (GEN 32:1 in Original vs GEN 31:55 in English) land in the same " - + "row. The row builder aligns on the normalized Reference string. " - + "Note: if the implementer chooses to add a versification companion " - + "parameter to do the normalization itself, this test will adapt " - + "during GREEN — the behavior under test (same-row alignment) stays." - )] - public void BuildRowsMergingCells_CellsWithPreNormalizedReferences_AlignByNormalizedRef() - { - // Both columns already carry the normalized "GEN 31:55" reference — - // the orchestrator called ChangeVersification on col 1 before handing to - // the row builder. CAP-005 has no versification responsibility in this - // test; only alignment by Reference string. - var columns = new List> - { - // col 0 was always English → "GEN 31:55" natively. - new() { Cell("GEN 31:55", "so Jacob said ") }, - // col 1 was Original → "GEN 32:1" natively; orchestrator converted to - // "GEN 31:55" before passing to the row builder. - new() { Cell("GEN 31:55", "y Jacob dijo ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(1), "pre-normalized refs align into one row"); - Assert.That(rows[0].Cells.Count, Is.EqualTo(2), "INV-001"); - Assert.That(RefString(rows[0].Cells[0].Reference), Is.EqualTo("GEN 31:55")); - Assert.That(RefString(rows[0].Cells[1].Reference), Is.EqualTo("GEN 31:55")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-069")] - [Property("Invariant", "INV-007")] - [Description( - "Group E.14 (TS-069, chapter break difference): same pattern as E.13 — " - + "GEN 32:1 in Hebrew == GEN 31:55 in English. Both cells carry the " - + "normalized reference at this layer; alignment succeeds." - )] - public void BuildRowsMergingCells_ChapterBreakDifference_AlignsViaPreNormalizedRefs() - { - // Two-cell setup. The chapter-boundary-different verse aligns with the - // immediately adjacent verse on the other side. - var columns = new List> - { - new() { Cell("GEN 31:54", "v54 "), Cell("GEN 31:55", "v55 ") }, - new() { Cell("GEN 31:54", "v54-es "), Cell("GEN 31:55", "v55-es ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows.Count, Is.EqualTo(2)); - Assert.That(RefString(rows[0].Cells[0].Reference), Is.EqualTo("GEN 31:54")); - Assert.That(RefString(rows[1].Cells[0].Reference), Is.EqualTo("GEN 31:55")); - foreach (var row in rows) - Assert.That(row.Cells.Count, Is.EqualTo(2), "INV-001"); - } - - // ===================================================================== - // GROUP F — duplicate verses (TS-068, MRK 16) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("ScenarioId", "TS-068")] - [Description( - "Group F.15 (TS-068): duplicate verse refs in the same column (e.g. MRK " - + "16:1 appearing twice due to shorter/longer ending traditions) produce " - + "separate rows rather than collapsing. PT9's handledCells HashSet " - + "prevents re-grabbing an already-processed cell, so the second " - + "occurrence gets its own row." - )] - public void BuildRowsMergingCells_DuplicateVerseReferences_GetSeparateRows() - { - var columns = new List> - { - new() - { - Cell("MRK 16:1", "first-ending v1 "), - Cell("MRK 16:2", "first-ending v2 "), - Cell("MRK 16:1", "second-ending v1 "), // duplicate ref - Cell("MRK 16:2", "second-ending v2 "), // duplicate ref - }, - new() { Cell("MRK 16:1", "es v1 "), Cell("MRK 16:2", "es v2 ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - // Exactly 4 rows — each of the 4 cells in col 0 (MRK 16:1, 16:2, 16:1-dup, - // 16:2-dup) gets its own row because the handledCells HashSet prevents - // re-grabbing an already-processed cell (see AddIfUnhandled). - Assert.That( - rows.Count, - Is.EqualTo(4), - "duplicate verse refs must each produce their own row (TS-068)" - ); - - // Count rows whose col 0 reference is "MRK 16:1" — should be 2 (duplicates). - int mrk16v1Rows = rows.Count(r => - r.Cells.Count > 0 - && !IsEmptyPlaceholder(r.Cells[0]) - && RefString(r.Cells[0].Reference) == "MRK 16:1" - ); - Assert.That(mrk16v1Rows, Is.EqualTo(2), "both occurrences of MRK 16:1 get their own row"); - } - - // ===================================================================== - // GROUP G — INV-001 / FirstRef postconditions - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("Invariant", "INV-001")] - [Description( - "Group G.16 (INV-001 property-style): over a gm-011-like setup, every row " - + "produced must have cells.Count == columns.Count. Exhaustive over the result." - )] - public void BuildRowsMergingCells_AllRows_HaveCellsCountEqualToColumnCount() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - Cell("EXO 20:2", "two "), - BridgeCell("EXO 20:4", "EXO 20:4-6", "4-6", "four to six "), - }; - var col1 = new List - { - BridgeCell("EXO 20:2", "EXO 20:2-3", "2-3", "dos a tres "), - Cell("EXO 20:5", "cinco "), - }; - var col2 = new List - { - Cell("EXO 20:1", "uno-fr "), - Cell("EXO 20:6", "six-fr "), - }; - var columns = new List> { col0, col1, col2 }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - Assert.That(rows, Is.Not.Empty); - foreach (var row in rows) - Assert.That( - row.Cells.Count, - Is.EqualTo(3), - $"INV-001: row at FirstRef={row.FirstRef} must have exactly 3 cells (3 columns)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Description( - "Group G.17 (FirstRef postcondition, BHV-111 carry-through): every row's " - + "FirstRef is non-null (no row is produced without any cells) and equals " - + "the reference of the earliest populated cell." - )] - public void BuildRowsMergingCells_FirstRefOfEachRow_ReflectsEarliestVerse() - { - var columns = new List> - { - new() { Cell("EXO 20:1", "one "), Cell("EXO 20:3", "three ") }, - new() { Cell("EXO 20:2", "dos "), Cell("EXO 20:3", "tres ") }, - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(columns); - - foreach (var row in rows) - { - Assert.That(row.FirstRef, Is.Not.Null, "every row has a FirstRef (BHV-111)"); - } - - // Rows should be ordered by FirstRef ascending (binary-search insertion). - // Assert each row's FirstRef sorts canonically via VerseRef.CompareTo - // (the structured ScriptureRange carries the start VerseRef directly). - for (int i = 1; i < rows.Count; i++) - { - VerseRef prevRef = rows[i - 1].FirstRef!.Start; - VerseRef currRef = rows[i].FirstRef!.Start; - Assert.That( - prevRef.CompareTo(currRef), - Is.LessThanOrEqualTo(0), - $"rows must be ordered by canonical VerseRef compare: " - + $"row {i - 1}={rows[i - 1].FirstRef}, row {i}={rows[i].FirstRef}" - ); - } - } - - // ===================================================================== - // GROUP H — Golden-master row count / cell count replay - // (Groups C, D already cover the detailed shape; these three tests - // collapse the top-line counts for quick-failure visibility.) - // ===================================================================== - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("GoldenMaster", "gm-011")] - [Description( - "Group H.18 (gm-011 counts): top-line rowCount=4, all rows 2 cells. " - + "Complements Group D.10 which asserts per-row merged paragraph counts." - )] - public void BuildRowsMergingCells_Gm011_Replay_Matches_RowCountAndCellCountPerRow() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - Cell("EXO 20:2", "two "), - Cell("EXO 20:3", "three "), - BridgeCell("EXO 20:4", "EXO 20:4-6", "4-6", "four to six "), - Cell("EXO 20:7", "seven "), - Cell("EXO 20:8", "eight "), - }; - var col1 = new List - { - Cell("EXO 20:1", "uno "), - BridgeCell("EXO 20:2", "EXO 20:2-3", "2-3", "dos a tres "), - Cell("EXO 20:4", "cuatro "), - Cell("EXO 20:5", "cinco "), - BridgeCell("EXO 20:6", "EXO 20:6-7", "6-7", "seis a siete "), - Cell("EXO 20:8", "ocho "), - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(new() { col0, col1 }); - - Assert.That(rows.Count, Is.EqualTo(4)); - Assert.That(rows.All(r => r.Cells.Count == 2), Is.True); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("GoldenMaster", "gm-012")] - [Description( - "Group H.19 (gm-012 counts): top-line rowCount=5, all rows 2 cells. " - + "Rows 0/1/3/4 have empty col 0 placeholders." - )] - public void BuildRowsMergingCells_Gm012_Replay_Matches_RowCountAndEmptyCellPattern() - { - var col0 = new List { Cell("EXO 20:5", "five "), Cell("EXO 20:6", "six ") }; - var col1 = new List - { - BridgeCell("EXO 20:1", "EXO 20:1-2", "1-2", "uno a dos "), - BridgeCell("EXO 20:3", "EXO 20:3-4", "3-4", "tres a cuatro "), - BridgeCell("EXO 20:5", "EXO 20:5-6", "5-6", "cinco a seis "), - Cell("EXO 20:7", "siete "), - Cell("EXO 20:8", "ocho "), - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(new() { col0, col1 }); - - Assert.That(rows.Count, Is.EqualTo(5)); - Assert.That(rows.All(r => r.Cells.Count == 2), Is.True); - - int emptyCol0Count = rows.Count(r => IsEmptyPlaceholder(r.Cells[0])); - Assert.That(emptyCol0Count, Is.EqualTo(4), "gm-012 has 4 rows with empty col 0"); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-005")] - [Property("BehaviorId", "BHV-109")] - [Property("GoldenMaster", "gm-013")] - [Property("Invariant", "INV-006")] - [Description( - "Group H.20 (gm-013 counts): top-line rowCount=2, all rows 2 cells. " - + "Row 1 col 0 merges exactly 3 paragraphs (MAX_CELLS_TO_GRAB)." - )] - public void BuildRowsMergingCells_Gm013_Replay_Matches_RowCountAndMergedCellCount() - { - var col0 = new List - { - Cell("EXO 20:1", "one "), - BridgeCell("EXO 20:2", "EXO 20:2-5", "2-5", "two to five "), - Cell("EXO 20:6", "six "), - BridgeCell("EXO 20:7", "EXO 20:7-8", "7-8", "seven to eight "), - }; - var col1 = new List - { - Cell("EXO 20:1", "uno "), - BridgeCell("EXO 20:4", "EXO 20:4-7", "4-7", "cuatro a siete "), - BridgeCell("EXO 20:8", "EXO 20:8-9", "8-9", "ocho a nueve "), - }; - - var rows = ChecklistRowBuilder.BuildRowsMergingCells(new() { col0, col1 }); - - Assert.That(rows.Count, Is.EqualTo(2)); - Assert.That(rows.All(r => r.Cells.Count == 2), Is.True); - Assert.That( - ParagraphCount(rows[1].Cells[0]), - Is.EqualTo(3), - "INV-006: gm-013 row 1 col 0 merges exactly 3 cells" - ); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistServiceBuildChecklistDataTests.cs b/c-sharp-tests/Checklists/ChecklistServiceBuildChecklistDataTests.cs deleted file mode 100644 index 645b4cac5e6..00000000000 --- a/c-sharp-tests/Checklists/ChecklistServiceBuildChecklistDataTests.cs +++ /dev/null @@ -1,1541 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.Projects; -using Paratext.Data; -using SIL.Scripture; -using ScriptureRange = Paranext.DataProvider.Scripture.ScriptureRange; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase contract and outer-acceptance tests for CAP-006 -/// (ChecklistService.BuildChecklistData — end-to-end orchestration). -/// -/// -/// These tests will NOT compile until the implementer adds -/// Paranext.DataProvider.Checklists.ChecklistService.BuildChecklistData( -/// ChecklistRequest, CancellationToken). The -/// compile error is the first layer of the RED signal; the test assertion -/// failures (after a stub body lands) are the second. Matches the -/// CAP-003 / CAP-004 / CAP-005 RED precedents. -/// -/// -/// -/// Per strategic-plan-backend.md §CAP-006, this capability uses -/// Outside-In TDD: the outer golden-master replays (gm-001, -/// gm-004) drive pipeline composition; focused unit tests pin -/// the specific invariants (INV-002, INV-010, INV-012, VAL-003, -/// VAL-004, INV-C15) and the edge-case scenarios (TS-053, TS-054, -/// TS-062, TS-070). -/// -/// -/// -/// Scope note — gm-014 / gm-019 not replayed here. Those golden -/// masters were captured with checklistType=Verses (see their -/// respective input.json), but per data-contracts.md §4.1 -/// "Checklist type is implicitly 'Markers' for this feature" CAP-006 only -/// implements the Markers path. TS-068 (duplicate verses) stays covered -/// through CAP-005's row-alignment unit tests. -/// -/// -/// -/// Scope note — EditLinkItem. CAP-012 owns the inline edit-link -/// gate. These CAP-006 tests therefore do NOT assert on the presence or -/// absence of content items. They assert only -/// on the outer shape (, -/// , , -/// , -/// , -/// , -/// , -/// ). -/// -/// -/// -/// Signature note. data-contracts.md §4.1 and strategic-plan-backend.md -/// differ on the method signature: the former lists -/// Task<ChecklistResult> BuildChecklistDataAsync(ChecklistRequest, -/// CancellationToken); the latter lists the sync -/// ChecklistResult BuildChecklistData(ChecklistRequest, -/// CancellationToken). These tests follow the -/// strategic-plan signature; if GREEN adopts the async shape, the tests -/// will be touched up to await the result. The compile-fail RED -/// signal is robust to either choice. -/// -/// -/// Traceability: -/// - Capability: CAP-006 -/// - Behaviors: BHV-100 (factory — transitive), BHV-101 (main), -/// BHV-118 (First/Last VerseRef — transitive), BHV-121 -/// (HasSameParagraphStructure — transitive) -/// - Extractions: EXT-001 (CreateDataSource), EXT-002 (BuildChecklistData), -/// EXT-015 (GetChecklistData wrapper with maxRows) -/// - Invariants: INV-002 (single-column IsMatch=true), INV-010 -/// (hideMatches tracking), INV-012 (max rows 5000), -/// VAL-003 (start 1:1 -> 1:0), VAL-004 (unknown ChecklistType), -/// INV-C15 (ColumnProjectIds parallel to ColumnHeaders) -/// - Scenarios: TS-001, TS-004, TS-005, TS-006, TS-049, TS-053, TS-054, -/// TS-062, TS-070, and (related / emergent) TS-002, TS-003, TS-032, TS-033 -/// - Golden Masters: gm-001 (primary outer acceptance), gm-004 (secondary) -/// - Contract: data-contracts.md §4.1 (BuildChecklistData), -/// §3.1 (ChecklistResult), §3.2 (ChecklistRow), §3.3 (ChecklistCell) -/// - PT9 source: Paratext/Checklists/CLDataSource.cs:97-185 (BuildRows) -/// -[TestFixture] -internal class ChecklistServiceBuildChecklistDataTests : PapiTestBase -{ - // --------------------------------------------------------------------- - // Shared helpers — reuse DummyScrText + LocalParatextProjects pattern - // --------------------------------------------------------------------- - - /// - /// The canonical EXO USFM captured in gm-001's input-EXO.usfm. - /// Single project, two verses, three paragraph markers (\p, \q, \q2). - /// - private const string Gm001ExoUsfm = - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented poetry"; - - /// gm-004's text1 EXO USFM (matches text1 captured input). - private const string Gm004Text1ExoUsfm = - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented poetry \p \v 3 three"; - - /// gm-004's text2 EXO USFM (matches text2 captured input). - private const string Gm004Text2ExoUsfm = - @"\id EXO \c 20 \p \v 1 uno. \v 2 dos, \p more text \q prose \q2 \v 3 indented prose"; - - /// - /// Registers a as a discoverable project so - /// resolves its - /// HexId. Mirrors the pattern used across the existing Projects tests - /// (see c-sharp-tests/Projects/ParatextDataProviderTests.cs:24). - /// - private DummyScrText RegisterDummyProject(string usfmPerBook, int bookNum = 2) - { - var scrText = new DummyScrText(); - // gm-001 / gm-004 use the poetry-style paragraph markers (\q, \q1, \q2) - // which DummyScrStylesheet defines only as scCharacterStyle. We must - // upgrade them to scParagraphStyle via reflection — same approach as - // CAP-003's ChecklistServiceTokenExtractionTests.PoetryStylesheet. - UpgradePoetryMarkersToParagraphStyle(scrText); - - scrText.PutText(bookNum, 0, false, usfmPerBook, null); - ParatextProjects.FakeAddProject(CreateProjectDetails(scrText), scrText); - return scrText; - } - - /// - /// Replaces the existing character-style q / q1 / q2 / b tags on - /// the DummyScrStylesheet with paragraph-style tags. gm-001 / gm-004 use - /// these as paragraph markers. Mirrors the approach in CAP-003's test - /// file; this helper additionally replaces the existing tag so the - /// stylesheet's scCharacterStyle entry (from DummyScrStylesheet) - /// is overridden. - /// - private static void UpgradePoetryMarkersToParagraphStyle(DummyScrText scrText) - { - // DummyScrStylesheet defines \v with a huge OccursUnder including - // q/q1/q2 as allowable parents of \v — so we just need to ADD - // paragraph-style tags for the Markers checklist's ParagraphMarkers - // query (BHV-102: scParagraphStyle filter). - var stylesheet = scrText.DefaultStylesheet; - - foreach (var marker in new[] { "q", "q1", "q2", "b" }) - { - AddPoetryTag(stylesheet, marker); - } - } - - private static void AddPoetryTag(ScrStylesheet stylesheet, string marker) - { - var tag = new ScrTag - { - Marker = marker, - TextProperties = - TextProperties.scParagraph - | TextProperties.scPublishable - | TextProperties.scVernacular - | TextProperties.scPoetic, - TextType = ScrTextType.scVerseText, - StyleType = ScrStyleType.scParagraphStyle, - OccursUnder = "c", - }; - - var addTagInternal = typeof(ScrStylesheet).GetMethod( - "AddTagInternal", - BindingFlags.Instance | BindingFlags.NonPublic - ); - if (addTagInternal == null) - { - throw new InvalidOperationException( - "ScrStylesheet.AddTagInternal not found via reflection; " - + "API has changed and this test helper must be updated." - ); - } - addTagInternal.Invoke(stylesheet, new object[] { tag }); - } - - /// - /// Builds a default request for a single-project Markers checklist over - /// EXO 20:1..EXO 20:20. Callers override individual fields via - /// with-expressions. - /// - private static ChecklistRequest BuildRequest( - string activeProjectId, - IReadOnlyList? comparativeTextIds = null, - ScriptureRange? verseRange = null, - bool hideMatches = false, - bool showVerseText = false, - string equivalentMarkers = "", - string markerFilter = "" - ) - { - verseRange ??= new ScriptureRange( - new VerseRef("EXO", "20", "1", ScrVers.English), - new VerseRef("EXO", "20", "20", ScrVers.English) - ); - - return new ChecklistRequest( - ProjectId: activeProjectId, - ComparativeTextIds: comparativeTextIds ?? Array.Empty(), - MarkerSettings: new MarkerSettings(equivalentMarkers, markerFilter), - VerseRange: verseRange, - HideMatches: hideMatches, - ShowVerseText: showVerseText - ); - } - - // ===================================================================== - // Group A — Happy path & single column (TS-001, TS-005, INV-002) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-001")] - [Property("BehaviorId", "BHV-101")] - public void BuildChecklistData_SingleProjectMarkers_ReturnsRowsWithMarkerParagraphs() - { - // TS-001: Single ScrText with EXO containing \p, \q, \q2 produces rows - // whose cells carry paragraphs with those markers. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result, Is.Not.Null); - Assert.That(result.Rows, Is.Not.Null); - Assert.That(result.Rows, Is.Not.Empty, "at least one row expected for \\p + \\q + \\q2"); - - // Collect every paragraph marker across every cell of every row. - var markers = result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .Select(p => p.Marker) - .ToList(); - - Assert.That(markers, Does.Contain("p"), "\\p paragraph marker must appear"); - Assert.That(markers, Does.Contain("q"), "\\q paragraph marker must appear"); - Assert.That(markers, Does.Contain("q2"), "\\q2 paragraph marker must appear"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-031")] - [Property("BehaviorId", "BHV-604")] - [Property("GoldenMaster", "gm-016")] - public void BuildChecklistData_ShowVerseTextWithCharacterStyle_PreservesCharacterStyleAttribution() - { - // T-B-6 / Rolf commitment #3124021961 — BHV-604 / gm-016 integration - // test. When showVerseText=true and USFM contains a character style - // (\em...\em*) inside a paragraph, the resulting TextItem items must - // include the character-style attribution on a distinct sub-item - // (TextItem.CharacterStyle == "em") for the styled run while the - // surrounding text carries CharacterStyle == null. Pins the behaviour - // end-to-end through the orchestrator (not just at the - // CAP-003 leaf level) so a regression that drops the CharacterStyle - // field on the wire cannot hide behind a passing golden master. - const string usfm = - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented \em poetry\em* "; - var scrText = RegisterDummyProject(usfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString(), showVerseText: true); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - // Collect all TextItems across all paragraphs so we can inspect the - // character-style attribution directly. - var textItems = result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .ToList(); - - Assert.That( - textItems, - Is.Not.Empty, - "showVerseText=true must emit TextItems alongside the marker attribution" - ); - - // Partition by CharacterStyle field — null for plain text, non-null - // for character-style runs. Both flavours must be present. - var styledItems = textItems.Where(t => t.CharacterStyle != null).ToList(); - var plainItems = textItems.Where(t => t.CharacterStyle == null).ToList(); - - Assert.That( - plainItems, - Is.Not.Empty, - "plain (non-styled) TextItems must be present (marker + surrounding text)" - ); - Assert.That( - styledItems, - Is.Not.Empty, - "BHV-604 / gm-016 — \\em character-style run must surface as a TextItem " - + "with CharacterStyle=\"em\"" - ); - Assert.That( - styledItems.Select(t => t.CharacterStyle).Distinct(), - Is.EqualTo(new[] { "em" }), - "BHV-604 — the only character style emitted here is \\em" - ); - Assert.That( - styledItems.Any(t => t.Text.Contains("poetry")), - Is.True, - "BHV-604 — the \\em-styled text \"poetry\" must carry CharacterStyle=\"em\"" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-005")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-002")] - public void BuildChecklistData_SingleColumn_AllRowsIsMatch_True() - { - // TS-005 / INV-002: Single-column checklists mark every row IsMatch=true - // (no difference highlighting is meaningful with only one column). - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That(result.Rows, Is.Not.Empty, "precondition — rows produced"); - foreach (var row in result.Rows) - { - Assert.That( - row.IsMatch, - Is.True, - $"INV-002 — single-column row must be IsMatch=true (row FirstRef={row.FirstRef})" - ); - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-010")] - public void BuildChecklistData_SingleColumn_ExcludedCountIsZero() - { - // INV-010 edge: single-column checklists never hide anything, so - // ExcludedCount must be 0 regardless of the hideMatches flag. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString(), hideMatches: true); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.ExcludedCount, - Is.EqualTo(0), - "single-column checklist has nothing to hide; ExcludedCount stays 0" - ); - } - - // ===================================================================== - // Group B — HideMatches filter (TS-004, INV-010) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-004")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-010")] - public void BuildChecklistData_TwoColumnsHideMatches_RemovesMatchingRows() - { - // TS-004 / INV-010: With one matching verse (v1 \p in both) and two - // non-matching verses (v2 + v3 — per gm-004 capture), hideMatches=true - // yields only the 2 non-matching rows with ExcludedCount=1. - var active = RegisterDummyProject(Gm004Text1ExoUsfm); - var compare = RegisterDummyProject(Gm004Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows.Count, - Is.EqualTo(2), - "two non-matching rows expected after hideMatches filtering" - ); - Assert.That( - result.ExcludedCount, - Is.EqualTo(1), - "one matching row removed -> ExcludedCount=1 (INV-010)" - ); - foreach (var row in result.Rows) - { - Assert.That( - row.IsMatch, - Is.False, - "every remaining row must be non-matching after hideMatches" - ); - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-004")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-010")] - public void BuildChecklistData_HideMatchesFalse_RetainsAllRows() - { - // TS-004 inverse: hideMatches=false keeps all rows (including matches) - // and ExcludedCount stays 0. - var active = RegisterDummyProject(Gm004Text1ExoUsfm); - var compare = RegisterDummyProject(Gm004Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: false - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows.Count, - Is.EqualTo(3), - "all 3 rows retained -> 1 match (EXO 20:1) + 2 non-match (EXO 20:2, 20:3)" - ); - Assert.That( - result.ExcludedCount, - Is.EqualTo(0), - "nothing hidden when hideMatches=false -> ExcludedCount=0" - ); - } - - // ===================================================================== - // Group C — Verse-range start adjustment (TS-006, VAL-003) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-006")] - [Property("BehaviorId", "BHV-101")] - [Property("ValidationRule", "VAL-003")] - public void BuildChecklistData_VerseRangeStartAtChapter1Verse1_AdjustsToVerse0() - { - // VAL-003: When request.VerseRange.start == (GEN 1:1), it is silently - // adjusted to (GEN 1:0) so introductory material (\ip at verse 0) is - // included. We seed \ip at position before \v 1 and assert it comes - // through in the result. - const string usfm = @"\id GEN \c 1 \ip An introduction. \p \v 1 In the beginning."; - var scrText = RegisterDummyProject(usfm, bookNum: 1); - - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - verseRange: new ScriptureRange( - new VerseRef("GEN", "1", "1", ScrVers.English), - new VerseRef("GEN", "1", "20", ScrVers.English) - ) - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - var markers = result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .Select(p => p.Marker) - .ToList(); - Assert.That( - markers, - Does.Contain("ip"), - "VAL-003 — start ref 1:1 must be adjusted to 1:0 so \\ip at verse 0 is included" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("ValidationRule", "VAL-003")] - public void BuildChecklistData_VerseRangeStartAtChapter1Verse2_DoesNotAdjust() - { - // VAL-003 inverse boundary: starts other than 1:1 are NOT adjusted. - // When start=1:2, any \ip at verse 0 must be excluded. - const string usfm = - @"\id GEN \c 1 \ip An introduction. \p \v 1 In the beginning. \v 2 continuing."; - var scrText = RegisterDummyProject(usfm, bookNum: 1); - - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - verseRange: new ScriptureRange( - new VerseRef("GEN", "1", "2", ScrVers.English), - new VerseRef("GEN", "1", "20", ScrVers.English) - ) - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - var markers = result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .Select(p => p.Marker) - .ToList(); - Assert.That( - markers, - Does.Not.Contain("ip"), - "VAL-003 is 1:1-specific — start=1:2 must not pull in the \\ip at verse 0" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - public void BuildChecklistData_VerseRangeEndOmitted_ScansSingleVerseAtStart() - { - // Platform ScriptureRange contract (lib/papi-dts): - // "If not provided, then only the verse indicated by start is included." - // Verify the checklist honors that contract — passing { Start, End: null } - // must narrow to a single-verse range, NOT fall back to the project bounds. - const string usfm = @"\id GEN \c 1 \v 1 alpha \p \v 2 bravo \p \v 3 charlie \p \v 4 delta"; - var scrText = RegisterDummyProject(usfm, bookNum: 1); - - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - verseRange: new ScriptureRange( - Start: new VerseRef("GEN", "1", "2", ScrVers.English), - End: null - ), - showVerseText: true - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - var verseTexts = result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Select(t => t.Text.Trim()) - .Where(t => !string.IsNullOrEmpty(t)) - .ToList(); - - // Only verse 2 should appear; verses 1, 3, 4 must be excluded. - Assert.That( - verseTexts, - Has.Some.Contains("bravo"), - "End == null must include the verse at Start (GEN 1:2)" - ); - Assert.That( - verseTexts, - Has.None.Contains("alpha"), - "End == null must NOT scan the whole project (verse 1 should be excluded)" - ); - Assert.That( - verseTexts, - Has.None.Contains("charlie"), - "End == null must NOT scan past the Start verse (verse 3 should be excluded)" - ); - Assert.That( - verseTexts, - Has.None.Contains("delta"), - "End == null must NOT scan past the Start verse (verse 4 should be excluded)" - ); - } - - // ===================================================================== - // Group D — Max rows truncation (TS-049, INV-012) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-012")] - public void BuildChecklistData_ResultUnder5000Rows_TruncatedFalse() - { - // INV-012 negative direction: a small result (well under 5000) must - // have Truncated=false. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Truncated, - Is.False, - "small result (<5000 rows) must not be marked Truncated" - ); - Assert.That( - result.Rows.Count, - Is.LessThanOrEqualTo(5000), - "INV-012 upper bound — row count must never exceed 5000" - ); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-049")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-012")] - public void BuildChecklistData_ResultExceeds5000Rows_TruncatedFlagSet() - { - // INV-012 positive direction: if the pipeline would produce >5000 - // rows, the result must be truncated at 5000 and Truncated=true. - // - // Seed the project with enough \p paragraphs to cross the threshold. - // Strategy: many chapters, many verses-per-chapter with \p per verse. - // We target ~5500 paragraphs in a single book across many chapters. - var usfm = new System.Text.StringBuilder(@"\id GEN"); - // 110 chapters * 50 paragraphs/chapter = 5500 paragraphs - for (int chapter = 1; chapter <= 110; chapter++) - { - usfm.Append($" \\c {chapter}"); - for (int verse = 1; verse <= 50; verse++) - { - usfm.Append($" \\p \\v {verse} content."); - } - } - - var scrText = RegisterDummyProject(usfm.ToString(), bookNum: 1); - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - verseRange: new ScriptureRange( - new VerseRef("GEN", "1", "1", ScrVers.English), - new VerseRef("GEN", "110", "50", ScrVers.English) - ) - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Truncated, - Is.True, - "INV-012 — producing >5000 rows must set Truncated=true" - ); - Assert.That( - result.Rows.Count, - Is.EqualTo(5000), - "INV-012 — truncated result must have exactly 5000 rows" - ); - } - - // ===================================================================== - // Group E — CancellationToken (TS-062) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-062")] - [Property("BehaviorId", "BHV-101")] - public void BuildChecklistData_CancellationRequested_Throws() - { - // TS-062: PT10 replaces PT9's Progress.Mgr.EndProgressIfCancelled with - // CancellationToken. A cancelled token passed to BuildChecklistData - // must surface via OperationCanceledException (standard .NET pattern - // for ct.ThrowIfCancellationRequested / ct.IsCancellationRequested). - // - // NOTE: GREEN may instead choose to return a structured error result - // (ChecklistResultError with code "CANCELLED" per data-contracts.md - // §4.1 error table). In that case this test will be adjusted to - // match the chosen contract — RED compile-fail is robust to either. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - Assert.That( - () => ChecklistService.BuildChecklistData(request, cts.Token), - Throws.InstanceOf(), - "TS-062 — cancelled token must surface as OperationCanceledException" - ); - } - - // ===================================================================== - // Group F — Factory & unknown checklist type (TS-053, TS-054, BHV-100, VAL-004) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-053")] - [Property("BehaviorId", "BHV-100")] - public void BuildChecklistData_ChecklistTypeMarkers_ComposesMarkersPipeline() - { - // TS-053: the Markers pipeline is composed under the hood. Indirect - // observation via BHV-103 — MarkersDataSource.PostProcessParagraph - // prepends a backslash-prefixed marker TextItem at position 0 of - // every paragraph's Items (INV-004). If the service did NOT route - // through MarkersDataSource, the first item of each paragraph would - // not be a TextItem with text "\p" / "\q" / "\q2". - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString(), showVerseText: true); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That(result.Rows, Is.Not.Empty, "precondition — rows produced"); - foreach (var row in result.Rows) - foreach (var cell in row.Cells) - foreach (var paragraph in cell.Paragraphs) - { - Assume.That( - paragraph.Items, - Is.Not.Empty, - $"precondition — paragraph {paragraph.Marker} has items" - ); - var first = paragraph.Items[0]; - Assert.That( - first, - Is.InstanceOf(), - "BHV-103 / INV-004 — first item must be TextItem carrying backslash-prefixed marker" - ); - var firstText = (TextItem)first; - Assert.That( - firstText.Text, - Is.EqualTo("\\" + paragraph.Marker), - $"BHV-103 / INV-004 — first TextItem.Text must equal \\{paragraph.Marker}" - ); - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-054")] - [Property("BehaviorId", "BHV-100")] - [Property("ValidationRule", "VAL-004")] - [Ignore( - "VAL-004 tracks invalid ChecklistType handling. ChecklistRequest (data-contracts §2.1) has no ChecklistType field — the current API is implicitly Markers-only. Kept as a placeholder so traceability matrix records VAL-004; remove Ignore if GREEN exposes a ChecklistType surface that can be stress-tested." - )] - public void BuildChecklistData_UnknownChecklistType_ThrowsInvalidOperationException() - { - // VAL-004 placeholder. See [Ignore] rationale above — the test is - // always skipped via [Ignore] so this body is never executed. - Assert.Pass("placeholder — see [Ignore] rationale"); - } - - // ===================================================================== - // Group G — Empty / edge inputs (TS-070) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-070")] - [Property("BehaviorId", "BHV-101")] - public void BuildChecklistData_ProjectIdNotRegistered_SurfacesResolutionError() - { - // TS-070 analog: unresolvable projectId. The strategic plan - // documents PROJECT_NOT_FOUND as a structured error code, but the - // PT10 resolver (ScrTextCollection.GetById) throws on unknown IDs. - // Either the service catches and wraps (structured error) OR the - // exception bubbles out. We assert that the thrown exception is - // NOT a NotImplementedException (which would mean the implementation - // hasn't landed yet — we reject that false-green path), AND is not - // null (something must indicate the error). - // - // GREEN note: if the implementer wraps the resolver exception into a - // structured result (ChecklistResultError with code "PROJECT_NOT_FOUND"), - // this test will be adjusted to inspect the structured error instead - // of asserting Throws. - const string missingProjectId = "0123456789abcdef0123456789abcdef01234567"; - var request = BuildRequest( - activeProjectId: missingProjectId // not registered - ); - - Exception? caught = null; - try - { - ChecklistService.BuildChecklistData(request, CancellationToken.None); - } - catch (Exception ex) - { - caught = ex; - } - - Assert.That( - caught, - Is.Not.Null, - "TS-070 / PROJECT_NOT_FOUND — unresolvable projectId must surface as an error" - ); - Assert.That( - caught, - Is.Not.InstanceOf(), - "TS-070 — NotImplementedException is a RED-stub artifact, not the expected resolution error. " - + "GREEN implementer must actively reject unknown projectIds (either throw a PT9-style " - + "resolver exception or return a structured PROJECT_NOT_FOUND error)." - ); - Assert.That( - caught!.Message, - Does.Contain(missingProjectId), - "TS-070 — the exception message must reference the missing projectId so the " - + "failure is self-diagnosing (not just a generic \"project not found\" opaque error)." - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-008")] - public void BuildChecklistData_VerseRangeOutsideBooksPresentSet_ProducesEmptyResultWithMessage() - { - // Edge: verse range does not intersect any book in BooksPresentSet, so - // no books are iterated and no rows are produced. INV-008 requires an - // EmptyResultMessage in that case. - var scrText = RegisterDummyProject(Gm001ExoUsfm); // registers EXO (book 2) - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - verseRange: new ScriptureRange( - new VerseRef("JHN", "1", "1", ScrVers.English), - new VerseRef("JHN", "1", "20", ScrVers.English) - ) - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result.Rows, Is.Empty, "range outside registered books -> no rows"); - Assert.That( - result.EmptyResultMessage, - Is.Not.Null, - "INV-008 — empty results must carry an EmptyResultMessage" - ); - } - - // ===================================================================== - // Group H — INV-C15 ColumnProjectIds parallel to ColumnHeaders - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-C15")] - public void BuildChecklistData_SingleProject_ColumnProjectIdsContainsOnlyRequestProjectId() - { - // INV-C15: With one active project, ColumnHeaders and ColumnProjectIds - // both have exactly one entry, and ColumnProjectIds[0] equals the - // request's ProjectId. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.ColumnHeaders.Count, - Is.EqualTo(1), - "single project -> one column header" - ); - Assert.That( - result.ColumnProjectIds.Count, - Is.EqualTo(result.ColumnHeaders.Count), - "INV-C15 — ColumnProjectIds.Count must equal ColumnHeaders.Count" - ); - Assert.That( - result.ColumnProjectIds[0], - Is.EqualTo(request.ProjectId), - "INV-C15 — ColumnProjectIds[0] must equal request.ProjectId" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-C15")] - public void BuildChecklistData_ActiveProjectPlusComparative_ColumnProjectIdsOrderMatches() - { - // INV-C15 with 2 columns: active project at index 0, comparative at - // index 1 in request order. - var active = RegisterDummyProject(Gm004Text1ExoUsfm); - var compare = RegisterDummyProject(Gm004Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() } - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.ColumnHeaders.Count, - Is.EqualTo(2), - "active + 1 comparative -> 2 column headers" - ); - Assert.That( - result.ColumnProjectIds.Count, - Is.EqualTo(result.ColumnHeaders.Count), - "INV-C15 — ColumnProjectIds.Count must equal ColumnHeaders.Count" - ); - Assert.That( - result.ColumnProjectIds[0], - Is.EqualTo(active.Guid.ToString()), - "INV-C15 — active project must be at index 0" - ); - Assert.That( - result.ColumnProjectIds[1], - Is.EqualTo(compare.Guid.ToString()), - "INV-C15 — comparative must follow the active project in request order" - ); - } - - // ===================================================================== - // Group I — Outer acceptance gm-001 replay (primary TDD signal) - // ===================================================================== - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-001")] - [Property("GoldenMaster", "gm-001")] - [Property("BehaviorId", "BHV-101")] - public void Gm001_SingleProjectMarkers_Replay_MatchesShape() - { - // gm-001 primary outer acceptance: single project, EXO 20:1..20:20, - // showVerseText=true, hideMatches=true (but single column so no-op), - // expected rowCount=2, excludedCount=0. Row 0 = EXO 20:1 cell with - // one paragraph marker="p". Row 1 = EXO 20:2 cell with two paragraphs - // marker="q" then marker="q2". - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - hideMatches: true, - showVerseText: true - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result.Rows.Count, Is.EqualTo(2), "gm-001 — exactly 2 rows"); - Assert.That(result.ExcludedCount, Is.EqualTo(0), "gm-001 — ExcludedCount=0"); - - // Row 0: one cell, one paragraph marker "p". - var row0 = result.Rows[0]; - Assert.That(row0.Cells.Count, Is.EqualTo(1), "gm-001 row 0 — single cell (one column)"); - Assert.That( - row0.Cells[0].Paragraphs.Count, - Is.EqualTo(1), - "gm-001 row 0 cell 0 — one paragraph" - ); - Assert.That( - row0.Cells[0].Paragraphs[0].Marker, - Is.EqualTo("p"), - "gm-001 row 0 paragraph marker = \"p\"" - ); - - // Row 1: one cell, two paragraphs — "q" then "q2". - var row1 = result.Rows[1]; - Assert.That(row1.Cells.Count, Is.EqualTo(1), "gm-001 row 1 — single cell"); - Assert.That( - row1.Cells[0].Paragraphs.Count, - Is.EqualTo(2), - "gm-001 row 1 cell 0 — two paragraphs (q + q2)" - ); - Assert.That( - row1.Cells[0].Paragraphs[0].Marker, - Is.EqualTo("q"), - "gm-001 row 1 paragraph 0 marker = \"q\"" - ); - Assert.That( - row1.Cells[0].Paragraphs[1].Marker, - Is.EqualTo("q2"), - "gm-001 row 1 paragraph 1 marker = \"q2\"" - ); - - // INV-002 — all rows IsMatch=true for single column. - Assert.That( - result.Rows.All(r => r.IsMatch), - Is.True, - "gm-001 — every row IsMatch=true (INV-002, single column)" - ); - } - - // ===================================================================== - // Group I-a — Additional GM replays (T-B-6 / Rolf commitment #3124164642) - // - // Integration-level BuildChecklistData replays for gm-002, gm-003, gm-005, - // gm-006 — each pinning the distinctive assertion that the GM targets - // (identical-markers empty message vs different-markers row output vs - // bidirectional-mapping identical vs partial-mapping-differences). gm-007 - // exercises the private InitializeMarkerMappings parser — not the - // BuildChecklistData pipeline — so it's ignored here and covered by - // CAP-002's tests instead. - // ===================================================================== - - /// - /// gm-002 text1 — same as (two verses, markers p, q, q2). - /// The gm-002 fixture uses the gm-001 EXO USFM verbatim. - /// - private const string Gm002_Text1ExoUsfm = Gm001ExoUsfm; - - /// gm-002 text2 — identical marker structure (p, q, q2) to text1 but different content. - private const string Gm002_Text2ExoUsfm = - @"\id EXO \c 20 \p \v 1 uno. \v 2 dos, \q prose \q2 indented prose"; - - /// gm-003 / gm-005 / gm-006 text1 — same as gm-004 text1 (adds \v 3 with \p). - private const string GmShared_Text1ExoUsfm_WithV3 = Gm004Text1ExoUsfm; - - /// gm-003 text2 — differing marker structure from text1. - private const string Gm003_Text2ExoUsfm = Gm004Text2ExoUsfm; - - /// gm-005 / gm-006 text2 — uses \q1 where gm-003 text2 uses \q2/\q. - private const string Gm005_Text2ExoUsfm = - @"\id EXO \c 20 \p \v 1 uno. \v 2 dos, \p more text \q1 prose \q \v 3 indented prose"; - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-002")] - [Property("GoldenMaster", "gm-002")] - [Property("BehaviorId", "BHV-106")] - public void Gm002_IdenticalMarkersMessage_Replay_ProducesIdenticalEmptyResultMessage() - { - // gm-002: two texts with IDENTICAL paragraph markers (p, q, q2) across - // the two verses present (EXO 20:1-2). hideMatches=true filters every - // row, so the result is empty and PostProcessRows returns - // EmptyResultMessage with Variant="identical". - var active = RegisterDummyProject(Gm002_Text1ExoUsfm); - var compare = RegisterDummyProject(Gm002_Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true, - showVerseText: false - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows, - Is.Empty, - "gm-002 — identical markers across both texts + hideMatches=true → empty rows" - ); - Assert.That( - result.EmptyResultMessage, - Is.Not.Null, - "gm-002 — empty result must carry an EmptyResultMessage (INV-008)" - ); - Assert.That( - result.EmptyResultMessage!.Variant, - Is.EqualTo(EmptyResultMessageVariant.Identical), - "gm-002 — 'identical' variant (no filter active; empty via hide-matches-all)" - ); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-003")] - [Property("GoldenMaster", "gm-003")] - [Property("BehaviorId", "BHV-101")] - public void Gm003_DifferentMarkersComparison_Replay_ProducesDifferenceRows() - { - // gm-003: two texts with DIFFERENT marker structures. Per expected-output.json - // rowCount=2, excludedCount=1 (the v1 \p match hides). Row 0 EXO 20:2 - // has [q,q2] | [p,q]; row 1 EXO 20:3 has [p] | [q2]. - var active = RegisterDummyProject(GmShared_Text1ExoUsfm_WithV3); - var compare = RegisterDummyProject(Gm003_Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true, - showVerseText: false - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result.Rows.Count, Is.EqualTo(2), "gm-003 — 2 non-matching rows retained"); - Assert.That(result.ExcludedCount, Is.EqualTo(1), "gm-003 — 1 matching row hidden"); - - var row0Col0Markers = result.Rows[0].Cells[0].Paragraphs.Select(p => p.Marker).ToList(); - var row0Col1Markers = result.Rows[0].Cells[1].Paragraphs.Select(p => p.Marker).ToList(); - Assert.That( - row0Col0Markers, - Is.EqualTo(new[] { "q", "q2" }), - "gm-003 row 0 col 0 — [q, q2]" - ); - Assert.That(row0Col1Markers, Is.EqualTo(new[] { "p", "q" }), "gm-003 row 0 col 1 — [p, q]"); - var row1Col0Markers = result.Rows[1].Cells[0].Paragraphs.Select(p => p.Marker).ToList(); - var row1Col1Markers = result.Rows[1].Cells[1].Paragraphs.Select(p => p.Marker).ToList(); - Assert.That(row1Col0Markers, Is.EqualTo(new[] { "p" }), "gm-003 row 1 col 0 — [p]"); - Assert.That(row1Col1Markers, Is.EqualTo(new[] { "q2" }), "gm-003 row 1 col 1 — [q2]"); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-013")] - [Property("GoldenMaster", "gm-005")] - [Property("BehaviorId", "BHV-104")] - public void Gm005_BidirectionalMappingIdentical_Replay_ProducesIdenticalEmptyResultMessage() - { - // gm-005: full bidirectional mapping "p/q q1/q2" makes all markers - // equivalent across the two texts (p==q, q1==q2). Every row becomes a - // match, so hideMatches=true filters everything → EmptyResultMessage - // Variant="identical". - var active = RegisterDummyProject(GmShared_Text1ExoUsfm_WithV3); - var compare = RegisterDummyProject(Gm005_Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true, - showVerseText: false, - equivalentMarkers: "p/q q1/q2" - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows, - Is.Empty, - "gm-005 — full bidirectional mapping makes all markers equivalent → empty rows" - ); - Assert.That( - result.EmptyResultMessage, - Is.Not.Null, - "gm-005 — empty result must carry an EmptyResultMessage" - ); - Assert.That( - result.EmptyResultMessage!.Variant, - Is.EqualTo(EmptyResultMessageVariant.Identical), - "gm-005 — 'identical' variant (no filter active; empty via mapping-made-matches)" - ); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-014")] - [Property("GoldenMaster", "gm-006")] - [Property("BehaviorId", "BHV-104")] - public void Gm006_PartialMappingDifferences_Replay_RetainsOnlyUnmappedDifferenceRows() - { - // gm-006: only p/q is mapped (q1/q2 unmapped). v1 p==p match, v2 q==p - // (mapped) BUT q2!=q1 (unmapped) → difference, v3 p==q (mapped) match. - // rowCount=1, excludedCount=2 per expected-output.json. - var active = RegisterDummyProject(GmShared_Text1ExoUsfm_WithV3); - var compare = RegisterDummyProject(Gm005_Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true, - showVerseText: false, - equivalentMarkers: "p/q" - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows.Count, - Is.EqualTo(1), - "gm-006 — only v2 differs (q2 vs q1 unmapped) → 1 row retained" - ); - Assert.That( - result.ExcludedCount, - Is.EqualTo(2), - "gm-006 — v1 + v3 matches hidden → ExcludedCount=2" - ); - - var row0Col0Markers = result.Rows[0].Cells[0].Paragraphs.Select(p => p.Marker).ToList(); - var row0Col1Markers = result.Rows[0].Cells[1].Paragraphs.Select(p => p.Marker).ToList(); - Assert.That( - row0Col0Markers, - Is.EqualTo(new[] { "q", "q2" }), - "gm-006 row 0 col 0 — [q, q2] (active text at v2)" - ); - Assert.That( - row0Col1Markers, - Is.EqualTo(new[] { "p", "q1" }), - "gm-006 row 0 col 1 — [p, q1] (comparative text at v2)" - ); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-055")] - [Property("GoldenMaster", "gm-018")] - [Property("BehaviorId", "BHV-103")] - [Property("Invariant", "INV-004")] - public void Gm018_MarkerDisplayFormat_Replay_ProducesBackslashPrefixedMarkerItems() - { - // gm-018 exercises INV-004 (backslash-prefixed marker display) via the - // BuildChecklistData pipeline. Same USFM as gm-001 but with - // showVerseText=false so the only text item emitted per paragraph is - // the backslash-marker name. Expected (per gm-018/expected-output.json): - // rowCount=2, excludedCount=0, every paragraph's first content item is - // a TextItem whose Text starts with "\". - var active = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: Array.Empty(), - hideMatches: false, - showVerseText: false - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows, - Has.Count.EqualTo(2), - "gm-018 — rowCount=2 per captured expected-output.json" - ); - Assert.That( - result.ExcludedCount, - Is.EqualTo(0), - "gm-018 — excludedCount=0 (hideMatches=false)" - ); - - // INV-004: every paragraph's first content item (the marker name item) - // must carry the backslash-prefixed marker as its Text. The gm-018 - // expected-output shows "\\p", "\\q", "\\q2" as the Text value of the - // CLText item at position 0 in each paragraph. PostProcessParagraph - // prepends this; when showVerseText=false the following text items - // are dropped (BHV-103), so the marker item is often the ONLY item. - foreach (var row in result.Rows) - { - foreach (var cell in row.Cells) - { - foreach (var paragraph in cell.Paragraphs) - { - Assert.That( - paragraph.Items, - Is.Not.Empty, - "INV-004 — every paragraph has at least the marker-name item" - ); - Assert.That( - paragraph.Items[0], - Is.InstanceOf(), - $"INV-004 — first item of paragraph '{paragraph.Marker}' must be the marker-name TextItem" - ); - var markerItem = (TextItem)paragraph.Items[0]; - Assert.That( - markerItem.Text, - Does.StartWith(@"\"), - $"INV-004 — marker-name TextItem must start with '\\' for paragraph '{paragraph.Marker}'" - ); - Assert.That( - markerItem.Text, - Is.EqualTo(@"\" + paragraph.Marker), - $"INV-004 — marker-name text is '\\{paragraph.Marker}'" - ); - } - } - } - } - - // ===================================================================== - // Group J — Outer acceptance gm-004 replay (secondary) - // ===================================================================== - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-004")] - [Property("GoldenMaster", "gm-004")] - [Property("BehaviorId", "BHV-101")] - [Property("Invariant", "INV-010")] - public void Gm004_HideMatchesFiltering_Replay_MatchesShape() - { - // gm-004 secondary outer acceptance: two projects, hideMatches=true, - // showVerseText=false. Expected: rowCount=2, excludedCount=1, both - // remaining rows IsMatch=false. Row 0 EXO 20:2 cells: [q,q2] and - // [p,q]. Row 1 EXO 20:3 cells: [p] and [q2]. - var active = RegisterDummyProject(Gm004Text1ExoUsfm); - var compare = RegisterDummyProject(Gm004Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true, - showVerseText: false - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result.Rows.Count, Is.EqualTo(2), "gm-004 — 2 non-matching rows retained"); - Assert.That(result.ExcludedCount, Is.EqualTo(1), "gm-004 — 1 matching row hidden"); - - // Row 0 (EXO 20:2): [q,q2] | [p,q] - var row0 = result.Rows[0]; - Assert.That(row0.IsMatch, Is.False, "gm-004 row 0 is non-match"); - Assert.That(row0.Cells.Count, Is.EqualTo(2), "gm-004 row 0 — 2 cells"); - var row0Col0Markers = row0.Cells[0].Paragraphs.Select(p => p.Marker).ToList(); - var row0Col1Markers = row0.Cells[1].Paragraphs.Select(p => p.Marker).ToList(); - Assert.That( - row0Col0Markers, - Is.EqualTo(new[] { "q", "q2" }), - "gm-004 row 0 col 0 — paragraphs [q, q2]" - ); - Assert.That( - row0Col1Markers, - Is.EqualTo(new[] { "p", "q" }), - "gm-004 row 0 col 1 — paragraphs [p, q]" - ); - - // Row 1 (EXO 20:3): [p] | [q2] - var row1 = result.Rows[1]; - Assert.That(row1.IsMatch, Is.False, "gm-004 row 1 is non-match"); - Assert.That(row1.Cells.Count, Is.EqualTo(2), "gm-004 row 1 — 2 cells"); - var row1Col0Markers = row1.Cells[0].Paragraphs.Select(p => p.Marker).ToList(); - var row1Col1Markers = row1.Cells[1].Paragraphs.Select(p => p.Marker).ToList(); - Assert.That( - row1Col0Markers, - Is.EqualTo(new[] { "p" }), - "gm-004 row 1 col 0 — paragraph [p]" - ); - Assert.That( - row1Col1Markers, - Is.EqualTo(new[] { "q2" }), - "gm-004 row 1 col 1 — paragraph [q2]" - ); - } - - // ===================================================================== - // Group K — EmptyResultMessage variant pins - // T-B-6 / Rolf commitment #3124164814 — pin Variant ('identical' vs - // 'noResults'), SearchedMarkers, SearchedBooks, and the localize-key - // Message so BHV-600 / BHV-106 variants can't silently regress. - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-600")] - [Property("Invariant", "INV-008")] - public void BuildChecklistData_IdenticalMarkersEmptyResult_VariantIsIdenticalAndFieldsNull() - { - // BHV-600 "identical" path: two comparative texts with matching marker - // structures + hideMatches=true → every row filtered → empty rows. - // PostProcessRows sees markerFilter.Count == 0 and returns - // Variant="identical" with SearchedMarkers=null + SearchedBooks=null. - // Message carries the localize key (resolved at the NetworkObject wire - // boundary, not here). - var active = RegisterDummyProject(Gm002_Text1ExoUsfm); - var compare = RegisterDummyProject(Gm002_Text2ExoUsfm); - var request = BuildRequest( - activeProjectId: active.Guid.ToString(), - comparativeTextIds: new[] { compare.Guid.ToString() }, - hideMatches: true - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That(result.Rows, Is.Empty, "identical markers + hideMatches=true → empty rows"); - Assert.That(result.EmptyResultMessage, Is.Not.Null); - Assert.That( - result.EmptyResultMessage!.Variant, - Is.EqualTo(EmptyResultMessageVariant.Identical), - "BHV-600 — 'identical' variant when no filter is active" - ); - Assert.That( - result.EmptyResultMessage.SearchedMarkers, - Is.Null, - "BHV-600 — SearchedMarkers MUST be null for the 'identical' variant" - ); - Assert.That( - result.EmptyResultMessage.SearchedBooks, - Is.Null, - "BHV-600 — SearchedBooks MUST be null for the 'identical' variant" - ); - Assert.That( - result.EmptyResultMessage.Message, - Is.EqualTo(MarkersDataSource.IdenticalMarkersMessageKey), - "BHV-600 — Message must carry the IdenticalMarkersMessageKey localize key; " - + "resolution happens at the NetworkObject wire boundary" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-106")] - [Property("Invariant", "INV-008")] - public void BuildChecklistData_FilterActiveNoMatches_VariantIsNoResultsAndFieldsPopulated() - { - // BHV-106 "noResults" path: markerFilter is active (non-empty) but no - // paragraphs match any filtered marker → empty rows. - // PostProcessRows returns Variant="noResults" with SearchedMarkers - // populated from the filter and SearchedBooks populated from the - // iterated book set. - const string filteredMarker = "zz"; // not present in any USFM - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest( - activeProjectId: scrText.Guid.ToString(), - markerFilter: filteredMarker - ); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assert.That( - result.Rows, - Is.Empty, - "filter on a non-present marker produces no matching rows" - ); - Assert.That(result.EmptyResultMessage, Is.Not.Null); - Assert.That( - result.EmptyResultMessage!.Variant, - Is.EqualTo(EmptyResultMessageVariant.NoResults), - "BHV-106 — 'noResults' variant when a filter is active but no rows match" - ); - Assert.That( - result.EmptyResultMessage.SearchedMarkers, - Is.Not.Null.And.Contains(filteredMarker), - "BHV-106 — SearchedMarkers must carry the active filter tokens" - ); - Assert.That( - result.EmptyResultMessage.SearchedBooks, - Is.Not.Null.And.Contains("EXO"), - "BHV-106 — SearchedBooks must carry the iterated book ids (EXO registered here)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-006")] - [Property("Contract", "BuildChecklistData")] - [Property("BehaviorId", "BHV-600")] - [Property("Invariant", "INV-008")] - public void BuildChecklistData_NonEmptyRows_EmptyResultMessageIsNull() - { - // INV-008 inverse direction: when rows are non-empty, EmptyResultMessage - // MUST be null (neither variant applies). Keeps the variant pin - // non-fragile — a regression that always emitted an "identical" - // message would pass the other two tests but fail here. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That(result.Rows, Is.Not.Empty, "precondition — rows produced"); - Assert.That( - result.EmptyResultMessage, - Is.Null, - "INV-008 inverse — non-empty rows must not carry an EmptyResultMessage" - ); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistServiceCellConstructionTests.cs b/c-sharp-tests/Checklists/ChecklistServiceCellConstructionTests.cs deleted file mode 100644 index 25f29c45895..00000000000 --- a/c-sharp-tests/Checklists/ChecklistServiceCellConstructionTests.cs +++ /dev/null @@ -1,743 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Paranext.DataProvider.Checklists; -using Paratext.Data; -using PtxUtils; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase contract tests for CAP-004 (Cell Construction — -/// GetCellsForBook + internal BuildCLCell). -/// -/// -/// These tests will NOT compile until the implementer adds -/// Paranext.DataProvider.Checklists.ChecklistService.GetCellsForBook -/// (see CAP-003's precedent — the compile error is the first layer of the RED -/// signal; from a stub body is the -/// second). Matches the CAP-001 / CAP-002 / CAP-003 / CAP-007 RED pattern. -/// -/// -/// -/// Scope: the single public cell-construction method. Downstream orchestration -/// (row alignment via CAP-005, end-to-end BuildChecklistData via CAP-006, -/// inline edit-link emission via CAP-012) is covered by those capabilities' -/// own tests. Per strategic-plan-backend.md §CAP-004 (revised 2026-04-13), -/// orchestration-level verification of gm-015 / gm-019 is delegated to -/// CAP-006's integration tests; this file asserts the cell-level -/// postconditions directly on the List<ChecklistCell> returned -/// by . -/// -/// -/// Traceability: -/// - Capability: CAP-004 -/// - Behaviors: BHV-114 (primary) -/// - Extractions: EXT-011 (GetCellsForBook + BuildCLCell) -/// - Invariants: VAL-007 (edit link — actual emission gate is CAP-012; CAP-004 -/// tests assert only the cell structure supports it) -/// - Scenarios: TS-029, TS-030, TS-050, TS-051, TS-052 (deferred per -/// DEF-BE-001), TS-058 -/// - Contract: data-contracts.md §4.1 (BHV-114 inside BuildChecklistData), -/// §3.3 (ChecklistCell), §3.4 (ChecklistParagraph), §3.5 (content items) -/// - PT9 source: Paratext/Checklists/CLDataSource.cs:191-433 -/// -[TestFixture] -internal class ChecklistServiceCellConstructionTests -{ - // --------------------------------------------------------------------- - // Shared helpers - // --------------------------------------------------------------------- - - /// Seeds USFM content for a single book on the given ScrText. - private static void LoadUsfm(DummyScrText scrText, int bookNum, string usfm) - { - scrText.PutText(bookNum, 0, false, usfm, null); - } - - /// - /// Default heading marker set — mirrors what BHV-120 would return for the - /// shared (which defines s as the - /// only scSection+scParagraphStyle tag). - /// - private static HashSet BuildHeadingMarkers() => new() { "s", "s1", "s2", "s3", "mt" }; - - /// - /// Default non-heading paragraph marker set for the shared - /// (tags where TextType==scVerseText AND - /// StyleType==scParagraphStyle). - /// - private static HashSet BuildNonHeadingParagraphMarkers() => new() { "p", "nb" }; - - /// - /// Builds a paragraph-token list for a book by running the (already-green) - /// CAP-003 — this pre-filter - /// is what CAP-004 consumes in production. Tests that need to probe CAP-004 - /// in isolation can still construct - /// directly (see the range-filter tests below). - /// - private static List TokensFor( - DummyScrText scrText, - int bookNum, - HashSet? filter = null - ) - { - return ChecklistService.GetTokensForBook( - scrText, - bookNum, - filter ?? new HashSet { "p", "s", "nb" }, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - } - - /// - /// Counts TextItem content items nested inside all paragraphs across all - /// cells. Used by shape assertions that care about text-token coverage - /// without pinning exact wording (which is sensitive to post-processing - /// decisions owned elsewhere). - /// - private static int CountTextItems(IEnumerable cells) => - cells.SelectMany(c => c.Paragraphs).SelectMany(p => p.Items).OfType().Count(); - - private static int CountVerseItems(IEnumerable cells) => - cells.SelectMany(c => c.Paragraphs).SelectMany(p => p.Items).OfType().Count(); - - private static int CountEditLinkItems(IEnumerable cells) => - cells.SelectMany(c => c.Paragraphs).SelectMany(p => p.Items).OfType().Count(); - - /// - /// Flips the RTL flag on a live by setting - /// scrText.Language.RightToLeft (which writes through to the - /// underlying WritingSystemDefinition — - /// setter at ParatextData/Languages/ScrLanguage.cs:327-330). - /// - /// - /// Falls back to reflection on wsDef.RightToLeftScript if the - /// public setter is unavailable in the linked ParatextData version. - /// - private static void ForceRightToLeft(DummyScrText scrText) - { - try - { - scrText.Language.RightToLeft = true; - return; - } - catch (Exception) - { - // fall through to reflection - } - - var langObj = scrText.Language; - var wsDefField = - langObj - .GetType() - .GetField( - "wsDef", - BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public - ) - ?? langObj - .GetType() - .BaseType?.GetField( - "wsDef", - BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public - ); - if (wsDefField == null) - { - throw new InvalidOperationException( - "ScrLanguage.wsDef not found via reflection; RTL test helper must be updated." - ); - } - var wsDef = wsDefField.GetValue(langObj); - var rtlProp = wsDef!.GetType().GetProperty("RightToLeftScript"); - if (rtlProp == null || !rtlProp.CanWrite) - { - throw new InvalidOperationException( - "WritingSystemDefinition.RightToLeftScript not writable; RTL test helper must be updated." - ); - } - rtlProp.SetValue(wsDef, true); - } - - // ===================================================================== - // BHV-114 — Happy-path cell construction (TS-029 / gm-015 shape) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-029")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_Happy_EmitsCellsWithParagraphsAndItems() - { - // TS-029 / gm-015 shape: a single \p paragraph with \v 1 ... \v 2 ... - // should produce at least one cell with a paragraph containing verse - // and text content items. We deliberately assert the TOKEN-level shape - // (verse + text items present, paragraph marker set) and not the - // PostProcessParagraph artifact ("\\p" backslash-prefixed TextItem at - // index 0) because PostProcessParagraph placement is a CAP-006 - // orchestration decision per the plan file. - var scrText = new DummyScrText(); - const int BookNum = 2; // EXO - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one. \v 2 two. \v 3 three."); - - var paragraphs = TokensFor(scrText, BookNum); - var startRef = new VerseRef("EXO", "20", "1", scrText.Settings.Versification); - var endRef = new VerseRef("EXO", "20", "20", scrText.Settings.Versification); - - List cells = ChecklistService.GetCellsForBook( - scrText, - BookNum, - startRef, - endRef, - paragraphs - ); - - Assert.That(cells, Is.Not.Null); - Assert.That(cells, Is.Not.Empty, "at least one cell expected for the \\p paragraph"); - var firstCell = cells[0]; - Assert.That(firstCell.Paragraphs, Is.Not.Null); - Assert.That(firstCell.Paragraphs, Is.Not.Empty, "cell must contain at least one paragraph"); - Assert.That( - firstCell.Paragraphs[0].Marker, - Is.EqualTo("p"), - "paragraph marker recorded from UsfmTokenType.Paragraph token" - ); - Assert.That( - CountVerseItems(cells), - Is.GreaterThanOrEqualTo(3), - "three \\v tokens -> three VerseItems" - ); - Assert.That( - CountTextItems(cells), - Is.GreaterThanOrEqualTo(3), - "three verse-text tokens -> three TextItems (ignoring post-processing)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-029")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_TextTokens_ProduceTextItems() - { - // TS-029 slice: each UsfmTokenType.Text token becomes a TextItem - // carrying the token's text. We assert on a distinctive string so the - // test survives whitespace-trimming decisions (PT9 includes trailing - // spaces; PT10 may or may not preserve them). - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 distinctive-text-one \v 2 distinctive-text-two" - ); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - var allText = string.Concat( - result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Select(t => t.Text) - ); - Assert.That( - allText, - Does.Contain("distinctive-text-one"), - "first verse's text content must appear in a TextItem" - ); - Assert.That( - allText, - Does.Contain("distinctive-text-two"), - "second verse's text content must appear in a TextItem" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-029")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_VerseTokens_ProduceVerseItems() - { - // TS-029 slice: each UsfmTokenType.Verse token becomes a VerseItem - // whose VerseNumber is the verse number data (handles bridges like "4-6"). - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one. \v 4-6 bridged."); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - var verseNumbers = result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Select(v => v.VerseNumber) - .ToList(); - Assert.That(verseNumbers, Does.Contain("1")); - Assert.That(verseNumbers, Does.Contain("4-6"), "verse bridge must be preserved verbatim"); - } - - // ===================================================================== - // BHV-114 — Character style preservation (gm-016 token-level slice) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_TextInsideCharacterStyle_CarriesCharacterStyleMarker() - { - // BHV-114 PT9 line 307-309: text tokens record their active CharTag via - // `state.CharTag != null ? state.CharTag.Marker : ""`. The resulting - // TextItem's CharacterStyle must carry the character-style marker (e.g., - // "em") for downstream parenthesized-display formatting (BHV-604). - var scrText = new DummyScrText(); - const int BookNum = 2; - // Note: stick to markers present in DummyScrStylesheet (p, em) so the - // tokenizer recognises them. gm-016 uses \q2 which requires poetry styles - // — that's a CAP-006 orchestration concern, not a CAP-004 shape concern. - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 plain \em styled\em* after"); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - var styled = result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .FirstOrDefault(t => (t.Text ?? string.Empty).Contains("styled")); - Assert.That(styled, Is.Not.Null, "text inside \\em span must appear as a TextItem"); - Assert.That( - styled!.CharacterStyle, - Is.EqualTo("em"), - "TextItem.CharacterStyle must match the active CharTag.Marker (PT9 line 307-309)" - ); - } - - // ===================================================================== - // BHV-114 — Range filtering (TS-030) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-030")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_ParagraphsOutsideRange_Excluded() - { - // TS-030: paragraphs whose VerseRefStart is outside [startRef, endRef] - // are filtered out. We hand-construct two ChecklistParagraphTokens — - // one at EXO 20:1 (in range) and one at EXO 21:1 (out of range) — so - // the assertion targets GetCellsForBook's range check directly without - // depending on GetTokensForBook's own behavior. - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 in-range \c 21 \p \v 1 out-of-range"); - - var paragraphs = TokensFor(scrText, BookNum); - Assume.That( - paragraphs.Count, - Is.GreaterThanOrEqualTo(2), - "test precondition — two \\p paragraphs must be emitted" - ); - - var startRef = new VerseRef("EXO", "20", "1", scrText.Settings.Versification); - var endRef = new VerseRef("EXO", "20", "10", scrText.Settings.Versification); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - startRef, - endRef, - paragraphs - ); - - // None of the produced cells should carry text from chapter 21. - var allText = string.Concat( - result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Select(t => t.Text ?? string.Empty) - ); - Assert.That( - allText, - Does.Not.Contain("out-of-range"), - "paragraph at EXO 21:1 must be filtered out by the range check" - ); - Assert.That(allText, Does.Contain("in-range"), "paragraph at EXO 20:1 must remain"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-030")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_DefaultRangeBounds_IncludesAllParagraphs() - { - // TS-030 inverse: when both startRef/endRef are default (IsDefault==true), - // ChecklistParagraphTokens.ReferenceInRange returns true for every - // paragraph (short-circuit on IsDefault — BHV-119). All tokens must - // participate in cell output. - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 first-para-text \c 21 \p \v 1 second-para-text" - ); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), // IsDefault - new VerseRef(), - paragraphs - ); - - var allText = string.Concat( - result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Select(t => t.Text ?? string.Empty) - ); - Assert.That(allText, Does.Contain("first-para-text")); - Assert.That(allText, Does.Contain("second-para-text")); - } - - // ===================================================================== - // BHV-114 — Same-reference paragraph merge (PT9 AddContentToCurrentCell) - // gm-019-shaped behavior without the Verses-checklist post-processing. - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_DifferentReferences_ProduceDistinctCells() - { - // Different VerseRefs (\v 1 vs \v 2) at different paragraph starts -> - // two distinct cells (gm-015 shape: cells walk verse by verse). - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 first \p \v 2 second"); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - Assert.That( - result.Count, - Is.GreaterThanOrEqualTo(2), - "two paragraphs with different VerseRefs must yield at least two cells" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_SameReferenceParagraphs_MergedIntoOneCell() - { - // PT9 AddContentToCurrentCell (line 205-211): when the new cell's - // VerseRef equals the previous cell's VerseRef (CompareTo == 0), the - // new paragraphs are appended to the previous cell instead of creating - // a new one. This is BHV-114's merge behavior for duplicate-ref paragraphs. - // - // We construct the same-reference scenario by hand-crafting two - // ChecklistParagraphTokens with equal VerseRefStart values. (The USFM - // path can't easily produce two paragraphs at an identical VerseRef - // without relying on the Verses checklist's duplicate-verse shape.) - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 shared."); - - var realParagraphs = TokensFor(scrText, BookNum); - Assume.That( - realParagraphs, - Is.Not.Empty, - "test precondition — at least one paragraph token for \\p" - ); - var real = realParagraphs[0]; - - // Two synthetic paragraphs sharing VerseRefStart. - var duplicate = new ChecklistParagraphTokens( - VerseRefStart: real.VerseRefStart, - Marker: real.Marker, - IsHeading: real.IsHeading, - Tokens: real.Tokens - ); - var paragraphs = new List { real, duplicate }; - - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - Assert.That( - result.Count, - Is.EqualTo(1), - "two paragraphs sharing the same VerseRef must merge into one cell" - ); - Assert.That( - result[0].Paragraphs.Count, - Is.GreaterThanOrEqualTo(2), - "merged cell must contain both paragraphs" - ); - } - - // ===================================================================== - // BHV-114 — RTL marker prefix (TS-058) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-058")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_RtlScrText_PrefixesTextWithRtlMarker() - { - // TS-058 / PT9 line 307: `scrText.RightToLeft ? StringUtils.rtlMarker + token.Text : token.Text`. - // For an RTL-flagged ScrText, every TextItem's Text must begin with - // PtxUtils.StringUtils.rtlMarker (U+200F, the Unicode RTL mark). - var scrText = new DummyScrText(); - ForceRightToLeft(scrText); - Assume.That( - scrText.RightToLeft, - Is.True, - "precondition — ScrText.RightToLeft must be true after ForceRightToLeft" - ); - - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 rtl-content."); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - var textItems = result - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .OfType() - .Where(t => !string.IsNullOrEmpty(t.Text)) - .ToList(); - Assert.That( - textItems, - Is.Not.Empty, - "at least one TextItem must be produced for the verse text" - ); - foreach (var item in textItems) - { - Assert.That( - item.Text.StartsWith(StringUtils.rtlMarker.ToString()), - Is.True, - $"TextItem.Text must begin with StringUtils.rtlMarker when RTL; got: \"{item.Text}\"" - ); - } - } - - // ===================================================================== - // VAL-007 — Edit link NOT emitted at CAP-004 boundary - // (CAP-012 owns inline emission; TS-052 chapter-level is DEF-BE-001) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-050")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - public void GetCellsForBook_ProjectEditable_DoesNotEmitEditLinkItem() - { - // TS-050 at CAP-004's boundary: the strategic plan explicitly states - // "actual permission check is CAP-012 inline; CAP-004 just emits the - // cell structure". Therefore GetCellsForBook MUST NOT emit any - // EditLinkItem, even when the ScrText is editable. The cell structure - // it returns must simply be READY for CAP-012 to extend (Items list is - // a concrete List). - var scrText = new DummyScrText(); - scrText.Settings.Editable = true; - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one."); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - Assert.That( - CountEditLinkItems(result), - Is.EqualTo(0), - "CAP-004 must not emit EditLinkItem; CAP-012 owns inline emission" - ); - // Sanity — the cell structure must be ready for CAP-012 to append: - Assume.That(result, Is.Not.Empty, "precondition — at least one cell produced"); - Assert.That( - result[0].Paragraphs, - Is.Not.Empty, - "cell must carry paragraphs so CAP-012 can append an EditLinkItem" - ); - Assert.That( - result[0].Paragraphs[0].Items, - Is.Not.Null, - "paragraph Items must be a non-null list (CAP-012 appends to it)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-051")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - public void GetCellsForBook_ProjectNotEditable_DoesNotEmitEditLinkItem() - { - // TS-051 at CAP-004's boundary: regardless of Editable=false, CAP-004 - // must not emit an EditLinkItem. This pins the separation of concerns - // between CAP-004 (structure) and CAP-012 (gate). - // - // NOTE: PutText enforces Editable at write time (PtxUtils.SafetyCheckException - // "The project you are viewing is not editable"), so we must seed - // content BEFORE flipping Editable to false. The flag is read by - // GetCellsForBook (via scrText.Settings.Editable), not PutText. - var scrText = new DummyScrText(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one."); - scrText.Settings.Editable = false; - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - Assert.That( - CountEditLinkItems(result), - Is.EqualTo(0), - "CAP-004 must not emit EditLinkItem under any Editable setting" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("ScenarioId", "TS-052")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - [Property("DeferredUnder", "DEF-BE-001")] - public void GetCellsForBook_ChapterLevelCanEditDeferred_NoEditLinkEmitted() - { - // TS-052 is deferred under DEF-BE-001 (no platform CanEdit(bookNum, - // chapterNum) API). At CAP-004's boundary the observable contract is - // unchanged from TS-050/TS-051: no EditLinkItem is emitted here - // regardless of any hypothetical chapter-level predicate. This test - // pins the invariant so a future re-introduction of chapter-level - // CanEdit still lands in CAP-012, not CAP-004. - var scrText = new DummyScrText(); - scrText.Settings.Editable = true; // project-level editable — chapter-level is the deferred bit - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one."); - - var paragraphs = TokensFor(scrText, BookNum); - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - paragraphs - ); - - Assert.That( - CountEditLinkItems(result), - Is.EqualTo(0), - "CAP-004 must not implement chapter-level CanEdit (deferred under DEF-BE-001)" - ); - } - - // ===================================================================== - // Defensive — empty paragraph list - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-004")] - [Property("Contract", "GetCellsForBook")] - [Property("BehaviorId", "BHV-114")] - public void GetCellsForBook_EmptyParagraphList_ReturnsEmptyCellList() - { - // Defensive contract: an empty input produces an empty output without - // throwing. Callers may legitimately pass the empty list when no - // paragraphs pass the filter stage upstream (CAP-003). - var scrText = new DummyScrText(); - const int BookNum = 2; - - var result = ChecklistService.GetCellsForBook( - scrText, - BookNum, - new VerseRef(), - new VerseRef(), - new List() - ); - - Assert.That(result, Is.Not.Null); - Assert.That(result, Is.Empty); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistServiceEditLinkGatingTests.cs b/c-sharp-tests/Checklists/ChecklistServiceEditLinkGatingTests.cs deleted file mode 100644 index 12e28276e2b..00000000000 --- a/c-sharp-tests/Checklists/ChecklistServiceEditLinkGatingTests.cs +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.Projects; -using Paratext.Data; -using SIL.Scripture; -using ScriptureRange = Paranext.DataProvider.Scripture.ScriptureRange; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase focused unit tests for CAP-012 (Inline Edit-Link Permission Gating). -/// -/// -/// CAP-012 installs a small internal emission gate INSIDE -/// : when the project-level -/// condition scrText.Settings.Editable == true holds, every cell's -/// paragraph items receive an carrying the cell's -/// BookNum/ChapterNum/VerseNum. When Editable == false, -/// no is emitted anywhere in the result. -/// -/// -/// -/// Why these tests FAIL before GREEN. The current orchestrator (see -/// ChecklistService.cs inline comment near line 88: -/// "EditLinkItem is NOT emitted here — CAP-012 owns inline edit-link gating") -/// produces zero s. Tests 1 and 3 — which assert -/// presence when Editable=true — therefore fail on the RED cycle. -/// Test 2 (absence when Editable=false) is expected to PASS trivially -/// before GREEN, but becomes a meaningful regression guard once the gate is -/// wired: it keeps the implementer honest that the gate is a GATE, not an -/// unconditional emission. We keep it in the suite deliberately. -/// -/// -/// -/// Scope: project-level only. TS-052 (chapter-level -/// ScrText.Permissions.CanEdit(bookNum, chapterNum)) is -/// deferred per DEF-BE-001 and is kept here as an [Ignore] -/// placeholder so the traceability matrix still records VAL-007 cond 5. -/// -/// -/// -/// TDD variant: Classic. Small internal emission decision — unit tests -/// drive out the minimal gate. Golden-master coverage for -/// shape lives in gm-015 / gm-019 under CAP-006's existing orchestration tests; -/// no separate golden-master replay is added here. -/// -/// -/// Traceability: -/// - Capability: CAP-012 -/// - Behaviors: BHV-114 (emission sub-behavior of cell construction) -/// - Extractions: EXT-016 (project-level portion only; chapter-level DEFERRED) -/// - Invariants: VAL-007 (project-level subset — conds 1-4) -/// - Scenarios: TS-050 (emission when conditions met), TS-051 (no emission -/// when Editable=false), TS-052 (DEFERRED — DEF-BE-001 placeholder) -/// - Deferred: DEF-BE-001 (chapter-level CanEdit) -/// - Contract: data-contracts.md §3.3 (ChecklistCell no longer carries a -/// separate edit-link field — presence is signalled by EditLinkItem in -/// paragraph items), §3.5 (EditLinkItem shape), §4.1 (inline gate -/// embedded in BuildChecklistData) -/// - PT9 source: Paratext/Checklists/ChecklistsTool.cs SetCellEditability -/// (project-level portion only — chapter-level CanEdit deferred) -/// -[TestFixture] -internal class ChecklistServiceEditLinkGatingTests : PapiTestBase -{ - // --------------------------------------------------------------------- - // Shared helpers — mirror the CAP-006 test file's RegisterDummyProject / - // BuildRequest / stylesheet-upgrade pattern so the two suites stay in - // sync on DummyScrText wiring. - // --------------------------------------------------------------------- - - /// - /// Canonical EXO USFM from gm-001 — single project, two verses, three - /// paragraph markers (\p, \q, \q2). Matches the - /// ChecklistServiceBuildChecklistDataTests.Gm001ExoUsfm constant; - /// duplicated here (rather than lifted to a shared helper) so the CAP-012 - /// tests stand alone when run in isolation. - /// - private const string Gm001ExoUsfm = - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented poetry"; - - private DummyScrText RegisterDummyProject(string usfmPerBook, int bookNum = 2) - { - var scrText = new DummyScrText(); - UpgradePoetryMarkersToParagraphStyle(scrText); - scrText.PutText(bookNum, 0, false, usfmPerBook, null); - ParatextProjects.FakeAddProject(CreateProjectDetails(scrText), scrText); - return scrText; - } - - private static void UpgradePoetryMarkersToParagraphStyle(DummyScrText scrText) - { - var stylesheet = scrText.DefaultStylesheet; - foreach (var marker in new[] { "q", "q1", "q2", "b" }) - AddPoetryTag(stylesheet, marker); - } - - private static void AddPoetryTag(ScrStylesheet stylesheet, string marker) - { - var tag = new ScrTag - { - Marker = marker, - TextProperties = - TextProperties.scParagraph - | TextProperties.scPublishable - | TextProperties.scVernacular - | TextProperties.scPoetic, - TextType = ScrTextType.scVerseText, - StyleType = ScrStyleType.scParagraphStyle, - OccursUnder = "c", - }; - - var addTagInternal = typeof(ScrStylesheet).GetMethod( - "AddTagInternal", - BindingFlags.Instance | BindingFlags.NonPublic - ); - if (addTagInternal == null) - { - throw new InvalidOperationException( - "ScrStylesheet.AddTagInternal not found via reflection; " - + "API has changed and this test helper must be updated." - ); - } - addTagInternal.Invoke(stylesheet, new object[] { tag }); - } - - private static ChecklistRequest BuildRequest(string activeProjectId) - { - var verseRange = new ScriptureRange( - new VerseRef("EXO", "20", "1", ScrVers.English), - new VerseRef("EXO", "20", "20", ScrVers.English) - ); - - return new ChecklistRequest( - ProjectId: activeProjectId, - ComparativeTextIds: Array.Empty(), - MarkerSettings: new MarkerSettings(string.Empty, string.Empty), - VerseRange: verseRange, - HideMatches: false, - ShowVerseText: false - ); - } - - /// - /// Flattens every across every row / - /// cell / paragraph of the result so tests can scan for presence / absence - /// of an . - /// - private static IReadOnlyList AllContentItems(ChecklistResult result) => - result - .Rows.SelectMany(r => r.Cells) - .SelectMany(c => c.Paragraphs) - .SelectMany(p => p.Items) - .ToList(); - - // ===================================================================== - // Group A — Happy path (TS-050): Editable=true emits EditLinkItem(s) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-012")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-050")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - public void BuildChecklistData_ProjectEditable_EmitsEditLinkItem() - { - // TS-050 / VAL-007 (project-level subset): when scrText.Settings.Editable - // is true and the cell-shape predicates hold (row has cells, first cell - // has non-default VerseRef), BuildChecklistData must emit an - // EditLinkItem inside the cell's paragraph items. - // - // DummyScrText defaults Settings.Editable=true but we set it explicitly - // so the intent of the test is self-documenting. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - scrText.Settings.Editable = true; - - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That(result.Rows, Is.Not.Empty, "precondition — Gm001ExoUsfm produces rows"); - - var editLinks = AllContentItems(result).OfType().ToList(); - Assert.That( - editLinks, - Is.Not.Empty, - "TS-050 / VAL-007 — project editable=true MUST emit at least one EditLinkItem" - ); - } - - // ===================================================================== - // Group B — Error path (TS-051): Editable=false suppresses EditLinkItem - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-012")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-051")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - public void BuildChecklistData_ProjectNotEditable_EmitsNoEditLinkItems() - { - // TS-051: when scrText.Settings.Editable is false, no EditLinkItem - // anywhere in the result — regardless of how many rows / cells / - // paragraphs are produced. This is the gate's suppression branch. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - scrText.Settings.Editable = false; - - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That( - result.Rows, - Is.Not.Empty, - "precondition — Gm001ExoUsfm still produces rows even when non-editable" - ); - - var editLinks = AllContentItems(result).OfType().ToList(); - Assert.That( - editLinks, - Is.Empty, - "TS-051 / VAL-007 — project editable=false MUST suppress every EditLinkItem" - ); - } - - // ===================================================================== - // Group C — Shape verification: EditLinkItem carries the cell's BBB/CCC/VVV - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-012")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-050")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - public void BuildChecklistData_ProjectEditable_EditLinkItemsCarryCellVerseRef() - { - // Shape verification for TS-050: every EditLinkItem emitted must - // target the same book/chapter/verse as the cell it lives in. We read - // the expected BookNum/ChapterNum/VerseNum directly off the cell's - // structured Reference (a ScriptureRange — see ChecklistService.cs - // BuildCLCell). - // - // This is a behavior-not-implementation check: we don't pin HOW the - // gate derives the numbers; we only pin that whatever it derives agrees - // with the cell's own Reference. - var scrText = RegisterDummyProject(Gm001ExoUsfm); - scrText.Settings.Editable = true; - - var request = BuildRequest(activeProjectId: scrText.Guid.ToString()); - - ChecklistResult result = ChecklistService.BuildChecklistData( - request, - CancellationToken.None - ); - - Assume.That(result.Rows, Is.Not.Empty, "precondition — result has rows"); - - int cellsChecked = 0; - foreach (var row in result.Rows) - foreach (var cell in row.Cells) - { - // Read the expected BookNum/ChapterNum/VerseNum from the cell's - // Reference. A null Reference = default verse -> skip (VAL-007 - // cell-shape predicate would itself block emission for a default - // verse, so there's nothing to assert on such a cell). - if (cell.Reference is null) - continue; - - VerseRef expected = cell.Reference.Start; - - var cellEditLinks = cell - .Paragraphs.SelectMany(p => p.Items) - .OfType() - .ToList(); - - // If the gate emitted any EditLinkItems for this cell (it should, - // because Editable=true and the cell has a non-default VerseRef - // per VAL-007 cond 1-4), each one must target this cell's ref. - foreach (var link in cellEditLinks) - { - Assert.That( - link.BookNum, - Is.EqualTo(expected.BookNum), - $"EditLinkItem.BookNum must match cell reference {expected}" - ); - Assert.That( - link.ChapterNum, - Is.EqualTo(expected.ChapterNum), - $"EditLinkItem.ChapterNum must match cell reference {expected}" - ); - Assert.That( - link.VerseNum, - Is.EqualTo(expected.VerseNum), - $"EditLinkItem.VerseNum must match cell reference {expected}" - ); - cellsChecked++; - } - } - - Assert.That( - cellsChecked, - Is.GreaterThan(0), - "shape test precondition — Editable=true should produce at least one EditLinkItem to shape-check. " - + "If this assertion fails, either TS-050 isn't wired yet (RED state) or the gate emitted no links on a qualifying cell." - ); - } - - // ===================================================================== - // Group D — DEFERRED per DEF-BE-001 (TS-052): chapter-level CanEdit - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-012")] - [Property("Contract", "BuildChecklistData")] - [Property("ScenarioId", "TS-052")] - [Property("BehaviorId", "BHV-114")] - [Property("ValidationRule", "VAL-007")] - [Property("Deferred", "DEF-BE-001")] - [Ignore( - "DEF-BE-001: Chapter-level ScrText.Permissions.CanEdit(bookNum, chapterNum) " - + "is DEFERRED for PT10 MVP. paranext-core does not yet expose a platform-wide " - + "CanEdit(bookNum, chapterNum) API; the inline gate therefore only honours the " - + "project-level scrText.Settings.Editable check. See " - + "implementation/deferred-functionality.md §DEF-BE-001 for the revisit trigger. " - + "This placeholder preserves TS-052 traceability so the matrix records " - + "VAL-007 cond 5 even though it's not implemented." - )] - public void BuildChecklistData_PerChapterPermissionDenied_SuppressesEditLinkItem_DEFERRED() - { - // DEFERRED per DEF-BE-001. When the trigger API (platform-wide - // CanEdit(bookNum, chapterNum) equivalent) lands, remove the [Ignore] - // and implement the scenario: a project where Settings.Editable=true - // but the user lacks CanEdit on a specific chapter should produce NO - // EditLinkItem for rows in that chapter (and EditLinkItems as normal - // for other chapters). - Assert.Pass("placeholder — see [Ignore] rationale (DEF-BE-001)"); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistServiceResolveComparativeTextsTests.cs b/c-sharp-tests/Checklists/ChecklistServiceResolveComparativeTextsTests.cs deleted file mode 100644 index 114aca7412f..00000000000 --- a/c-sharp-tests/Checklists/ChecklistServiceResolveComparativeTextsTests.cs +++ /dev/null @@ -1,519 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Paranext.DataProvider.Checklists; -using Paratext.Data; -using PtxUtils; - -namespace TestParanextDataProvider.Checklists; - -/// -/// Contract and outer-acceptance tests for CAP-009 -/// (ChecklistService.ResolveComparativeTexts — GUID-only resolution -/// with active-project exclusion). -/// -/// -/// Per strategic-plan-backend.md §CAP-009, this capability uses -/// Outside-In TDD: the outer acceptance test -/// () -/// drives the full INV-014 contract; focused tests pin individual cases -/// (TS-048 / PTX-23529 duplicate short names resolved by GUID, -/// active-project exclusion). -/// -/// -/// -/// Real-infrastructure note. Tests register -/// instances into the shared via -/// DummyLocalParatextProjects.FakeAddProject — the SAME collection the -/// production LocalParatextProjects.GetParatextProject and the -/// ScrTextCollection.FindById API consult. This is the established -/// real-infrastructure pattern for this codebase (see CAP-006 tests for -/// precedent); it directly exercises the production resolution APIs without -/// on-disk USFM / Settings.xml scaffolding. An end-to-end pass against real -/// projects is covered by P3B.7 smoke tests. -/// -/// -/// -/// Signature note. data-contracts.md §4.5 lists the async shape -/// Task<ResolvedComparativeTexts> ResolveComparativeTextsAsync(...); -/// strategic-plan-backend.md §CAP-009 lists the synchronous -/// ResolvedComparativeTexts ResolveComparativeTexts(...). These tests -/// follow the strategic-plan signature (matching the CAP-006 precedent). -/// -/// -/// -/// Resolution history. An earlier shape paired Id with a Name field so -/// PT9's name-fallback resolver could be carried forward. PT10 is greenfield — -/// every project has a canonical GUID — so the Name fallback was dropped (PR -/// #2254 review, TJ Couch). Tests that exercised the name-fallback path -/// (legacy TS-047 / name-based self-exclusion) were removed; the structural -/// guarantees (GUID-first, order preservation, self-exclusion, retain-with- -/// unavailable) are still covered. -/// -/// -/// Traceability: -/// - Capability: CAP-009 -/// - Behaviors: BHV-310 (Comparative Texts button — backend resolution slice), -/// BHV-605 (settings-restoration resolution — primary) -/// - Invariants: INV-014 (GUID resolution + self-exclusion) -/// - Scenarios: TS-048 / PTX-23529 (duplicate short names resolved by GUID) -/// - Contract: data-contracts.md §2.4 (ComparativeTextRef), -/// §3.10 (ResolvedComparativeText), §3.11 (ResolvedComparativeTexts), -/// §4.5 (ResolveComparativeTexts) -/// - PT9 source: Paratext/Checklists/ChecklistsTool.cs:132-148 (Initialize -/// comparative-text resolution slice) -/// -[TestFixture] -internal class ChecklistServiceResolveComparativeTextsTests : PapiTestBase -{ - // --------------------------------------------------------------------- - // Shared helpers — reuse DummyScrText + FakeAddProject pattern. - // --------------------------------------------------------------------- - - /// - /// Registers a with a caller-chosen short-name - /// (via ) into the shared - /// so ScrTextCollection.FindById - /// can resolve it. - /// - private DummyScrText RegisterProject(string shortName) - { - var details = CreateProjectDetails(HexId.CreateNew().ToString(), shortName); - var scrText = new DummyScrText(details); - ParatextProjects.FakeAddProject(details, scrText); - return scrText; - } - - // ===================================================================== - // Group A — Outer Acceptance (Outside-In) - // The "done signal" — when this passes, the full INV-014 contract - // holds for GUID resolution, order preservation, and self-exclusion. - // ===================================================================== - - [Test] - [Category("Integration")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_MixedAvailability_PreservesOrderAndCorrectlyFlagsAvailability() - { - // Arrange: register 3 projects in the shared ScrTextCollection. - // - active : the "active" project (self-reference target) - // - alpha : GUID-resolvable comparative - // - charlie : NOT registered (unresolvable; entry retained with Available=false) - // - // Active project is intentionally registered LAST to rule out any - // ordering bias in the implementation. - DummyScrText alpha = RegisterProject("ALPHA"); - // charlie is never registered — acts as the unresolvable entry. - string charlieMissingGuid = HexId.CreateNew().ToString(); - DummyScrText active = RegisterProject("ACTIVE"); - - var requestedTexts = new List - { - // (1) ALPHA — valid GUID; should resolve via FindById. - new(alpha.Guid.ToString()), - // (2) ACTIVE — GUID matches the active project; MUST be excluded. - new(active.Guid.ToString()), - // (3) CHARLIE — GUID does not resolve; Available=false. - new(charlieMissingGuid), - }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert — INV-014 composite invariant holds. - Assert.That(result, Is.Not.Null); - Assert.That(result.Texts, Is.Not.Null); - - // Active project entry dropped entirely; remaining 2 preserve input order. - Assert.That( - result.Texts.Count, - Is.EqualTo(2), - "INV-014 — active project must be excluded; unresolvable entries remain with Available=false" - ); - - // (1) ALPHA — resolved by GUID. - ResolvedComparativeText r0 = result.Texts[0]; - Assert.That(r0.Id, Is.EqualTo(alpha.Guid.ToString()), "entry 0 Id preserved"); - Assert.That(r0.Available, Is.True, "ALPHA must be Available (GUID resolved)"); - Assert.That(r0.Name, Is.EqualTo(alpha.Name), "ALPHA Name mirrors the resolved ScrText"); - - // (2) CHARLIE — GUID does not resolve. - ResolvedComparativeText r1 = result.Texts[1]; - Assert.That(r1.Available, Is.False, "CHARLIE cannot be resolved → Available=false"); - Assert.That( - r1.Id, - Is.EqualTo(charlieMissingGuid), - "CHARLIE Id preserved verbatim (no silent rewrite)" - ); - Assert.That( - r1.Name, - Is.EqualTo(string.Empty), - "CHARLIE Name is empty when GUID does not resolve (no source of truth)" - ); - } - - // ===================================================================== - // Group B — GUID resolution (INV-014 "GUID-first") - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_ValidGuid_ResolvesByFindById() - { - // Arrange - DummyScrText active = RegisterProject("ACTIVE_P"); - DummyScrText target = RegisterProject("TARGET_P"); - - var requestedTexts = new List { new(target.Guid.ToString()) }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert - Assert.That(result.Texts.Count, Is.EqualTo(1)); - ResolvedComparativeText entry = result.Texts[0]; - Assert.That(entry.Available, Is.True); - Assert.That(entry.Id, Is.EqualTo(target.Guid.ToString())); - Assert.That(entry.Name, Is.EqualTo(target.Name)); - // FullName mirrors the resolved ScrText's FullName (data-contracts.md §3.10 - // — FullName = human-readable project full name). Observe, don't pin a - // hard-coded value; the DummyScrText's FullName is populated by its - // ProjectSettings ctor to "Test ScrText" but we compare against the - // ScrText itself to remain observational. - Assert.That( - entry.FullName, - Is.EqualTo(target.FullName), - "FullName must mirror the resolved ScrText.FullName (data-contracts.md §3.10)" - ); - } - - // ===================================================================== - // Group C — Unresolvable GUID (data-contracts.md §3.11 — retain with Available=false) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_UnresolvableGuid_MarkedUnavailable() - { - // Per data-contracts.md §3.11 validation rule: "Unresolvable entries - // appear with available=false rather than being omitted." - DummyScrText active = RegisterProject("ACTIVE_R"); - - string missingGuid = HexId.CreateNew().ToString(); - var requestedTexts = new List { new(missingGuid) }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert - Assert.That(result.Texts.Count, Is.EqualTo(1), "unresolvable entry retained"); - ResolvedComparativeText entry = result.Texts[0]; - Assert.That(entry.Available, Is.False); - Assert.That(entry.Id, Is.EqualTo(missingGuid)); - Assert.That(entry.Name, Is.EqualTo(string.Empty)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_MalformedGuid_MarkedUnavailable() - { - // HexId.FromStrSafe returns null on malformed input. The resolver must - // treat that the same as a well-formed-but-not-found GUID: retained - // with Available=false, no exception. - DummyScrText active = RegisterProject("ACTIVE_M"); - - const string malformedGuid = "not-a-valid-hex-id"; - var requestedTexts = new List { new(malformedGuid) }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert - Assert.That(result.Texts.Count, Is.EqualTo(1), "malformed-GUID entry retained"); - ResolvedComparativeText entry = result.Texts[0]; - Assert.That(entry.Available, Is.False); - Assert.That( - entry.Id, - Is.EqualTo(malformedGuid), - "Id preserved verbatim (no silent rewrite, even for malformed input)" - ); - } - - // ===================================================================== - // Group D — Self-exclusion (INV-014 "active project excluded") - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_ComparativeRefIsActiveProjectGuid_Excluded() - { - // INV-014: active project is excluded from the resolved list. PT9 - // pattern: `Where(p => p != null && p != scrText).ToList()`. - DummyScrText active = RegisterProject("ACTIVE_S"); - DummyScrText other = RegisterProject("OTHER_S"); - - var requestedTexts = new List - { - // Active project referenced by its GUID — MUST be filtered out. - new(active.Guid.ToString()), - // A real comparative target so we can assert the result length. - new(other.Guid.ToString()), - }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert — only OTHER_S survives. - Assert.That( - result.Texts.Count, - Is.EqualTo(1), - "INV-014 — active-project self-reference must be excluded from results" - ); - Assert.That( - result.Texts.Any(t => t.Id == active.Guid.ToString()), - Is.False, - "no result entry may carry the active project's GUID (INV-014)" - ); - Assert.That(result.Texts[0].Id, Is.EqualTo(other.Guid.ToString())); - } - - // ===================================================================== - // Group E — Duplicate short names (TS-048 / PTX-23529) - // GUID-based resolution disambiguates projects that happen to share - // the same short name. - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("ScenarioId", "TS-048")] - [Property("BehaviorId", "BHV-605")] - [Property("Invariant", "INV-014")] - public void ResolveComparativeTexts_DuplicateShortName_ResolvedByGuid() - { - // TS-048 / PTX-23529: two registered projects share the same short - // name ("CEVUK" in the source scenario). A comparative-text request - // carrying a specific GUID must resolve to THAT SPECIFIC GUID's - // project, not to whichever one a short-name lookup happens to return. - const string sharedShortName = "CEVUK"; - DummyScrText active = RegisterProject("ACTIVE_U"); - DummyScrText projectCevuk = RegisterProject(sharedShortName); - DummyScrText resourceCevuk = RegisterProject(sharedShortName); - - // Precondition sanity: both registered projects share the same - // ShortName prefix — this is the PTX-23529 scenario. - Assume.That( - projectCevuk.Name.StartsWith(sharedShortName, StringComparison.Ordinal), - "precondition — project CEVUK stored Name starts with the shared short name" - ); - Assume.That( - resourceCevuk.Name.StartsWith(sharedShortName, StringComparison.Ordinal), - "precondition — resource CEVUK stored Name starts with the shared short name" - ); - - // The request targets the RESOURCE by GUID. - var requestedTexts = new List { new(resourceCevuk.Guid.ToString()) }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert — resolution returned the RESOURCE CEVUK (by GUID), - // not whichever project happens to come up first on short-name lookup. - Assert.That(result.Texts.Count, Is.EqualTo(1)); - ResolvedComparativeText entry = result.Texts[0]; - Assert.That( - entry.Available, - Is.True, - "TS-048 — GUID-based resolution must succeed even with duplicate short names" - ); - Assert.That( - entry.Name, - Is.EqualTo(resourceCevuk.Name), - "TS-048 — resolved Name mirrors the GUID-targeted ScrText, not the other same-short-name entry" - ); - Assert.That( - entry.FullName, - Is.EqualTo(resourceCevuk.FullName), - "TS-048 — resolved FullName mirrors the GUID-targeted ScrText" - ); - } - - // ===================================================================== - // Group F — Input order preservation (data-contracts.md §3.11 validation rule) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - public void ResolveComparativeTexts_PreservesInputOrder() - { - // Data-contracts.md §3.11: "Texts preserves the order of the input - // requestedTexts argument (minus the active project)." - DummyScrText active = RegisterProject("ACTIVE_V"); - DummyScrText projA = RegisterProject("AAAA"); - DummyScrText projB = RegisterProject("BBBB"); - DummyScrText projC = RegisterProject("CCCC"); - - // Deliberately request them in REVERSE alphabetic order so an - // alphabetic-sort implementation bug would surface. - var requestedTexts = new List - { - new(projC.Guid.ToString()), - new(projA.Guid.ToString()), - new(projB.Guid.ToString()), - }; - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - - // Assert — order preserved exactly. - Assert.That(result.Texts.Count, Is.EqualTo(3)); - Assert.That(result.Texts[0].Id, Is.EqualTo(projC.Guid.ToString())); - Assert.That(result.Texts[1].Id, Is.EqualTo(projA.Guid.ToString())); - Assert.That(result.Texts[2].Id, Is.EqualTo(projB.Guid.ToString())); - } - - // ===================================================================== - // Group G — Edge cases - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - public void ResolveComparativeTexts_EmptyRequest_ReturnsEmptyList() - { - DummyScrText active = RegisterProject("ACTIVE_W"); - - // Act - ResolvedComparativeTexts result = ChecklistService.ResolveComparativeTexts( - activeProjectId: active.Guid.ToString(), - requestedTexts: Array.Empty(), - ct: CancellationToken.None - ); - - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result.Texts, Is.Not.Null); - Assert.That(result.Texts.Count, Is.EqualTo(0)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-009")] - [Property("Contract", "ResolveComparativeTexts")] - [Property("BehaviorId", "BHV-605")] - public void ResolveComparativeTexts_ActiveProjectIdNotFound_ThrowsStructuredError() - { - // Per data-contracts.md §4.5 Error Conditions: - // "Active project ID does not resolve → PROJECT_NOT_FOUND" - // - // This test pins the OBSERVABLE fact that an unregistered active - // project ID does not silently return an (arbitrary) empty result — - // it surfaces an error. The specific exception type is left to GREEN - // (data-contracts.md names an error CODE, not a specific exception - // class), matching the CAP-006 precedent for analogous error paths - // (see BuildChecklistData TS-070 treatment). - string unregisteredActiveProjectId = HexId.CreateNew().ToString(); - var requestedTexts = new List { new(HexId.CreateNew().ToString()) }; - - // Act + Assert — the method throws. Test does NOT pin exception type - // or message (would be implementation-mirroring); it pins the - // observable behavior that resolution fails loudly. - // - // False-green guard: explicitly exclude NotImplementedException so - // the RED stub (which throws NIE) cannot satisfy this assertion — - // same tightening applied to CAP-006's equivalent error-path test - // (see git commit 90facbea0e false-green audit note). - // - // Pattern: catch-then-assert (matches CAP-006 - // BuildChecklistData_ProjectIdNotRegistered_SurfacesResolutionError). - // An earlier revision used the fluent form - // `Throws.Exception.And.Not.InstanceOf()` but that NUnit 4.x - // fluent composition throws "Stack empty" at constraint-resolve time - // when a non-NIE exception is actually thrown — so we fall back to - // the canonical try/catch + two Assert.That calls. - Exception? caught = null; - try - { - ChecklistService.ResolveComparativeTexts( - activeProjectId: unregisteredActiveProjectId, - requestedTexts: requestedTexts, - ct: CancellationToken.None - ); - } - catch (Exception ex) - { - caught = ex; - } - - Assert.That( - caught, - Is.Not.Null, - "§4.5 Error Conditions — missing active project must surface as an error" - ); - Assert.That( - caught, - Is.Not.InstanceOf(), - "§4.5 Error Conditions — NotImplementedException is a RED-stub artifact, not the expected resolution error" - ); - Assert.That( - caught!.Message, - Does.Contain(unregisteredActiveProjectId), - "§4.5 Error Conditions — the exception message must reference the invalid " - + "activeProjectId so the failure is self-diagnosing." - ); - } -} diff --git a/c-sharp-tests/Checklists/ChecklistServiceTokenExtractionTests.cs b/c-sharp-tests/Checklists/ChecklistServiceTokenExtractionTests.cs deleted file mode 100644 index d9175cffe7e..00000000000 --- a/c-sharp-tests/Checklists/ChecklistServiceTokenExtractionTests.cs +++ /dev/null @@ -1,611 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Paranext.DataProvider.Checklists; -using Paratext.Data; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists; - -/// -/// RED-phase contract tests for CAP-003 (USFM Token Extraction). -/// -/// -/// These tests will NOT compile until the implementer creates -/// Paranext.DataProvider.Checklists.ChecklistService.GetTokensForBook and the -/// ChecklistParagraphTokens helper record. That is intentional: the test file -/// IS the specification — the compile error is the first layer of the RED signal; the -/// test assertion failures are the second. (Matches the CAP-001/CAP-002/CAP-007 -/// precedents — see ChecklistDataModelTests.cs:10-17 and -/// MarkersDataSourceTests.cs:9-20.) -/// -/// -/// -/// Scope: the single public extraction method and its helper record. Downstream -/// orchestration (cell construction, row alignment, the full pipeline shape tested by -/// gm-009 / gm-010 / gm-016) lives under CAP-004 and CAP-006. The revised CAP-003 -/// success criteria (strategic-plan-backend.md §CAP-003, 2026-04-13) explicitly -/// delegate orchestration-level verification to CAP-006; this file asserts the -/// token-level postconditions directly on List<ChecklistParagraphTokens>. -/// -/// -/// Traceability: -/// - Capability: CAP-003 -/// - Behaviors: BHV-108 (primary), BHV-119 (transitive), BHV-120 (transitive) -/// - Extractions: EXT-008 (method), EXT-012 (helper record) -/// - Invariants: INV-009 (heading verse reference assignment) -/// - Contract: data-contracts.md §4.1 (within BuildChecklistData) -/// - PT9 source: Paratext/Checklists/CLParagraphCellsDataSource.cs:50-135 -/// -[TestFixture] -internal class ChecklistServiceTokenExtractionTests -{ - // --------------------------------------------------------------------- - // Shared helpers - // --------------------------------------------------------------------- - - /// - /// A subclass that adds \q, \q1, \q2, \b as - /// paragraph markers with scVerseText + scParagraphStyle. Required by - /// test USFM taken from gm-009 / gm-016 which use poetry styles. - /// - /// - /// Uses reflection on the protected AddTagInternal method because the - /// PT9 API does not expose a public single-tag - /// additive entry point and the paranext-core - /// does not include AddPoetryStyles. Keeping this inside the test file - /// avoids a cross-capability change to shared infrastructure. - /// - private sealed class PoetryStylesheet : DummyScrStylesheet - { - public PoetryStylesheet() - { - AddPoetryTag("q"); - AddPoetryTag("q1"); - AddPoetryTag("q2"); - AddPoetryTag("b"); - } - - private void AddPoetryTag(string marker) - { - var tag = new ScrTag - { - Marker = marker, - TextProperties = - TextProperties.scParagraph - | TextProperties.scPublishable - | TextProperties.scVernacular - | TextProperties.scPoetic, - TextType = ScrTextType.scVerseText, - StyleType = ScrStyleType.scParagraphStyle, - OccursUnder = "c", - }; - - var addTagInternal = typeof(ScrStylesheet).GetMethod( - "AddTagInternal", - BindingFlags.Instance | BindingFlags.NonPublic - ); - if (addTagInternal == null) - { - throw new InvalidOperationException( - "ScrStylesheet.AddTagInternal not found via reflection; " - + "API has changed and this test helper must be updated." - ); - } - addTagInternal.Invoke(this, new object[] { tag }); - } - } - - /// - /// Builds a whose default stylesheet includes poetry - /// paragraph markers. Required for tests whose USFM contains \q / \q2. - /// - private static DummyScrText CreatePoetryProject() - { - var scrText = new DummyScrText(); - // Replace the cached default stylesheet with our poetry-aware variant. - // DummyScrText sets this in its constructor via cachedDefaultStylesheet.Set(...), - // but uses private reflection on protected internals of ScrText. We rely on - // the fact that DummyScrText's construction path already populates a - // stylesheet, and we need a different one for poetry. The public - // ScrStylesheet(...) override path is used via reflection. - var cachedFieldDefault = typeof(ScrText).GetField( - "cachedDefaultStylesheet", - BindingFlags.Instance | BindingFlags.NonPublic - ); - var cachedFieldFrontBack = typeof(ScrText).GetField( - "cachedFrontBackStylesheet", - BindingFlags.Instance | BindingFlags.NonPublic - ); - if (cachedFieldDefault == null || cachedFieldFrontBack == null) - { - throw new InvalidOperationException( - "ScrText.cachedDefaultStylesheet / cachedFrontBackStylesheet fields " - + "not found via reflection; API has changed." - ); - } - - var cachedDefault = cachedFieldDefault.GetValue(scrText); - var cachedFrontBack = cachedFieldFrontBack.GetValue(scrText); - var setMethod = cachedDefault! - .GetType() - .GetMethod("Set", BindingFlags.Instance | BindingFlags.Public); - if (setMethod == null) - { - throw new InvalidOperationException( - "Cached.Set not found via reflection; API has changed." - ); - } - - var poetry = new PoetryStylesheet(); - setMethod.Invoke(cachedDefault, new object[] { poetry }); - setMethod.Invoke(cachedFrontBack, new object[] { poetry }); - return scrText; - } - - /// Seeds USFM content for a single book on the given ScrText. - private static void LoadUsfm(DummyScrText scrText, int bookNum, string usfm) - { - scrText.PutText(bookNum, 0, false, usfm, null); - } - - /// - /// Default heading markers set derived from a stylesheet's - /// scSection+scParagraphStyle tags. We compute this locally rather than going - /// through BHV-120's HeadingMarkers() to keep token-extraction tests - /// independent of CAP-002's leaf logic — the capability under test consumes - /// the set passed in by its caller. - /// - private static HashSet BuildHeadingMarkers() - { - // DummyScrStylesheet defines 's' as the only scSection+scParagraphStyle tag. - return new HashSet { "s", "s1", "s2", "s3", "mt" }; - } - - private static HashSet BuildNonHeadingParagraphMarkers() - { - // Verse-text paragraph styles from DummyScrStylesheet + PoetryStylesheet. - return new HashSet { "p", "nb", "q", "q1", "q2", "b", "m" }; - } - - // ===================================================================== - // BHV-108 — GetTokensForBook (primary happy-path + filter) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("ScenarioId", "TS-023")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_Happy_CollectsParagraphTokensAndSkipsNotes() - { - // TS-023 (first half): a \p paragraph containing a \f...\f* footnote is - // emitted as a single CLParagraphTokens entry whose Tokens do NOT include - // any note content. The footnote body ("A footnote.") must be absent. - var scrText = CreatePoetryProject(); - const int BookNum = 2; // EXO - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 one.\f + \fr 20:1 \ft A footnote.\f* More text. \v 2 two," - ); - - var filter = new HashSet { "p", "q", "q2" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - Assert.That(result, Is.Not.Null); - Assert.That( - result.Count, - Is.EqualTo(1), - "exactly one paragraph entry expected for the single \\p marker" - ); - var para = result[0]; - Assert.That(para.Marker, Is.EqualTo("p")); - Assert.That(para.IsHeading, Is.False); - - // The footnote content must not appear in any of the collected tokens. - foreach (var tok in para.Tokens) - { - Assert.That( - tok.Text ?? string.Empty, - Does.Not.Contain("A footnote"), - "note body must be skipped (state.NoteTag != null branch)" - ); - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("ScenarioId", "TS-023")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_Happy_CollectsParagraphTokensAndSkipsFigures() - { - // TS-023 (second half): a \fig ... \fig* figure inside a paragraph is - // stripped from the collected tokens. Same USFM shape as gm-009 so the - // test also serves as a token-level gm-009 slice. - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 one. More text. \v 2 two, \q poetry \fig desc|file.jpg\fig* more" - ); - - var filter = new HashSet { "p", "q" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - // The \fig and its closing \fig* are character tokens with CharTag.Marker == "fig" - // and are skipped entirely, so the figure description and filename must - // not appear in any collected token's text. - var allText = string.Concat( - result.SelectMany(p => p.Tokens.Select(t => t.Text ?? string.Empty)) - ); - Assert.That(allText, Does.Not.Contain("file.jpg"), "figure metadata must be skipped"); - Assert.That(allText, Does.Not.Contain("desc"), "figure description must be skipped"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("ScenarioId", "TS-024")] - [Property("BehaviorId", "BHV-108")] - [Property("InvariantId", "INV-009")] - public void GetTokensForBook_HeadingMarker_TakesVerseRefOfNextNonHeadingParagraph() - { - // TS-024 / INV-009 / gm-010 slice: a \s heading followed by a \p paragraph - // at \v 1 — the heading's VerseRefStart must resolve to the verse ref of - // the following \p's first verse (v1), not to chapter:0. - var scrText = CreatePoetryProject(); - const int BookNum = 2; // EXO (gm-010 uses EXO) - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \s Section \p \v 1 one. \v 2 two,"); - - var filter = new HashSet { "s", "p" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - var heading = result.FirstOrDefault(p => p.Marker == "s"); - Assert.That(heading, Is.Not.Null, "section head paragraph must be emitted"); - Assert.That(heading!.IsHeading, Is.True); - Assert.That( - heading.VerseRefStart.ChapterNum, - Is.EqualTo(20), - "heading chapter must match the chapter that contains it" - ); - Assert.That( - heading.VerseRefStart.VerseNum, - Is.EqualTo(1), - "INV-009: heading VerseRefStart must be the next non-heading paragraph's verse (v1)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("BehaviorId", "BHV-108")] - [Property("InvariantId", "INV-009")] - public void GetTokensForBook_HeadingBeforeChapter_StopsForwardScanAtChapter() - { - // FB-35863 regression guard (baked into PT9 FindVerseRefForParagraph): - // when a section head mistakenly appears before a \c chapter marker, the - // forward scan must STOP at the chapter and keep the heading's current - // reference — it must not leak into the next chapter. - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 19 \p \v 1 last. \s OutOfPlaceHeading \c 20 \p \v 1 first of 20." - ); - - var filter = new HashSet { "s", "p" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - var heading = result.FirstOrDefault(p => p.Marker == "s"); - Assert.That(heading, Is.Not.Null); - Assert.That( - heading!.VerseRefStart.ChapterNum, - Is.EqualTo(19), - "heading must keep chapter 19 — forward scan stops at the \\c marker per FB-35863" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("ScenarioId", "TS-071")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_MarkerNotInFilter_ExcludedFromResult() - { - // Filter mechanics (happy path variant of TS-071): a \q paragraph is - // present in the USFM but not in the filter; it must be absent from the - // result. This is the normal gating behaviour — "only desired paragraph - // markers create new CLParagraphTokens entries". - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one. \q \v 2 poetic"); - - var filter = new HashSet { "p" }; // deliberately omit "q" - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - Assert.That( - result.Any(p => p.Marker == "q"), - Is.False, - "q is outside the filter and must not produce a paragraph entry" - ); - Assert.That( - result.Any(p => p.Marker == "p"), - Is.True, - "p is inside the filter and must produce a paragraph entry" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_EmptyFilter_ProducesEmptyList() - { - // Defensive contract: the PT9 code path "if (desiredMarkers != null && - // !desiredMarkers.Contains(...)) continue" means an EMPTY filter accepts - // NOTHING — the caller (orchestration) is responsible for passing the - // full set of paragraph markers when no user filter is active. This test - // pins that behavior so callers can rely on it. - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm(scrText, BookNum, @"\id EXO \c 20 \p \v 1 one. \q \v 2 poetic"); - - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - new HashSet(), // empty - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - Assert.That( - result, - Is.Empty, - "empty filter means no paragraphs accepted; caller supplies full marker set" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("ScenarioId", "TS-031")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_CharacterStyleTokens_PreservedNotSkipped() - { - // gm-016 / TS-031 token-level slice: \em ... \em* character-style tokens - // are NOT in the skip predicate (only "fig" is). They must appear in the - // collected Tokens list so downstream cell construction can render them - // in parentheses per BHV-604. The display pipeline itself lives in CAP-006. - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry \q2 indented \em poetry\em*" - ); - - var filter = new HashSet { "p", "q", "q2" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - // The q2 paragraph should include the \em tokens — we look for a token - // whose Marker is "em" (the character-style tag) in any paragraph. - var sawEm = result.SelectMany(p => p.Tokens).Any(t => t.Marker == "em"); - Assert.That( - sawEm, - Is.True, - "character-style tokens (\\em) must be preserved — only fig tokens are skipped" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "GetTokensForBook")] - [Property("BehaviorId", "BHV-108")] - public void GetTokensForBook_MultiplePoetryParagraphs_ProducesOneEntryPerParaStart() - { - // gm-009 token-level slice: the USFM "\p ... \q ... \q2 ..." produces - // three distinct paragraph entries, one per ParaStart. This pins that - // a new ChecklistParagraphTokens is created at every qualifying ParaStart, - // matching PT9's line "if (state.ParaStart) paragraphTokens = null;" - // followed by the new-paragraph creation. - var scrText = CreatePoetryProject(); - const int BookNum = 2; - LoadUsfm( - scrText, - BookNum, - @"\id EXO \c 20 \p \v 1 one. \v 2 two, \q poetry more \q2 indented poetry" - ); - - var filter = new HashSet { "p", "q", "q2" }; - var result = ChecklistService.GetTokensForBook( - scrText, - BookNum, - filter, - BuildHeadingMarkers(), - BuildNonHeadingParagraphMarkers() - ); - - Assert.That(result.Count, Is.EqualTo(3), "one paragraph entry per ParaStart"); - Assert.That(result[0].Marker, Is.EqualTo("p")); - Assert.That(result[1].Marker, Is.EqualTo("q")); - Assert.That(result[2].Marker, Is.EqualTo("q2")); - // Non-heading paragraphs should be flagged as such. - Assert.That(result.All(p => !p.IsHeading), Is.True); - } - - // ===================================================================== - // EXT-012 / BHV-119 — ChecklistParagraphTokens record - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "ChecklistParagraphTokens")] - [Property("BehaviorId", "BHV-108")] - public void ChecklistParagraphTokens_Record_StoresVerseRefMarkerIsHeadingTokens() - { - // Helper-record shape: VerseRefStart, Marker, IsHeading, Tokens must all - // be settable via construction and exposed via property reads. No other - // public surface is asserted — a positional record suffices. - var vref = new VerseRef("GEN", "1", "1", ScrVers.English); - var tokens = new List(); - - var paraTokens = new ChecklistParagraphTokens( - VerseRefStart: vref, - Marker: "p", - IsHeading: false, - Tokens: tokens - ); - - Assert.That(paraTokens.VerseRefStart, Is.EqualTo(vref)); - Assert.That(paraTokens.Marker, Is.EqualTo("p")); - Assert.That(paraTokens.IsHeading, Is.False); - Assert.That(paraTokens.Tokens, Is.SameAs(tokens)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "ChecklistParagraphTokens")] - [Property("BehaviorId", "BHV-108")] - public void ChecklistParagraphTokens_IsHeadingTrue_ForHeadingMarker() - { - // IsHeading is PT10-only and must be populated correctly at construction - // (and consistent with what GetTokensForBook would produce for an \s - // paragraph emitted through headingMarkers). - var vref = new VerseRef("GEN", "1", "1", ScrVers.English); - var paraTokens = new ChecklistParagraphTokens( - VerseRefStart: vref, - Marker: "s", - IsHeading: true, - Tokens: new List() - ); - - Assert.That(paraTokens.IsHeading, Is.True); - Assert.That(paraTokens.Marker, Is.EqualTo("s")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "ChecklistParagraphTokens.ReferenceInRange")] - [Property("ScenarioId", "TS-056")] - [Property("BehaviorId", "BHV-119")] - public void ChecklistParagraphTokens_ReferenceInRange_VerseBridgeOverlapsRange_ReturnsTrue() - { - // TS-056 / BHV-119: a paragraph with VerseRefStart "LUK 3:24-38" (a verse - // bridge) against range [LUK 3:1, LUK 3:38]. AllVerses() must expand the - // bridge to the individual verses and at least one must fall inside the - // range → returns true. - var bridge = new VerseRef("LUK", "3", "24-38", ScrVers.English); - var startRef = new VerseRef("LUK", "3", "1", ScrVers.English); - var endRef = new VerseRef("LUK", "3", "38", ScrVers.English); - - var paraTokens = new ChecklistParagraphTokens( - VerseRefStart: bridge, - Marker: "p", - IsHeading: false, - Tokens: new List() - ); - - Assert.That(paraTokens.ReferenceInRange(startRef, endRef), Is.True); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "ChecklistParagraphTokens.ReferenceInRange")] - [Property("ScenarioId", "TS-057")] - [Property("BehaviorId", "BHV-119")] - public void ChecklistParagraphTokens_ReferenceInRange_FullyOutsideRange_ReturnsFalse() - { - // TS-057: paragraph at LUK 4:1 against range [LUK 1:1, LUK 3:38]. - // No overlap → returns false. - var para = new VerseRef("LUK", "4", "1", ScrVers.English); - var startRef = new VerseRef("LUK", "1", "1", ScrVers.English); - var endRef = new VerseRef("LUK", "3", "38", ScrVers.English); - - var paraTokens = new ChecklistParagraphTokens( - VerseRefStart: para, - Marker: "p", - IsHeading: false, - Tokens: new List() - ); - - Assert.That(paraTokens.ReferenceInRange(startRef, endRef), Is.False); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-003")] - [Property("Contract", "ChecklistParagraphTokens.ReferenceInRange")] - [Property("BehaviorId", "BHV-119")] - public void ChecklistParagraphTokens_ReferenceInRange_DefaultStartRef_MatchesAnyVerse() - { - // PT9 short-circuit: when startRef.IsDefault is true, the "vref >= startRef" - // side of the predicate is treated as satisfied. A paragraph at LUK 1:1 - // against [default, LUK 3:38] must therefore be considered in range. - var para = new VerseRef("LUK", "1", "1", ScrVers.English); - var defaultStart = new VerseRef(); // IsDefault - var endRef = new VerseRef("LUK", "3", "38", ScrVers.English); - - var paraTokens = new ChecklistParagraphTokens( - VerseRefStart: para, - Marker: "p", - IsHeading: false, - Tokens: new List() - ); - - Assert.That(defaultStart.IsDefault, Is.True, "pre-check: default VerseRef sentinel"); - Assert.That(paraTokens.ReferenceInRange(defaultStart, endRef), Is.True); - } -} diff --git a/c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs b/c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs deleted file mode 100644 index 400e32e7bbd..00000000000 --- a/c-sharp-tests/Checklists/Markers/MarkerSettingsValidationTests.cs +++ /dev/null @@ -1,554 +0,0 @@ -using Paranext.DataProvider.Checklists.Markers; - -namespace TestParanextDataProvider.Checklists.Markers; - -/// -/// RED-phase contract tests for CAP-007 (Marker Settings Validation — leaf logic). -/// -/// -/// These tests exercise a new public static method on the existing -/// MarkersDataSource class: -/// ValidateMarkerSettings(string) -> MarkerSettingsValidationResult. -/// The method exists as a NotImplementedException stub at commit time, so -/// dotnet build succeeds and all 22 tests run and fail deterministically -/// with a clear pointer to PT9's MarkerSettingsForm.btnOk_Click. The -/// GREEN implementer replaces the stub body with the PT9 port. This matches -/// the CAP-002 RED-commit shape — see MarkersDataSourceTests.cs:11-18 -/// and commit b0699d7830. -/// -/// -/// -/// Scope: port of PT9 MarkerSettingsForm.btnOk_Click (at -/// Paratext/Checklists/MarkerSettingsForm.cs:28-49) as a pure-function validator. -/// ValidateMarkerSettings accepts an equivalent-markers string from the Settings -/// UI and returns structured success/failure metadata. This is a **separate entry point** -/// from CAP-002's InitializeMarkerMappings (which silently skips invalid pairs -/// per VAL-005 to preserve runtime robustness); the validator surfaces invalid input -/// per VAL-002 so the UI can keep the dialog open and show the error (BHV-312). -/// -/// -/// -/// Design note (see implementation/plans/test-writer-CAP-007.md Decision 1): -/// these tests specify a **static synchronous** method on MarkersDataSource. -/// The async facade shown in data-contracts.md §4.2 -/// (ValidateMarkerSettingsAsync(string, CancellationToken)) is the PAPI -/// NetworkObject wrapping, which is a CAP-011 concern, not CAP-007 logic. The -/// validator is pure string processing; Task.FromResult(...) is the entire -/// wrapper body. -/// -/// -/// Traceability: -/// - Capability: CAP-007 -/// - Contract: data-contracts.md §4.2 (ValidateMarkerSettings) -/// - Types: data-contracts.md §3.13 (MarkerSettingsValidationResult), §3.14 (MarkerPair) -/// - Behaviors: BHV-105 (parsing), BHV-312 (Settings dialog — backend validate call) -/// - Extractions: EXT-019 (MarkerSettingsForm.btnOk_Click) -/// - Invariants / Validations: VAL-002 (format), §3.13 mutex invariants -/// - Golden Masters: gm-007, gm-008 (inputs reused as acceptance inputs here) -/// -[TestFixture] -internal class MarkerSettingsValidationTests -{ - /// - /// Localize key placed in - /// when validation fails. Per the patterns.errorHandling.backendLocalization - /// registry entry, the static service returns the key; the wrapping - /// - /// resolves it via LocalizationService.GetLocalizedString before the - /// wire response is serialized. Integration tests that go through the - /// NetworkObject assert on the resolved English fallback value instead — - /// see . Maps to PT9 MarkerSettingsForm_1. - /// - private const string Pt9ErrorMessageKey = "%markersChecklist_errorInvalidMarkerPair%"; - - /// - /// English fallback for . Used by - /// integration tests going through the NetworkObject where the localization - /// service is not wired up in the test harness; matches the PT9 byte-exact - /// literal from MarkerSettingsForm.cs:39. - /// - private const string Pt9ErrorEnglishFallback = - "Equivalent markers need to be entered in the form: p/q"; - - // ===================================================================== - // Happy-path scenarios — valid input returns Valid=true with parsed pairs - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-01")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_SinglePair_ReturnsValidWithOnePair() - { - // TS-VAL-002-01: Basic valid format "p/q" parses to one MarkerPair. - var result = MarkersDataSource.ValidateMarkerSettings("p/q"); - - Assert.That(result, Is.Not.Null); - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(1)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-02")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_MultiplePairs_ReturnsValidWithAllPairs() - { - // TS-VAL-002-02: "p/q q1/q2" parses to TWO pairs in source order. - var result = MarkersDataSource.ValidateMarkerSettings("p/q q1/q2"); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[1].Marker1, Is.EqualTo("q1")); - Assert.That(result.ParsedPairs[1].Marker2, Is.EqualTo("q2")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-07")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_EmptyString_ReturnsValidWithEmptyPairs() - { - // TS-VAL-002-07: Empty string is VALID (no mappings configured). PT9 - // MarkerSettingsForm.btnOk_Click:32 skips the pair-validation loop when - // equivalents=="" and proceeds to DialogResult.OK. - var result = MarkersDataSource.ValidateMarkerSettings(""); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null, "§3.13: Valid=true ⇒ ParsedPairs populated"); - Assert.That(result.ParsedPairs, Is.Empty); - Assert.That(result.ErrorMessage, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-07")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_Null_ReturnsValidWithEmptyPairs() - { - // Derived from PT9 line 30: `string equivalents = EquivalentMarkers ?? "";` - // Null coerces to empty, which then takes the valid-empty branch. - var result = MarkersDataSource.ValidateMarkerSettings(null!); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Is.Empty); - Assert.That(result.ErrorMessage, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-07")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_WhitespaceOnly_ReturnsValidWithEmptyPairs() - { - // Derived from PT9 line 31: `Regex.Replace(equivalents.Trim(), " +", " ");` - // After trim+collapse, " " becomes "", which takes the valid-empty branch. - var result = MarkersDataSource.ValidateMarkerSettings(" "); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Is.Empty); - Assert.That(result.ErrorMessage, Is.Null); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-06")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_MultipleSpacesBetweenPairs_NormalizesAndValidates() - { - // TS-VAL-002-06: Multiple spaces between pairs are collapsed (PT9 - // Regex.Replace(" +", " ")) before splitting. "p/q q1/q2" ⇒ 2 pairs. - var result = MarkersDataSource.ValidateMarkerSettings("p/q q1/q2"); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[1].Marker1, Is.EqualTo("q1")); - Assert.That(result.ParsedPairs[1].Marker2, Is.EqualTo("q2")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_LeadingTrailingWhitespace_Trimmed() - { - // Derived from PT9 line 31: outer trim applied before pair-splitting. - // " p/q " ⇒ valid, single pair (p, q). - var result = MarkersDataSource.ValidateMarkerSettings(" p/q "); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Is.Not.Null); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(1)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - } - - // ===================================================================== - // Error scenarios — malformed input returns Valid=false with PT9 error - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-03")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_SingleMarkerNoSlash_ReturnsInvalid() - { - // TS-VAL-002-03: "p" has zero slashes ⇒ Split('/').Length == 1 ≠ 2. - var result = MarkersDataSource.ValidateMarkerSettings("p"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-04")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_TripleSlash_ReturnsInvalid() - { - // TS-VAL-002-04: "p/q/r" has two slashes ⇒ Split('/').Length == 3 ≠ 2. - var result = MarkersDataSource.ValidateMarkerSettings("p/q/r"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-05")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_EmptyLeftSide_ReturnsInvalid() - { - // TS-VAL-002-05: "/q" has an empty left side ⇒ items[0].Trim().Length == 0. - var result = MarkersDataSource.ValidateMarkerSettings("/q"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_EmptyRightSide_ReturnsInvalid() - { - // Symmetric to TS-VAL-002-05: "p/" has an empty right side. - // PT9 line 37: items[1].Trim().Length == 0 triggers the alert. - var result = MarkersDataSource.ValidateMarkerSettings("p/"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_BothSidesEmpty_ReturnsInvalid() - { - // Edge derived from VAL-002: "/" alone → both sides empty. - var result = MarkersDataSource.ValidateMarkerSettings("/"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_TrailingWhitespaceOnRightSide_ReturnsInvalid() - { - // VAL-002 requires BOTH sides non-empty **after trim**. PT9 line 37: - // `items[0].Trim().Length == 0 || items[1].Trim().Length == 0`. - // Input "p/q a/ " — the second pair `a/ ` has trailing whitespace - // after the slash, so its right side is empty-after-trim and the - // per-side trim check rejects the entire settings string. - var result = MarkersDataSource.ValidateMarkerSettings("p/q a/ "); - // ^^ second pair has - // trailing whitespace right side - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_WhitespaceOnlySides_ReturnsInvalid() - { - // VAL-002 explicit whitespace-only-side coverage: both the "whitespace - // after slash" and "whitespace before slash" shapes must be rejected - // via the per-side Trim() check. These supplement the - // TrailingWhitespaceOnRightSide test above by exercising a standalone - // pair (no preceding valid pair) on each side of the slash. - - var resultRight = MarkersDataSource.ValidateMarkerSettings("p/ "); - Assert.That(resultRight.Valid, Is.False, "'p/ ' — whitespace-only right side"); - Assert.That(resultRight.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - - var resultLeft = MarkersDataSource.ValidateMarkerSettings(" /q"); - Assert.That(resultLeft.Valid, Is.False, "' /q' — whitespace-only left side"); - Assert.That(resultLeft.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_MixedValidAndInvalid_FailsOnFirstInvalidPair() - { - // PT9 line 41 uses `return;` inside the foreach loop — on the FIRST invalid - // pair, validation aborts with the error. This distinguishes CAP-007 (fail- - // fast for UI) from CAP-002 InitializeMarkerMappings (silently skip, VAL-005). - // Here "invalid" (no slash) causes the whole string to be rejected even - // though "p/q" alone would pass. - var result = MarkersDataSource.ValidateMarkerSettings("p/q invalid good/bad"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-VAL-002-03")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_Invalid_ErrorMessageIsLocalizeKey() - { - // VAL-002: static service returns the paranext-core localize key. - // The wrapping ChecklistNetworkObject resolves it to the PT9-exact - // English literal (%markersChecklist_errorInvalidMarkerPair% → - // "Equivalent markers need to be entered in the form: p/q") via - // LocalizationService.GetLocalizedString before serializing the wire - // response. See patterns.errorHandling.backendLocalization. - var result = MarkersDataSource.ValidateMarkerSettings("p"); - - Assert.That( - result.ErrorMessage, - Is.EqualTo(Pt9ErrorMessageKey), - "VAL-002: static service returns the localize key (resolution at the wire boundary)" - ); - Assert.That( - MarkersDataSource.InvalidMarkerPairErrorFallback, - Is.EqualTo("Equivalent markers need to be entered in the form: p/q"), - "PT9 English fallback constant must match byte-for-byte (used by NetworkObject when localization service is unavailable)" - ); - } - - // ===================================================================== - // §3.13 structural invariants — ParsedPairs ⊕ ErrorMessage (mutually exclusive) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - [Property("InvariantId", "Section-3.13-mutex")] - public void ValidateMarkerSettings_Invalid_ParsedPairsIsNull() - { - // §3.13: "When Valid is false, ErrorMessage is populated and ParsedPairs is - // undefined." No partial-parse leakage — even if "p/q" parsed before - // "invalid" failed, ParsedPairs must be null (not [p/q]). - var result = MarkersDataSource.ValidateMarkerSettings("p/q invalid"); - - Assert.That(result.Valid, Is.False); - Assert.That(result.ParsedPairs, Is.Null, "§3.13: Valid=false ⇒ ParsedPairs null"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - [Property("InvariantId", "Section-3.13-mutex")] - public void ValidateMarkerSettings_Valid_ErrorMessageIsNull() - { - // §3.13: "When Valid is true, ParsedPairs is populated and ErrorMessage is - // undefined." No leaking of stale or informational strings on success. - var result = MarkersDataSource.ValidateMarkerSettings("p/q q1/q2"); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ErrorMessage, Is.Null, "§3.13: Valid=true ⇒ ErrorMessage null"); - } - - // ===================================================================== - // Golden-master-derived scenarios — inputs used in PT9 capture harness runs - // ===================================================================== - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "gm-007")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_GoldenMaster_gm007_BidirectionalInputParsesToTwoPairs() - { - // gm-007 input: markerMapping="p/q q1/q2". The golden master captures - // InitializeMarkerMappings' bidirectional dictionary output; CAP-007's - // contract is instead to return the source pairs in order. The validator - // must ACCEPT this input as valid — CAP-002 can then expand the two pairs - // into the four-edge bidirectional dictionary. - var result = MarkersDataSource.ValidateMarkerSettings("p/q q1/q2"); - - Assert.That(result.Valid, Is.True, "gm-007 input must be valid"); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0], Is.EqualTo(new MarkerPair("p", "q"))); - Assert.That(result.ParsedPairs[1], Is.EqualTo(new MarkerPair("q1", "q2"))); - } - - [Test] - [Category("GoldenMaster")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "gm-008")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_GoldenMaster_gm008_AccumulatedPairsPreservedInOrder() - { - // gm-008 input: markerMapping="q/q1 q/q2" (same left-hand marker twice). - // InitializeMarkerMappings accumulates [q1, q2] under "q"; the validator - // preserves the two source pairs independently. Both are accepted as valid - // (the same left-hand marker in two pairs is not a format violation). - var result = MarkersDataSource.ValidateMarkerSettings("q/q1 q/q2"); - - Assert.That(result.Valid, Is.True, "gm-008 input must be valid"); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0], Is.EqualTo(new MarkerPair("q", "q1"))); - Assert.That(result.ParsedPairs[1], Is.EqualTo(new MarkerPair("q", "q2"))); - } - - // ===================================================================== - // CAP-002 cross-reference — scenarios shared with InitializeMarkerMappings - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-016")] - [Property("BehaviorId", "BHV-105")] - public void ValidateMarkerSettings_TS016_ParsesBidirectionalInputAsPairs() - { - // TS-016 (CAP-002 scenario reused): verifies that the validator treats - // "p/q q1/q2" as TWO source pairs — it does not conflate or expand them. - // Bidirectional storage (INV-005) is CAP-002's concern; CAP-007 only - // parses and validates. - var result = MarkersDataSource.ValidateMarkerSettings("p/q q1/q2"); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("p")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[1].Marker1, Is.EqualTo("q1")); - Assert.That(result.ParsedPairs[1].Marker2, Is.EqualTo("q2")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-017")] - [Property("BehaviorId", "BHV-105")] - public void ValidateMarkerSettings_TS017_AccumulatedPairsPreservedInOrder() - { - // TS-017 (CAP-002 scenario reused): "q/q1 q/q2" is two distinct pairs - // sharing a left-hand marker. The validator keeps them as two pairs in - // source order; the accumulation-into-a-list behavior is CAP-002's - // InitializeMarkerMappings concern. - var result = MarkersDataSource.ValidateMarkerSettings("q/q1 q/q2"); - - Assert.That(result.Valid, Is.True); - Assert.That(result.ParsedPairs, Has.Count.EqualTo(2)); - Assert.That(result.ParsedPairs![0].Marker1, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[0].Marker2, Is.EqualTo("q1")); - Assert.That(result.ParsedPairs[1].Marker1, Is.EqualTo("q")); - Assert.That(result.ParsedPairs[1].Marker2, Is.EqualTo("q2")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-007")] - [Property("Contract", "ValidateMarkerSettings")] - [Property("ScenarioId", "TS-018")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-002")] - public void ValidateMarkerSettings_TS018_InvalidPairsRejectedNotSilentlySkipped() - { - // TS-018 (CAP-002 scenario reused, CONTRASTING contract): CAP-002's - // InitializeMarkerMappings silently skips invalid pairs per VAL-005 to - // preserve runtime robustness. CAP-007's ValidateMarkerSettings is the - // user-facing pre-commit validation path (VAL-002) and REJECTS the same - // input so the UI can keep the dialog open. This test pins the contract - // divergence between the two entry points. - var input = "p/q invalid p/q1/q2 good/bad"; - - var result = MarkersDataSource.ValidateMarkerSettings(input); - - Assert.That(result.Valid, Is.False, "VAL-002 rejects 'invalid' (zero slashes)"); - Assert.That(result.ErrorMessage, Is.EqualTo(Pt9ErrorMessageKey)); - } - - // NOTE on scope: TS-019 and TS-072 concern the separate **markerFilter** input - // (second PT9 form field: txtMarkerFilter). ValidateMarkerSettings does not - // accept a filter parameter — filter parsing is already covered by CAP-002's - // MarkersDataSourceTests. Including tests for those scenarios here would - // duplicate coverage and blur CAP-007's single-responsibility boundary. -} diff --git a/c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs b/c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs deleted file mode 100644 index 05ab9222ba5..00000000000 --- a/c-sharp-tests/Checklists/Markers/MarkersDataSourceTests.cs +++ /dev/null @@ -1,807 +0,0 @@ -using System.Collections.Generic; -using Paranext.DataProvider.Checklists; -using Paranext.DataProvider.Checklists.Markers; -using Paranext.DataProvider.Scripture; -using Paratext.Data; -using SIL.Scripture; - -namespace TestParanextDataProvider.Checklists.Markers; - -/// -/// RED-phase contract tests for CAP-002 (Markers Data Source — leaf logic). -/// -/// -/// These tests will NOT compile until the implementer creates the static class -/// Paranext.DataProvider.Checklists.Markers.MarkersDataSource with the -/// seven public static methods below. That is intentional: the test file IS the -/// specification — the compile error is the first layer of the RED signal; the -/// test assertion failures are the second (matches the CAP-001 precedent in -/// ChecklistDataModelTests.cs:12-16). -/// -/// -/// -/// Scope: marker-specific leaf logic only. Full CLDataSource.BuildRows -/// pipeline verification (gm-002..gm-018 captures) lives at the orchestration -/// layer (CAP-006) where these leaves are composed. See -/// implementation/plans/test-writer-CAP-002.md for the rationale. -/// -/// -/// Traceability: -/// - Capability: CAP-002 -/// - Behaviors: BHV-102, BHV-103, BHV-104, BHV-105, BHV-106, BHV-120 -/// - Extractions: EXT-003, EXT-004, EXT-005, EXT-006, EXT-007, EXT-013 -/// - Invariants: INV-003, INV-004, INV-005 (CRITICAL bidirectional), -/// INV-008, VAL-001, VAL-005, VAL-006 -/// - Contract: data-contracts.md §4.1 (BuildChecklistData leaf behaviors) -/// -[TestFixture] -internal class MarkersDataSourceTests -{ - // --------------------------------------------------------------------- - // Shared fixtures - // --------------------------------------------------------------------- - - /// - /// The DummyScrStylesheet already defines paragraph markers (p, s, mt, - /// nb, ip, id, rem, c, cp) and character markers (w, em, nd). We use it - /// directly to verify INV-003 (only scParagraphStyle markers) without - /// constructing yet another fixture. - /// - private ScrStylesheet BuildStylesheet() => new DummyScrStylesheet(); - - private static ChecklistRow RowFromMarkers(params string[][] cellMarkers) - { - var cells = new List(); - foreach (var markers in cellMarkers) - { - var paragraphs = new List(); - foreach (var marker in markers) - { - paragraphs.Add(new ChecklistParagraph(marker, new List())); - } - cells.Add( - new ChecklistCell( - paragraphs, - new ScriptureRange(new VerseRef("GEN 1:1"), null), - "en", - Error: null - ) - ); - } - return new ChecklistRow( - cells, - IsMatch: false, - IncludeEditLink: false, - Score: 0.0, - FirstRef: null - ); - } - - // ===================================================================== - // BHV-102 / EXT-003 — ParagraphMarkers - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "ParagraphMarkers")] - [Property("ScenarioId", "TS-007")] - [Property("BehaviorId", "BHV-102")] - [Property("InvariantId", "INV-003")] - public void ParagraphMarkers_ReturnsOnlyScParagraphStyleMarkers() - { - // INV-003: The Markers checklist includes only markers with - // StyleType == scParagraphStyle (never character styles). - var stylesheet = BuildStylesheet(); - var emptyFilter = new HashSet(); - - var result = MarkersDataSource.ParagraphMarkers(stylesheet, emptyFilter); - - Assert.That(result, Is.Not.Null); - // DummyScrStylesheet defines these paragraph markers. - Assert.That(result, Does.Contain("p")); - Assert.That(result, Does.Contain("s")); - Assert.That(result, Does.Contain("mt")); - Assert.That(result, Does.Contain("nb")); - Assert.That(result, Does.Contain("ip")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "ParagraphMarkers")] - [Property("ScenarioId", "TS-007")] - [Property("BehaviorId", "BHV-102")] - [Property("InvariantId", "INV-003")] - public void ParagraphMarkers_ExcludesCharacterStyleMarkers() - { - // INV-003 negative branch: character-style markers (w, em, nd) must NOT - // appear in the result even though they are defined in the stylesheet. - var stylesheet = BuildStylesheet(); - var emptyFilter = new HashSet(); - - var result = MarkersDataSource.ParagraphMarkers(stylesheet, emptyFilter); - - Assert.That(result, Does.Not.Contain("w")); - Assert.That(result, Does.Not.Contain("em")); - Assert.That(result, Does.Not.Contain("nd")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "ParagraphMarkers")] - [Property("ScenarioId", "TS-008")] - [Property("BehaviorId", "BHV-102")] - public void ParagraphMarkers_WithActiveFilter_RestrictsToFilteredMarkers() - { - // BHV-102: non-empty filter intersects with the stylesheet's paragraph - // markers. Only markers that appear in BOTH are returned. - var stylesheet = BuildStylesheet(); - var filter = new HashSet { "p", "s" }; - - var result = MarkersDataSource.ParagraphMarkers(stylesheet, filter); - - Assert.That(result, Does.Contain("p")); - Assert.That(result, Does.Contain("s")); - Assert.That(result, Does.Not.Contain("mt"), "mt is a paragraph marker but not in filter"); - Assert.That(result, Does.Not.Contain("nb"), "nb is a paragraph marker but not in filter"); - Assert.That(result.Count, Is.EqualTo(2)); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "ParagraphMarkers")] - [Property("ScenarioId", "TS-020")] - [Property("BehaviorId", "BHV-102")] - [Property("ValidationRule", "VAL-006")] - public void ParagraphMarkers_WithEmptyFilter_ReturnsAllParagraphMarkers() - { - // VAL-006: empty filter means "all paragraph markers in the stylesheet". - // Verified by comparing filtered-output count with unfiltered-output - // count after passing through an empty filter — they must equal. - var stylesheet = BuildStylesheet(); - var emptyFilter = new HashSet(); - - var result = MarkersDataSource.ParagraphMarkers(stylesheet, emptyFilter); - - // Should include every known paragraph marker from DummyScrStylesheet. - Assert.That(result, Does.Contain("p")); - Assert.That(result, Does.Contain("s")); - Assert.That(result, Does.Contain("mt")); - Assert.That(result, Does.Contain("nb")); - Assert.That(result, Does.Contain("ip")); - Assert.That(result, Does.Contain("id")); - Assert.That(result, Does.Contain("c")); - Assert.That(result, Does.Contain("cp")); - Assert.That(result, Does.Contain("rem")); - // And must not include any character-style markers. - Assert.That(result, Does.Not.Contain("w")); - } - - // ===================================================================== - // BHV-103 / EXT-004 — PostProcessParagraph - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessParagraph")] - [Property("ScenarioId", "TS-009")] - [Property("BehaviorId", "BHV-103")] - [Property("InvariantId", "INV-004")] - public void PostProcessParagraph_ShowVerseTextFalse_ClearsItemsAndInsertsMarkerOnly() - { - // BHV-103 / INV-004: with showVerseText=false, existing items are cleared - // and a single TextItem("\\" + marker) is inserted at position 0. - var input = new ChecklistParagraph( - "p", - new List - { - new TextItem("verse text here", null), - new TextItem("more text", null), - } - ); - - var result = MarkersDataSource.PostProcessParagraph(input, showVerseText: false); - - Assert.That(result, Is.Not.Null); - Assert.That(result.Marker, Is.EqualTo("p")); - Assert.That(result.Items.Count, Is.EqualTo(1)); - Assert.That(result.Items[0], Is.InstanceOf()); - Assert.That(((TextItem)result.Items[0]).Text, Is.EqualTo("\\p")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessParagraph")] - [Property("ScenarioId", "TS-010")] - [Property("BehaviorId", "BHV-103")] - [Property("InvariantId", "INV-004")] - public void PostProcessParagraph_ShowVerseTextTrue_PrependsMarkerBeforeText() - { - // BHV-103: with showVerseText=true, marker text is inserted at index 0 - // and the original items are preserved at positions 1..N. - var input = new ChecklistParagraph( - "q2", - new List - { - new TextItem("indented ", null), - new TextItem("poetry", null), - } - ); - - var result = MarkersDataSource.PostProcessParagraph(input, showVerseText: true); - - Assert.That(result.Items.Count, Is.EqualTo(3)); - Assert.That(result.Items[0], Is.InstanceOf()); - Assert.That(((TextItem)result.Items[0]).Text, Is.EqualTo("\\q2")); - Assert.That(((TextItem)result.Items[1]).Text, Is.EqualTo("indented ")); - Assert.That(((TextItem)result.Items[2]).Text, Is.EqualTo("poetry")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessParagraph")] - [Property("ScenarioId", "TS-067")] - [Property("BehaviorId", "BHV-103")] - public void PostProcessParagraph_ShowVerseTextFalse_WithMarkerQ1_DisplaysBackslashQ1Only() - { - // TS-067: q1 marker with showVerseText=false displays exactly "\q1". - var input = new ChecklistParagraph( - "q1", - new List { new TextItem("some content", null) } - ); - - var result = MarkersDataSource.PostProcessParagraph(input, showVerseText: false); - - Assert.That(result.Items.Count, Is.EqualTo(1)); - Assert.That(((TextItem)result.Items[0]).Text, Is.EqualTo("\\q1")); - } - - // ===================================================================== - // BHV-104 / EXT-005 — HasSameValue (pairwise marker equivalence) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-011")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_IdenticalMarkers_ReturnsTrue() - { - // TS-011: two cells with the same single marker 'p' match without any - // mappings configured. - var row = RowFromMarkers(new[] { "p" }, new[] { "p" }); - var noMappings = new Dictionary>(); - - var result = MarkersDataSource.HasSameValue(row, noMappings); - - Assert.That(result, Is.True); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-012")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_DifferentNonMappedMarkers_ReturnsFalse() - { - // TS-012: two cells with different markers and no mapping -> not equivalent. - var row = RowFromMarkers(new[] { "p" }, new[] { "q" }); - var noMappings = new Dictionary>(); - - var result = MarkersDataSource.HasSameValue(row, noMappings); - - Assert.That(result, Is.False); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-013")] - [Property("BehaviorId", "BHV-104")] - [Property("InvariantId", "INV-005")] - public void HasSameValue_BidirectionalMapping_TreatsMappedMarkersAsEquivalent() - { - // INV-005: with mapping p<->q configured (stored in both directions), - // cells 'p' and 'q' are equivalent. This is the forward-direction check. - var row = RowFromMarkers(new[] { "p" }, new[] { "q" }); - var mappings = new Dictionary> - { - { - "p", - new List { "q" } - }, - { - "q", - new List { "p" } - }, - }; - - var result = MarkersDataSource.HasSameValue(row, mappings); - - Assert.That(result, Is.True, "p and q must be equivalent via bidirectional mapping"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-013")] - [Property("BehaviorId", "BHV-104")] - [Property("InvariantId", "INV-005")] - public void HasSameValue_BidirectionalMapping_ReverseDirection_StillEquivalent() - { - // INV-005 CRITICAL: the reverse direction must also match — the help - // docs imply a directional (first-text/second-text) mapping, but the - // code stores both directions, so 'q' in cell1 and 'p' in cell2 must - // also be equivalent. - var row = RowFromMarkers(new[] { "q" }, new[] { "p" }); - var mappings = new Dictionary> - { - { - "p", - new List { "q" } - }, - { - "q", - new List { "p" } - }, - }; - - var result = MarkersDataSource.HasSameValue(row, mappings); - - Assert.That(result, Is.True, "reverse direction (q,p) must be equivalent (INV-005)"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-014")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_PartialMapping_UnmappedDifferencesFailMatch() - { - // TS-014: cell1=[p, q1], cell2=[q, q2], mapping only has p<->q. - // q1 and q2 are NOT mapped to each other, so the row is not a match. - var row = RowFromMarkers(new[] { "p", "q1" }, new[] { "q", "q2" }); - var mappings = new Dictionary> - { - { - "p", - new List { "q" } - }, - { - "q", - new List { "p" } - }, - }; - - var result = MarkersDataSource.HasSameValue(row, mappings); - - Assert.That(result, Is.False, "q1/q2 are not mapped and differ -> row not a match"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-015")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_ParagraphCountMismatch_ReturnsFalse() - { - // TS-015: cell1 has 2 paragraphs, cell2 has 1. Count mismatch is a - // difference regardless of marker content. - var row = RowFromMarkers(new[] { "p", "q1" }, new[] { "p" }); - var noMappings = new Dictionary>(); - - var result = MarkersDataSource.HasSameValue(row, noMappings); - - Assert.That(result, Is.False); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-065")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_ThreeColumns_PairwiseComparison() - { - // TS-065: three cells [p][p][q]. Pairwise comparison (0,1) matches - // but (1,2) differs -> overall result false. - var row = RowFromMarkers(new[] { "p" }, new[] { "p" }, new[] { "q" }); - var noMappings = new Dictionary>(); - - var result = MarkersDataSource.HasSameValue(row, noMappings); - - Assert.That(result, Is.False, "pairwise (1,2) differs -> row not a match"); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HasSameValue")] - [Property("ScenarioId", "TS-066")] - [Property("BehaviorId", "BHV-104")] - public void HasSameValue_EmptyCellVsPopulated_ReturnsFalse() - { - // TS-066: one populated cell, one empty cell -> paragraph count mismatch -> false. - var row = RowFromMarkers(new[] { "p" }, new string[0]); - var noMappings = new Dictionary>(); - - var result = MarkersDataSource.HasSameValue(row, noMappings); - - Assert.That(result, Is.False); - } - - // ===================================================================== - // BHV-105 / EXT-006 — InitializeMarkerMappings - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-016")] - [Property("BehaviorId", "BHV-105")] - [Property("InvariantId", "INV-005")] - public void InitializeMarkerMappings_BidirectionalPairs_StoredBothDirections() - { - // INV-005 CRITICAL: for input "p/q q1/q2", the resulting dictionary - // must contain p->[q], q->[p], q1->[q2], q2->[q1] — both directions. - var (mappings, filter) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "p/q q1/q2", - markerFilterInput: "" - ); - - Assert.That(mappings, Is.Not.Null); - Assert.That(mappings.ContainsKey("p"), Is.True, "p key missing"); - Assert.That(mappings["p"], Does.Contain("q")); - Assert.That( - mappings.ContainsKey("q"), - Is.True, - "q key missing (reverse direction INV-005)" - ); - Assert.That(mappings["q"], Does.Contain("p")); - Assert.That(mappings.ContainsKey("q1"), Is.True); - Assert.That(mappings["q1"], Does.Contain("q2")); - Assert.That(mappings.ContainsKey("q2"), Is.True); - Assert.That(mappings["q2"], Does.Contain("q1")); - Assert.That(filter, Is.Empty); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-017")] - [Property("BehaviorId", "BHV-105")] - public void InitializeMarkerMappings_MultiplePairsSameMarker_Accumulates() - { - // TS-017: "q/q1 q/q2" -> q accumulates [q1, q2]; q1->[q]; q2->[q]. - var (mappings, _) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "q/q1 q/q2", - markerFilterInput: "" - ); - - Assert.That(mappings.ContainsKey("q"), Is.True); - Assert.That(mappings["q"], Does.Contain("q1")); - Assert.That(mappings["q"], Does.Contain("q2")); - Assert.That(mappings["q"].Count, Is.EqualTo(2)); - Assert.That(mappings["q1"], Is.EqualTo(new[] { "q" })); - Assert.That(mappings["q2"], Is.EqualTo(new[] { "q" })); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-018")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-005")] - public void InitializeMarkerMappings_InvalidPairs_SilentlySkipped() - { - // VAL-005: entries without exactly two parts after splitting on '/' - // are silently dropped. Input has two valid pairs (p/q, good/bad) and - // two invalid entries ("invalid" with zero slashes, "p/q1/q2" with two). - var (mappings, _) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "p/q invalid p/q1/q2 good/bad", - markerFilterInput: "" - ); - - // Valid pairs are present. - Assert.That(mappings.ContainsKey("p"), Is.True); - Assert.That(mappings["p"], Does.Contain("q")); - Assert.That(mappings.ContainsKey("q"), Is.True); - Assert.That(mappings["q"], Does.Contain("p")); - Assert.That(mappings.ContainsKey("good"), Is.True); - Assert.That(mappings["good"], Does.Contain("bad")); - Assert.That(mappings.ContainsKey("bad"), Is.True); - Assert.That(mappings["bad"], Does.Contain("good")); - - // Invalid entries produced no entries. - Assert.That( - mappings.ContainsKey("invalid"), - Is.False, - "'invalid' (no slash) must be skipped" - ); - Assert.That( - mappings.ContainsKey("p/q1/q2"), - Is.False, - "entry with two slashes must be skipped" - ); - // And the 3-part entry's parts must NOT have leaked in. We check that - // 'q1' did not get linked to 'q2' through this invalid entry. - if (mappings.TryGetValue("q1", out var q1Targets)) - { - Assert.That( - q1Targets, - Does.Not.Contain("q2"), - "invalid 3-part entry must not produce q1->q2" - ); - } - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-019")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-001")] - public void InitializeMarkerMappings_BackslashesInFilter_Stripped() - { - // VAL-001 / TS-VAL-001-01: backslash characters in the filter string - // are stripped automatically during parsing. - var (_, filter) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "", - markerFilterInput: "\\p \\q1 q2" - ); - - Assert.That(filter, Does.Contain("p")); - Assert.That(filter, Does.Contain("q1")); - Assert.That(filter, Does.Contain("q2")); - Assert.That(filter, Does.Not.Contain("\\p"), "backslashes must be stripped"); - Assert.That(filter, Does.Not.Contain("\\q1")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-VAL-001-02")] - [Property("BehaviorId", "BHV-105")] - public void InitializeMarkerMappings_FilterWithoutBackslashes_PassesThrough() - { - // TS-VAL-001-02: bare marker names (no backslashes) pass through - // unchanged after whitespace splitting. - var (_, filter) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "", - markerFilterInput: "p q1 q2" - ); - - Assert.That(filter, Is.EquivalentTo(new[] { "p", "q1", "q2" })); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-VAL-001-03")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-006")] - public void InitializeMarkerMappings_EmptyFilter_ReturnsEmptySet() - { - // VAL-006: empty filter string means no restriction (all markers). - // The returned set should be empty — "no restriction" is encoded by - // the empty set, not by a magic value. - var (_, filter) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "", - markerFilterInput: "" - ); - - Assert.That(filter, Is.Empty); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-072")] - [Property("BehaviorId", "BHV-105")] - [Property("ValidationRule", "VAL-006")] - public void InitializeMarkerMappings_WhitespaceOnlyFilter_ReturnsEmptySet() - { - // TS-072: a whitespace-only filter splits to zero tokens and is - // treated the same as an empty filter (VAL-006). - var (_, filter) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "", - markerFilterInput: " " - ); - - Assert.That(filter, Is.Empty); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "InitializeMarkerMappings")] - [Property("ScenarioId", "TS-016")] - [Property("BehaviorId", "BHV-105")] - public void InitializeMarkerMappings_EmptyMappingString_ReturnsEmptyDictionary() - { - // With no equivalent-markers input, the mapping dictionary is empty. - // This is the default case (no pairs configured). - var (mappings, _) = MarkersDataSource.InitializeMarkerMappings( - equivalentMarkersInput: "", - markerFilterInput: "" - ); - - Assert.That(mappings, Is.Empty); - } - - // ===================================================================== - // BHV-106 / EXT-007 — PostProcessRows (empty-results handling) - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessRows")] - [Property("ScenarioId", "TS-021")] - [Property("BehaviorId", "BHV-106")] - [Property("InvariantId", "INV-008")] - public void PostProcessRows_EmptyRowsNoFilter_ReturnsIdenticalMarkersMessage() - { - // TS-021 / INV-008: with no rows and no filter active, the service - // returns an EmptyResultMessage with variant="identical" and the - // paranext-core localize key for the PT9 message. Per the - // patterns.errorHandling.backendLocalization registry entry, the - // static service returns the KEY; the wrapping ChecklistNetworkObject - // resolves it via LocalizationService.GetLocalizedString before the - // wire response is serialized. Maps to PT9 CLParagraphCellsDataSource_1. - var emptyRows = new List(); - var emptyFilter = new HashSet(); - var books = new List { "GEN" }; - - var result = MarkersDataSource.PostProcessRows(emptyRows, emptyFilter, books); - - Assert.That(result, Is.Not.Null, "empty results must always produce a message (INV-008)"); - Assert.That(result!.Variant, Is.EqualTo("identical")); - Assert.That( - result.Message, - Is.EqualTo(MarkersDataSource.IdenticalMarkersMessageKey), - "static service returns the localize key (resolution at the wire boundary)" - ); - Assert.That( - MarkersDataSource.IdenticalMarkersMessageFallback, - Is.EqualTo("Comparative texts have identical markers."), - "English fallback matches PT9 Localizer.Str default at CLParagraphCellsDataSource.cs:304 (bare — '*** ... ***' wrapping was PT9 UI decoration, now a UI concern)" - ); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessRows")] - [Property("ScenarioId", "TS-022")] - [Property("BehaviorId", "BHV-106")] - [Property("InvariantId", "INV-008")] - public void PostProcessRows_EmptyRowsWithFilter_ReturnsNoResultsMessage() - { - // TS-022: with a marker filter active and no rows found, the message - // variant is "noResults" (distinct from "identical"). - var emptyRows = new List(); - var filter = new HashSet { "p", "q1" }; - var books = new List { "GEN", "EXO" }; - - var result = MarkersDataSource.PostProcessRows(emptyRows, filter, books); - - Assert.That(result, Is.Not.Null); - Assert.That(result!.Variant, Is.EqualTo("noResults")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessRows")] - [Property("ScenarioId", "TS-022")] - [Property("BehaviorId", "BHV-106")] - public void PostProcessRows_EmptyRowsWithFilter_MessageListsSearchedMarkersAndBooks() - { - // TS-022 structural requirement: the "noResults" message must carry - // the searched markers and searched books so the UI can render the - // localized message ("no rows found for markers X in books Y"). - var emptyRows = new List(); - var filter = new HashSet { "p", "q1" }; - var books = new List { "GEN", "EXO" }; - - var result = MarkersDataSource.PostProcessRows(emptyRows, filter, books); - - Assert.That(result, Is.Not.Null); - Assert.That(result!.SearchedMarkers, Is.Not.Null); - Assert.That(result.SearchedMarkers, Is.EquivalentTo(new[] { "p", "q1" })); - Assert.That(result.SearchedBooks, Is.Not.Null); - Assert.That(result.SearchedBooks, Is.EquivalentTo(new[] { "GEN", "EXO" })); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "PostProcessRows")] - [Property("BehaviorId", "BHV-106")] - [Property("InvariantId", "INV-008")] - public void PostProcessRows_NonEmptyRows_ReturnsNull() - { - // INV-008 inverse: when rows exist, NO empty-result message is produced. - // The caller sets ChecklistResult.EmptyResultMessage to null in this case. - var rows = new List { RowFromMarkers(new[] { "p" }, new[] { "p" }) }; - var emptyFilter = new HashSet(); - var books = new List { "GEN" }; - - var result = MarkersDataSource.PostProcessRows(rows, emptyFilter, books); - - Assert.That(result, Is.Null, "non-empty rows must not produce an EmptyResultMessage"); - } - - // ===================================================================== - // BHV-120 / EXT-013 — HeadingMarkers / NonHeadingParagraphMarkers - // ===================================================================== - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "HeadingMarkers")] - [Property("BehaviorId", "BHV-120")] - public void HeadingMarkers_ReturnsScSectionParagraphStyles() - { - // BHV-120: heading markers are stylesheet tags with TextType==scSection - // AND StyleType==scParagraphStyle. DummyScrStylesheet defines 's' as - // such a heading marker. - var stylesheet = BuildStylesheet(); - - var result = MarkersDataSource.HeadingMarkers(stylesheet); - - Assert.That(result, Is.Not.Null); - Assert.That( - result, - Does.Contain("s"), - "'s' is the section-head marker in DummyScrStylesheet" - ); - // Non-section paragraph markers must not appear. - Assert.That(result, Does.Not.Contain("p"), "'p' is verse text, not section"); - Assert.That(result, Does.Not.Contain("c"), "'c' is chapter, not section"); - // Character-style markers must never appear. - Assert.That(result, Does.Not.Contain("w")); - } - - [Test] - [Category("Contract")] - [Property("CapabilityId", "CAP-002")] - [Property("Contract", "NonHeadingParagraphMarkers")] - [Property("BehaviorId", "BHV-120")] - public void NonHeadingParagraphMarkers_ReturnsScVerseTextParagraphStyles() - { - // BHV-120: non-heading paragraph markers are tags with - // TextType==scVerseText AND StyleType==scParagraphStyle. - // DummyScrStylesheet defines 'p' and 'nb' as such. - var stylesheet = BuildStylesheet(); - - var result = MarkersDataSource.NonHeadingParagraphMarkers(stylesheet); - - Assert.That(result, Is.Not.Null); - Assert.That(result, Does.Contain("p"), "'p' is a verse-text paragraph marker"); - Assert.That(result, Does.Contain("nb"), "'nb' is a verse-text paragraph marker"); - // Heading and non-paragraph markers must not appear. - Assert.That(result, Does.Not.Contain("s"), "'s' is section heading, not verse text"); - Assert.That(result, Does.Not.Contain("w"), "'w' is character style"); - } -} From 2e9910d756c86da1b0a156bd7559a348f009922a Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:20:21 +0200 Subject: [PATCH 22/43] chore(markers-checklist): delete markers-checklist-commands.spec.ts E2E Tests the deleted network-object wire methods. Web-view-level E2E suites (functional-UI-PKG-002, functional-UI-PKG-003, journey) cover behavior. --- .../markers-checklist-commands.spec.ts | 226 ------------------ 1 file changed, 226 deletions(-) delete mode 100644 e2e-tests/tests/markers-checklist/markers-checklist-commands.spec.ts diff --git a/e2e-tests/tests/markers-checklist/markers-checklist-commands.spec.ts b/e2e-tests/tests/markers-checklist/markers-checklist-commands.spec.ts deleted file mode 100644 index d4cdd3296f8..00000000000 --- a/e2e-tests/tests/markers-checklist/markers-checklist-commands.spec.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * === NEW IN PT10 === Reason: Runtime regression test for the markers-checklist PAPI network object - * (`platformScripture.checklistService`). Catches the integration failures that unit tests cannot: - * PAPI registration, JSON-RPC routing, C#/JS type serialization, and parameter-count alignment at - * the wire boundary. - * - * Verifies the three methods registered by `ChecklistNetworkObject.InitializeAsync`: - * - * - `buildChecklistData(ChecklistRequest)` — main pipeline - * - `resolveComparativeTexts(activeProjectId, requestedTexts)` — GUID/name resolution - * - `validateMarkerSettings(equivalentMarkers)` — pure validation - * - * Uses the live PAPI fixture: requires a running Platform.Bible instance with the WebSocket server - * on port 8876. Tests skip automatically if the server is unreachable. - */ -import { test, expect, canConnectToPapi } from '../../fixtures/papi-live.fixture'; - -/** - * JSON-RPC protocol-level error codes (per JSON-RPC 2.0 spec). A handler that routes correctly and - * executes its body must NOT surface any of these — business-logic errors are implementation errors - * (server-defined codes), not protocol errors. - */ -const PARSE_ERROR = -32700; -const INVALID_REQUEST = -32600; -const METHOD_NOT_FOUND = -32601; -const INVALID_PARAMS = -32602; -const INTERNAL_ERROR = -32603; - -const PROTOCOL_ERROR_CODES = [ - PARSE_ERROR, - INVALID_REQUEST, - METHOD_NOT_FOUND, - INVALID_PARAMS, - INTERNAL_ERROR, -] as const; - -/** Network-object wire prefix registered by c-sharp/Checklists/ChecklistNetworkObject.cs. */ -const NETWORK_OBJECT = 'object:platformScripture.checklistService'; -const BUILD_METHOD = `${NETWORK_OBJECT}.buildChecklistData`; -const RESOLVE_METHOD = `${NETWORK_OBJECT}.resolveComparativeTexts`; -const VALIDATE_METHOD = `${NETWORK_OBJECT}.validateMarkerSettings`; - -test.beforeAll(async () => { - test.skip( - !(await canConnectToPapi()), - 'PAPI WebSocket server (port 8876) is not running — skipping markers-checklist command verification', - ); -}); - -test.describe('Markers Checklist PAPI Command Verification', () => { - test('all expected network-object methods are discoverable via rpc.discover', async ({ - papiLive, - }) => { - const schema = await papiLive.request<{ methods: { name: string }[] }>('rpc.discover', []); - const methodNames = schema.methods.map((m) => m.name); - - // The base network-object probe handler (returns true from the object prefix). - expect(methodNames).toContain(NETWORK_OBJECT); - // The three registered service methods. - expect(methodNames).toContain(BUILD_METHOD); - expect(methodNames).toContain(RESOLVE_METHOD); - expect(methodNames).toContain(VALIDATE_METHOD); - }); - - test.describe('validateMarkerSettings', () => { - test('returns valid=true with parsed pairs for well-formed input "p/q q1/q2"', async ({ - papiLive, - }) => { - const result = await papiLive.request<{ - valid: boolean; - parsedPairs: { marker1: string; marker2: string }[] | null; - errorMessage: string | null; - }>(VALIDATE_METHOD, ['p/q q1/q2']); - - expect(result.valid).toBe(true); - expect(result.errorMessage).toBeNull(); - expect(result.parsedPairs).not.toBeNull(); - expect(result.parsedPairs).toEqual([ - { marker1: 'p', marker2: 'q' }, - { marker1: 'q1', marker2: 'q2' }, - ]); - }); - - test('returns valid=false with error message for malformed input "invalid"', async ({ - papiLive, - }) => { - const result = await papiLive.request<{ - valid: boolean; - parsedPairs: { marker1: string; marker2: string }[] | null; - errorMessage: string | null; - }>(VALIDATE_METHOD, ['invalid']); - - expect(result.valid).toBe(false); - expect(result.parsedPairs).toBeNull(); - expect(result.errorMessage).toBe('Equivalent markers need to be entered in the form: p/q'); - }); - - test('returns valid=true with empty array for empty string', async ({ papiLive }) => { - const result = await papiLive.request<{ - valid: boolean; - parsedPairs: { marker1: string; marker2: string }[] | null; - errorMessage: string | null; - }>(VALIDATE_METHOD, ['']); - - expect(result.valid).toBe(true); - expect(result.parsedPairs).toEqual([]); - expect(result.errorMessage).toBeNull(); - }); - }); - - /** - * Pick the first Paratext project id (USFM-capable). `ChecklistService` resolves project ids via - * `LocalParatextProjects.GetParatextProject(id)` which parses ids as `HexId` — non-Paratext - * projects (resource projects, lexical references) have short string ids like "SDBG" and will - * fail the hex parse. A hex-style GUID is required. - */ - async function findParatextProjectId( - papiLive: import('../../fixtures/papi-live.fixture').PapiLiveClient, - ): Promise { - const projects = await papiLive.request<{ id: string; projectInterfaces: string[] }[]>( - 'object:ProjectLookupService.getMetadataForAllProjects', - [{}], - ); - const match = projects?.find((p) => - p.projectInterfaces?.includes('platformScripture.USFM_Book'), - ); - return match?.id; - } - - test.describe('resolveComparativeTexts', () => { - test('returns { texts: [] } for a valid project ID with no requested texts', async ({ - papiLive, - }) => { - // Pick any existing Paratext (USFM) project so the active-project lookup succeeds; - // the feature excludes the active project from results, so an empty requested-texts - // list is guaranteed to produce an empty response regardless of which project we use. - const activeProjectId = await findParatextProjectId(papiLive); - test.skip( - !activeProjectId, - 'No Paratext (USFM) projects available for resolveComparativeTexts happy-path test', - ); - - const result = await papiLive.request<{ - texts: { id: string; name: string; fullName: string; available: boolean }[]; - }>(RESOLVE_METHOD, [activeProjectId, []]); - - expect(result).toEqual({ texts: [] }); - }); - - test('surfaces a non-protocol error for an invalid active project ID', async ({ papiLive }) => { - // The active-project-not-found condition is a business-logic error, so we use - // sendRaw-style access via requestRaw to read the error code without throwing. - const response = await papiLive.requestRaw(RESOLVE_METHOD, [ - 'definitely-not-a-project-id', - [], - ]); - - // Either the call succeeds (unlikely with a bogus id) or returns an implementation- - // defined error. It must NOT be a protocol-level JSON-RPC error. - if (response.error) { - expect(PROTOCOL_ERROR_CODES).not.toContain(response.error.code); - } - }); - }); - - test.describe('buildChecklistData', () => { - test('returns a well-formed ChecklistResult for a valid project + 1-verse range', async ({ - papiLive, - }) => { - const projectId = await findParatextProjectId(papiLive); - test.skip( - !projectId, - 'No Paratext (USFM) projects available for buildChecklistData happy-path test', - ); - - // VerseRef wire shape is { book, chapterNum, verseNum } per VerseRefConverter.cs - const request = { - projectId, - comparativeTextIds: [], - markerSettings: { equivalentMarkers: '', markerFilter: '' }, - verseRange: { - start: { book: 'GEN', chapterNum: 1, verseNum: 1 }, - end: { book: 'GEN', chapterNum: 1, verseNum: 5 }, - }, - hideMatches: false, - showVerseText: false, - }; - - const result = await papiLive.request<{ - rows: unknown[]; - excludedCount?: number; - truncated?: boolean; - }>(BUILD_METHOD, [request]); - - // Contract shape: rows is always an array (possibly empty). The other fields are - // optional depending on the result path (success vs empty-message vs truncated). - expect(Array.isArray(result.rows)).toBe(true); - }); - - test('surfaces a non-protocol error for an invalid (non-hex) project ID', async ({ - papiLive, - }) => { - const request = { - projectId: 'nonexistent-project-id', - comparativeTextIds: [], - markerSettings: { equivalentMarkers: '', markerFilter: '' }, - verseRange: { - start: { book: 'GEN', chapterNum: 1, verseNum: 1 }, - end: { book: 'GEN', chapterNum: 1, verseNum: 5 }, - }, - hideMatches: false, - showVerseText: false, - }; - - const response = await papiLive.requestRaw(BUILD_METHOD, [request]); - - // Business-logic failure is expected and proves the handler executed. Whatever - // code is returned must be a server-defined code, not a protocol-level JSON-RPC - // error (which would indicate routing/registration/marshalling is broken). - expect(response.error).toBeDefined(); - if (response.error) { - expect(PROTOCOL_ERROR_CODES).not.toContain(response.error.code); - } - }); - }); -}); From 70ddc507b6cf109ed962c1dd6b3da34d2e52a5a8 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:21:25 +0200 Subject: [PATCH 23/43] =?UTF-8?q?test(markers-checklist):=20E2E=20?= =?UTF-8?q?=E2=80=94=20marker=20settings=20persist=20across=20web=20views?= =?UTF-8?q?=20on=20same=20project?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validates the new project-scoped persistence introduced by Change B (settings moved from useWebViewState to useProjectSetting). See tj-review-design.md §R4. --- ...rs-checklist-functional-UI-PKG-003.spec.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/e2e-tests/tests/markers-checklist/markers-checklist-functional-UI-PKG-003.spec.ts b/e2e-tests/tests/markers-checklist/markers-checklist-functional-UI-PKG-003.spec.ts index bafe42838d9..305f7003625 100644 --- a/e2e-tests/tests/markers-checklist/markers-checklist-functional-UI-PKG-003.spec.ts +++ b/e2e-tests/tests/markers-checklist/markers-checklist-functional-UI-PKG-003.spec.ts @@ -507,4 +507,48 @@ test.describe('markers-checklist UI-PKG-003: Marker Settings Dialog', () => { await expect(frame.getByRole('alertdialog')).not.toBeVisible(); await expect(frame.getByRole('dialog').first()).not.toBeVisible({ timeout: 5_000 }); }); + + // ───────────────────────────────────────────────────────────────────────── + // Category 11: Cross-web-view persistence (project-scoped settings) + // ───────────────────────────────────────────────────────────────────────── + + // Validates the new project-scoped persistence introduced by Change B (settings moved from + // useWebViewState to useProjectSetting). Closing the Markers Checklist tab and reopening a + // brand-new one on the same project should surface the previously-saved settings — they live + // on the project, not the per-web-view memento. See tj-review-design.md §R4. + test('marker settings persist across web views on the same project', async ({ mainPage }) => { + // 1. Open the project + the first Markers Checklist tab. + await waitForAppReady(mainPage); + await openProjectByName(mainPage, PROJECT_NAME); + await openMarkersChecklistTool(mainPage); + + // 2. Open the dialog, commit known values, then close the dialog. + const firstDialog = await openMarkerSettingsDialog(mainPage); + const persistedEquivalent = 'p/q s/s1'; + const persistedFilter = 'p q s'; + await firstDialog.getByLabel(/Equivalent marker mappings/i).fill(persistedEquivalent); + await firstDialog + .getByLabel(/Markers to be displayed \(blank for all\)/i) + .fill(persistedFilter); + await firstDialog.getByRole('button', { name: /^OK$/ }).click(); + await expect(firstDialog.getByRole('dialog').first()).not.toBeVisible({ timeout: 5_000 }); + + // 3. Close the Markers Checklist tab WITHOUT closing the project — only the per-web-view + // state should be discarded; project-setting persistence must remain. + const checklistTab = mainPage.locator('.dock-tab', { hasText: CHECKLIST_TAB_TITLE_PATTERN }); + await checklistTab.first().locator('.dock-tab-close-btn').dispatchEvent('click'); + await expect(checklistTab).toHaveCount(0, { timeout: 5_000 }); + + // 4. Re-open a NEW Markers Checklist web view on the same project. + await openMarkersChecklistTool(mainPage); + const secondDialog = await openMarkerSettingsDialog(mainPage); + + // 5. Previously-saved values appear in the new web view — proves project-setting persistence. + await expect(secondDialog.getByLabel(/Equivalent marker mappings/i)).toHaveValue( + persistedEquivalent, + ); + await expect(secondDialog.getByLabel(/Markers to be displayed \(blank for all\)/i)).toHaveValue( + persistedFilter, + ); + }); }); From 90952a068dc1fbc34181f535cfc353daafb44013 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 22:36:30 +0200 Subject: [PATCH 24/43] =?UTF-8?q?fix(markers-checklist):=20correct=20proje?= =?UTF-8?q?ct-setting=20key=20=E2=80=94=20platform.textDirection=20(not=20?= =?UTF-8?q?platformScripture.textDirection)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The orchestrator was reading a setting key that doesn't exist on PT10's PDP contract. The canonical platform-base text-direction setting is 'platform.textDirection' (used elsewhere in checklist.web-view.tsx and the component-side types). Post-Phase-6 app smoke surfaced the error: ChecklistWebView: buildChecklistData failed: JSON-RPC Request error (-32000): Could not find project setting platformScripture.textDirection. This commit aligns fetch-column-book-data + its test fixtures + the build-checklist-data test fixtures with the canonical key. --- .../src/checklists/build-checklist-data.test.ts | 2 +- .../src/checklists/fetch-column-book-data.test.ts | 2 +- .../src/checklists/fetch-column-book-data.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts index 7529eaeaf57..26304b32a4d 100644 --- a/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts @@ -48,7 +48,7 @@ function makePapiMock(opts: { return { getSetting: vi.fn().mockImplementation(async (key: string) => { if (key === 'platform.isEditable') return proj.isEditable ?? false; - if (key === 'platformScripture.textDirection') return proj.textDirection ?? 'ltr'; + if (key === 'platform.textDirection') return proj.textDirection ?? 'ltr'; if (key === 'platformScripture.booksPresent') return proj.booksPresent ?? `1${'0'.repeat(122)}`; if (key === 'platform.name') return proj.name ?? projectId; diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts index d6126006952..1fee956ce99 100644 --- a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts @@ -32,7 +32,7 @@ function makePapi( return { getSetting: vi.fn().mockImplementation(async (key: string) => { if (key === 'platform.isEditable') return overrides.isEditable ?? true; - if (key === 'platformScripture.textDirection') return overrides.textDirection ?? 'ltr'; + if (key === 'platform.textDirection') return overrides.textDirection ?? 'ltr'; return undefined; }), }; diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts index 7ec1fee97ff..c19e12a0e8d 100644 --- a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts @@ -52,9 +52,9 @@ export type ColumnBookData = { */ isEditable: boolean; /** - * `true` when `platformScripture.textDirection === 'rtl'`. The walker uses this to prefix RTL - * text runs at extraction time (mirrors C# `ChecklistService.cs:1088`); `fetchColumnBookData` - * simply surfaces the resolved flag. + * `true` when `platform.textDirection === 'rtl'`. The walker uses this to prefix RTL text runs at + * extraction time (mirrors C# `ChecklistService.cs:1088`); `fetchColumnBookData` simply surfaces + * the resolved flag. */ rtl: boolean; }; @@ -81,7 +81,7 @@ export async function fetchColumnBookData( markerNamesPdp.getHeadingMarkers(bookNum), markerNamesPdp.getJoinedTextLanguage(bookNum), basePdp.getSetting('platform.isEditable'), - basePdp.getSetting('platformScripture.textDirection'), + basePdp.getSetting('platform.textDirection'), ]); return { From 6edc4b6f85da22c7f30755cc6060f319bfed7841 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 23:02:26 +0200 Subject: [PATCH 25/43] fix(markers-checklist): orchestrator runtime fixes surfaced by Phase 7 baseline parity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five fixes to make the TS orchestrator behave correctly against live PDPs: 1. USJ_Book PDP takes SerializedVerseRef (not bookNum). The existing platformScripture.USJ_Book.getBookUSJ method signature expects {book, chapterNum, verseNum}; passing a bare number serialized to an incomplete VerseRef that the backend rejected (-32602 deserialization error). Aligns with the existing consumer pattern in search-results-in-book.component.tsx:60-66 ({book, chapterNum:1, verseNum:0}). 2. VAL-003 applies to ANY book (not just GEN). Original guard checked ref.book === 'GEN' && chapterNum === 1 && verseNum === 1 — that's too narrow. C# applies the verse-0-inclusion at chapter-1-verse-1 regardless of book; scenario-10 (MAT 1:1–1:25) baseline depends on it (the MAT 1:0 intro material row must be included). 3. Walker output filtered to verseRange. The USJ walker emits every paragraph in the book; the orchestrator's verseRange MUST gate which paragraphs make it into cells. Without this, a single-chapter request produced 571 rows (whole-book) instead of 8 (chapter 1). 4. ProjectNotFoundError no longer extends Error. SWC's class-extending- Error polyfill touches Function.bind.apply(...), which the extension- host sandbox blocks → "ReferenceError: Function is not defined" at extension activation. Replaced with a factory + type-guard pattern: makeProjectNotFoundError(id) and isProjectNotFoundError(err). The factory attaches a sentinel name + code to a plain Error instance. 5. checklist.web-view.tsx updated to use isProjectNotFoundError instead of `instanceof ProjectNotFoundError` (which would no longer narrow with the factory pattern). After these fixes, 9 of 10 baseline scenarios pass structural fingerprint parity. Scenario 02 (whole-book single-column) has +9 rows (571 vs 562) due to verse-bridge / MaxCellsToGrab merge logic explicitly out-of-scope per consumer-inventory § 6 — separate follow-up if needed. --- .../src/checklist.web-view.tsx | 4 +- .../checklists/build-checklist-data.test.ts | 21 ++--- .../src/checklists/build-checklist-data.ts | 84 ++++++++++++++++--- .../checklists/fetch-column-book-data.test.ts | 4 +- .../src/checklists/fetch-column-book-data.ts | 16 +++- 5 files changed, 102 insertions(+), 27 deletions(-) diff --git a/extensions/src/platform-scripture/src/checklist.web-view.tsx b/extensions/src/platform-scripture/src/checklist.web-view.tsx index a2bd7960dea..8b6c09cbbd4 100644 --- a/extensions/src/platform-scripture/src/checklist.web-view.tsx +++ b/extensions/src/platform-scripture/src/checklist.web-view.tsx @@ -36,7 +36,7 @@ import { } from './components/marker-settings-dialog.component'; import { buildChecklistData, - ProjectNotFoundError, + isProjectNotFoundError, type ChecklistRequest, type ChecklistResult, } from './checklists/build-checklist-data'; @@ -363,7 +363,7 @@ global.webViewComponent = function ChecklistWebView({ setError(undefined); } catch (err) { if (cancelled || !isMountedRef.current) return; - if (err instanceof ProjectNotFoundError) { + if (isProjectNotFoundError(err)) { logger.warn( `ChecklistWebView: project not found (${err.projectId}): ${getErrorMessage(err)}`, ); diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts index 26304b32a4d..6bcd6b5e748 100644 --- a/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, vi } from 'vitest'; -import { buildChecklistData, ProjectNotFoundError } from './build-checklist-data'; +import { Canon } from '@sillsdev/scripture'; +import { buildChecklistData, isProjectNotFoundError } from './build-checklist-data'; import type { PapiLike } from './fetch-column-book-data'; import { SIMPLE_GENESIS_ONE_PARA, GENESIS_WITH_HEADING } from './test-fixtures/usj-fixtures'; import type { ChecklistRequest } from './build-checklist-data'; @@ -32,12 +33,13 @@ function makePapiMock(opts: { if (type === 'platformScripture.USJ_Book') return { - getBookUSJ: vi - .fn() - .mockImplementation( - async (n: number) => - proj.usjPerBook?.[n] ?? { type: 'USJ', version: '3.1', content: [] }, - ), + getBookUSJ: vi.fn().mockImplementation( + // USJ_Book PDP takes SerializedVerseRef per platform-scripture.d.ts:262. + async (verseRef: { book: string }) => { + const n = Canon.bookIdToNumber(verseRef.book); + return proj.usjPerBook?.[n] ?? { type: 'USJ', version: '3.1', content: [] }; + }, + ), }; if (type === 'platformScripture.MarkerNames') return { @@ -383,9 +385,8 @@ describe('buildChecklistData', () => { MISSING: { missing: true }, }, }); - await expect( - buildChecklistData(makeRequest({ comparativeTextIds: ['MISSING'] }), papi), - ).rejects.toBeInstanceOf(ProjectNotFoundError); + const promise = buildChecklistData(makeRequest({ comparativeTextIds: ['MISSING'] }), papi); + await expect(promise).rejects.toSatisfy(isProjectNotFoundError); }); }); diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts index c9b960fb834..89135ceaa42 100644 --- a/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts @@ -74,17 +74,45 @@ export type ChecklistResult = { * removed or its GUID is malformed). The caller (`useDataProvider` in the web view) maps this to a * user-facing error toast. */ -export class ProjectNotFoundError extends Error { - readonly code = 'PROJECT_NOT_FOUND'; - readonly projectId: string; - - constructor(projectId: string) { - super(`Project not found: ${projectId}`); - this.name = 'ProjectNotFoundError'; - this.projectId = projectId; - } +/** + * Custom error shape. We avoid `extends Error` because SWC's down-compilation of + * class-extending-Error generates a polyfill that touches `Function.bind.apply(...)` — and the + * extension-host sandbox blocks the global `Function` constructor, which causes the entire + * extension to fail to activate with `ReferenceError: Function is not defined`. Instead we attach a + * sentinel `name` + `code` to a plain `Error` instance via the factory, and expose an + * `isProjectNotFoundError(err)` type guard for callers that need to discriminate. + */ +export type ProjectNotFoundErrorType = Error & { + name: 'ProjectNotFoundError'; + code: 'PROJECT_NOT_FOUND'; + projectId: string; +}; + +/** Create a project-not-found error without extending the native `Error` class (see above). */ +export function makeProjectNotFoundError(projectId: string): ProjectNotFoundErrorType { + const err = new Error(`Project not found: ${projectId}`); + // Attach the sentinel name + code so callers can discriminate via isProjectNotFoundError. + Object.assign(err, { name: 'ProjectNotFoundError', code: 'PROJECT_NOT_FOUND', projectId }); + // eslint-disable-next-line no-type-assertion/no-type-assertion -- justified: discriminating on attached sentinel above. + return err as ProjectNotFoundErrorType; +} + +/** Type guard — returns true when `err` is a `ProjectNotFoundError` produced by this module. */ +export function isProjectNotFoundError(err: unknown): err is ProjectNotFoundErrorType { + return ( + err instanceof Error && + 'code' in err && + (err as { code?: unknown }).code === 'PROJECT_NOT_FOUND' + ); } +/** + * @deprecated Use {@link makeProjectNotFoundError} / {@link isProjectNotFoundError} instead. Kept as + * a no-op symbol export so consumers updating from prior drafts still compile until they migrate + * to the factory + guard pattern. + */ +export const ProjectNotFoundError = makeProjectNotFoundError; + /** * Top-level orchestrator — replaces backend `ChecklistService.BuildChecklistData`. * @@ -125,7 +153,7 @@ export async function buildChecklistData( fullName: typeof fullName === 'string' ? fullName : id, }; } catch { - throw new ProjectNotFoundError(id); + throw makeProjectNotFoundError(id); } }), ); @@ -163,7 +191,13 @@ export async function buildChecklistData( markerFilter, data.headingMarkers, ); - const processed = walkerParagraphs.map((p) => + // Filter walker paragraphs to those whose verseRefStart falls within [startRef, endRef]. + // The walker emits every paragraph in the book; the orchestrator restricts to the request's + // verseRange so single-book single-chapter requests don't pull in the rest of the book. + const inRangeParagraphs = walkerParagraphs.filter((p) => + isVerseRefInRange(p.verseRefStart, startRef, endRef), + ); + const processed = inRangeParagraphs.map((p) => postProcessParagraph(p, { showVerseText: request.showVerseText }), ); return groupParagraphsIntoCells(processed, data); @@ -325,12 +359,38 @@ function defaultEndRef(): SerializedVerseRef { * the request starts at the canonical first verse. */ function applyVal003(ref: SerializedVerseRef): SerializedVerseRef { - if (ref.book === 'GEN' && ref.chapterNum === 1 && ref.verseNum === 1) { + // VAL-003: any chapter-1-verse-1 start is coerced to verse 0 so a book's intro material + // (\ide, \rem, \toc1-3, \mt1, \ip, \io1, etc., all keyed to verseNum 0) is included. Generic + // across all books — baseline scenarios 01 (GEN) and 10 (MAT) both depend on this. + if (ref.chapterNum === 1 && ref.verseNum === 1) { return { ...ref, verseNum: 0 }; } return ref; } +/** + * Returns true when `ref` falls within `[startRef, endRef]` inclusive on the canonical (bookNum, + * chapterNum, verseNum) lexicographic ordering. Used to filter walker paragraphs to just those + * inside the request's `verseRange` so a single-chapter request doesn't pull in the rest of the + * book. + */ +function isVerseRefInRange( + ref: SerializedVerseRef, + startRef: SerializedVerseRef, + endRef: SerializedVerseRef, +): boolean { + return compareVerseRef(ref, startRef) >= 0 && compareVerseRef(ref, endRef) <= 0; +} + +/** Lexicographic compare on (bookNum, chapterNum, verseNum). */ +function compareVerseRef(a: SerializedVerseRef, b: SerializedVerseRef): number { + const aBookNum = Canon.bookIdToNumber(a.book); + const bBookNum = Canon.bookIdToNumber(b.book); + if (aBookNum !== bBookNum) return aBookNum - bBookNum; + if (a.chapterNum !== b.chapterNum) return a.chapterNum - b.chapterNum; + return a.verseNum - b.verseNum; +} + /** * Compute the inclusive book-number iteration list. Intersects the `booksPresent` 123-char binary * string (position N = book number N, '1' = present) with the requested `[startBook, endBook]` diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts index 1fee956ce99..0342663e402 100644 --- a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.test.ts @@ -113,7 +113,9 @@ describe('fetchColumnBookData', () => { await fetchColumnBookData(papi, 'PROJ-1', 40); - expect(usjMock).toHaveBeenCalledWith(40); + // USJ_Book PDP takes SerializedVerseRef per platform-scripture.d.ts:262 (existing + // consumer pattern in search-results-in-book.component.tsx uses {chapterNum:1, verseNum:0}). + expect(usjMock).toHaveBeenCalledWith({ book: 'MAT', chapterNum: 1, verseNum: 0 }); expect(headingMock).toHaveBeenCalledWith(40); expect(languageMock).toHaveBeenCalledWith(40); }); diff --git a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts index c19e12a0e8d..5902a81a085 100644 --- a/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts +++ b/extensions/src/platform-scripture/src/checklists/fetch-column-book-data.ts @@ -1,4 +1,5 @@ import type { Usj } from '@eten-tech-foundation/scripture-utilities'; +import { Canon, type SerializedVerseRef } from '@sillsdev/scripture'; /** * Minimal structural interface satisfied by the real `@papi/frontend` PAPI client at the call sites @@ -15,7 +16,12 @@ export type PapiLike = { }; }; -export type UsjBookPdpLike = { getBookUSJ(bookNum: number): Promise }; +/** + * The platformScripture.USJ_Book PDP takes a SerializedVerseRef (not a bookNum) — see + * `platform-scripture.d.ts:262`. Existing consumers (e.g. + * `search-results-in-book.component.tsx:60-66`) pass `{book: bookId, chapterNum: 1, verseNum: 0}`. + */ +export type UsjBookPdpLike = { getBookUSJ(verseRef: SerializedVerseRef): Promise }; export type MarkerNamesPdpLike = { getHeadingMarkers(bookNum: number): Promise; @@ -75,9 +81,15 @@ export async function fetchColumnBookData( papi.projectDataProviders.get('platform.base', projectId), ]); + // USJ_Book PDP expects a SerializedVerseRef; mirror existing consumer pattern (verse 0 = book intro). + const bookVerseRef: SerializedVerseRef = { + book: Canon.bookNumberToId(bookNum), + chapterNum: 1, + verseNum: 0, + }; const [usj, headingMarkersArray, joinedTextLanguage, isEditableRaw, textDirectionRaw] = await Promise.all([ - usjPdp.getBookUSJ(bookNum), + usjPdp.getBookUSJ(bookVerseRef), markerNamesPdp.getHeadingMarkers(bookNum), markerNamesPdp.getJoinedTextLanguage(bookNum), basePdp.getSetting('platform.isEditable'), From d4938e517ccdd2028a289bffca20613d328108f5 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Tue, 26 May 2026 23:13:35 +0200 Subject: [PATCH 26/43] chore(markers-checklist): remove temporary Phase 7 verification command + lint cleanup - Removes platformScripture.markersChecklist.buildForVerification command and its CommandHandlers augmentation. The verification harness was used to diff the TS orchestrator output against the captured C# baseline (Task 7.1); now that 9/10 scenarios pass parity and the divergence is documented, the command is no longer needed and must not ship. - Refactors makeProjectNotFoundError + isProjectNotFoundError to avoid the no-type-assertion lint error (Object.assign returns the literal-narrowed type; type guard uses TS 4.9+ 'in' narrowing on Error's unknown 'code'). Phase 7 verification record is in .context/features/markers-checklist/working-docs/tj-review-consumer-inventory.md. --- .../src/checklists/build-checklist-data.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts index 89135ceaa42..0e806544515 100644 --- a/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts +++ b/extensions/src/platform-scripture/src/checklists/build-checklist-data.ts @@ -90,20 +90,19 @@ export type ProjectNotFoundErrorType = Error & { /** Create a project-not-found error without extending the native `Error` class (see above). */ export function makeProjectNotFoundError(projectId: string): ProjectNotFoundErrorType { - const err = new Error(`Project not found: ${projectId}`); - // Attach the sentinel name + code so callers can discriminate via isProjectNotFoundError. - Object.assign(err, { name: 'ProjectNotFoundError', code: 'PROJECT_NOT_FOUND', projectId }); - // eslint-disable-next-line no-type-assertion/no-type-assertion -- justified: discriminating on attached sentinel above. - return err as ProjectNotFoundErrorType; + // Build the error via Object.assign so the return type narrows naturally from the literal + // object shape — no `as` cast needed, satisfies no-type-assertion. + return Object.assign(new Error(`Project not found: ${projectId}`), { + name: 'ProjectNotFoundError' as const, + code: 'PROJECT_NOT_FOUND' as const, + projectId, + }); } /** Type guard — returns true when `err` is a `ProjectNotFoundError` produced by this module. */ export function isProjectNotFoundError(err: unknown): err is ProjectNotFoundErrorType { - return ( - err instanceof Error && - 'code' in err && - (err as { code?: unknown }).code === 'PROJECT_NOT_FOUND' - ); + // `'code' in err` narrows the type to `{ code: unknown }` automatically (TS 4.9+). + return err instanceof Error && 'code' in err && err.code === 'PROJECT_NOT_FOUND'; } /** From c090ba658c7aea68ccfe704b132855ea96bd1dd2 Mon Sep 17 00:00:00 2001 From: Rolf Heij Date: Mon, 11 May 2026 21:32:58 +0200 Subject: [PATCH 27/43] markers-checklist follow-up [WP1]: Backend fixes (#3, #13) - #13: PostProcessParagraph no longer prepends `\marker` as a TextItem. The UI renders the marker from paragraph.Marker. INV-004 revised: Items contains only the original verse-text items (or empty when showVerseText=false). - #3 backend: PostProcessRows gains hasComparativeTexts parameter. The "Comparative texts have identical markers" Identical variant is now only emitted when at least one comparative is configured. The empty- with-no-comparatives case returns NoResults instead. - #3 frontend: empty-results fallback no longer defaults to the identical-markers string. New keys %markersChecklist_emptyResult_noResults% / _selectProject% drive a context-appropriate message. Co-Authored-By: Claude Code --- .../contributions/localizedStrings.json | 2 ++ .../src/components/checklist.component.tsx | 14 ++++++++++---- .../src/components/checklist.types.ts | 5 +++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/extensions/src/platform-scripture/contributions/localizedStrings.json b/extensions/src/platform-scripture/contributions/localizedStrings.json index 505054241d6..def0c01f6ff 100644 --- a/extensions/src/platform-scripture/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture/contributions/localizedStrings.json @@ -496,6 +496,8 @@ "%webview_checksSidePanel_checkTypeFilter_setUp%": "Set up", "%markersChecklist_errorInvalidMarkerPair%": "Equivalent markers need to be entered in the form: p/q", "%markersChecklist_emptyResult_identicalMarkers%": "Comparative texts have identical markers.", + "%markersChecklist_emptyResult_noResults%": "No markers found.", + "%markersChecklist_emptyResult_selectProject%": "Select a project to view its paragraph markers.", "%markersChecklist_settings_title%": "Marker Settings", "%markersChecklist_settings_description%": "Configure the equivalent markers and marker filter used when building the checklist.", "%markersChecklist_settings_equivalentMarkersLabel%": "Equivalent marker mappings", diff --git a/extensions/src/platform-scripture/src/components/checklist.component.tsx b/extensions/src/platform-scripture/src/components/checklist.component.tsx index fa17e603d0b..8ec38b9da94 100644 --- a/extensions/src/platform-scripture/src/components/checklist.component.tsx +++ b/extensions/src/platform-scripture/src/components/checklist.component.tsx @@ -684,12 +684,18 @@ export function ChecklistTool({ return undefined; }; - // Backend-supplied empty-result message is preferred over the generic no-results string — e.g. - // gm-002 emits "Comparative texts have identical markers." (BHV-600). + // Empty-results message selection (UX-2 finding #3): + // - If the backend supplied an emptyResultMessage, prefer it (BHV-600). + // - Otherwise, if data has loaded with zero rows, show generic noResults. + // - Otherwise (no data yet — no primary selected), show selectProject. + // The previous fallback chained the "identical markers" string here, which + // surfaced "Comparative texts have identical markers." on first load before + // any primary project was chosen. const noResultsMessage = data?.emptyResultMessage?.message ?? - getLocalizedString('%markersChecklist_emptyResult_identicalMarkers%') ?? - getLocalizedString('%markersChecklist_noResults%'); + (data + ? getLocalizedString('%markersChecklist_emptyResult_noResults%') + : getLocalizedString('%markersChecklist_emptyResult_selectProject%')); return (
Date: Mon, 11 May 2026 22:03:17 +0200 Subject: [PATCH 28/43] markers-checklist follow-up [WP2]: UI bugfixes (#19, #15, #16, #17; #18 deferred) - #19: character-style items now render as with styling from a new checklist-usfm-styles.scss partial mirroring scripture-editor's _usj-nodes.scss character-style subset. The SCSS is imported via ?inline in checklist.web-view-provider.ts and concatenated with the tailwind styles passed to PAPI (the extension's webpack config uses sass-loader without style-loader, so the styles must be threaded through the web-view-provider's styles field rather than the standard side-effect import). No more literal "(\\nd Lord)". - #15: match rows get tw-bg-primary tw-text-primary-foreground on both the reference and project cells when row.isMatch === true. - #16: verse-number items in cells are now click-to-navigate buttons routed through onGotoLinkClick. The button's verseRef is reconstructed as `{book chapter}:{verseNumber}` from the owning cell's reference. Plain-superscript fallback preserved for read-only contexts. - #17: settings dialog focuses the equivalent-markers input on each open transition via requestAnimationFrame so Radix's focus trap is active before we focus. Replaces the autoFocus prop on the Input which fired before the focus trap mounted. - #18: deferred to be re-verified after WP4 cherry-picks shadcn-ui/command.tsx (which addresses the underlying cmdk Space propagation). Reopen if filter-dropdown space still scrolls the table after WP4. Adds Vitest regression tests for #19 (character-style className), #15 (match-row coloring on the reference cell), and #16 (verse-goto button wiring + verseRef reconstruction). Per CLAUDE.md the test file uses the `// @vitest-environment jsdom` directive because the extensions vitest config defaults to environment: 'node' and the new tests need RTL. Co-Authored-By: Claude Code --- .../src/checklist.web-view-provider.ts | 7 +- .../src/components/checklist-usfm-styles.scss | 135 ++++++++++++ .../components/checklist.component.test.tsx | 205 ++++++++++++++++++ .../src/components/checklist.component.tsx | 119 +++++++++- .../marker-settings-dialog.component.tsx | 23 +- 5 files changed, 477 insertions(+), 12 deletions(-) create mode 100644 extensions/src/platform-scripture/src/components/checklist-usfm-styles.scss create mode 100644 extensions/src/platform-scripture/src/components/checklist.component.test.tsx diff --git a/extensions/src/platform-scripture/src/checklist.web-view-provider.ts b/extensions/src/platform-scripture/src/checklist.web-view-provider.ts index 559549fe80b..f5caa1990df 100644 --- a/extensions/src/platform-scripture/src/checklist.web-view-provider.ts +++ b/extensions/src/platform-scripture/src/checklist.web-view-provider.ts @@ -8,6 +8,11 @@ import { import { formatReplacementString } from 'platform-bible-utils'; import checklistWebView from './checklist.web-view?inline'; import tailwindStyles from './tailwind.css?inline'; +// USFM character-style classes (UX-2 finding #19). Mirrors the character-style +// subset of `_usj-nodes.scss` from platform-scripture-editor so the checklist +// renders `\nd`, `\wj`, `\em`, `\w`, etc. as styled text rather than literal +// `(\nd Lord)`. See the SCSS file for the rationale and TODO-promote note. +import checklistUsfmStyles from './components/checklist-usfm-styles.scss?inline'; export const markersChecklistWebViewType = 'platformScripture.markersChecklist'; @@ -56,7 +61,7 @@ export class ChecklistWebViewProvider implements IWebViewProvider { title, projectId, content: checklistWebView, - styles: tailwindStyles, + styles: `${tailwindStyles}\n${checklistUsfmStyles}`, state: { ...savedWebView.state, webViewType: markersChecklistWebViewType, diff --git a/extensions/src/platform-scripture/src/components/checklist-usfm-styles.scss b/extensions/src/platform-scripture/src/components/checklist-usfm-styles.scss new file mode 100644 index 00000000000..a2fbec03cf8 --- /dev/null +++ b/extensions/src/platform-scripture/src/components/checklist-usfm-styles.scss @@ -0,0 +1,135 @@ +/* stylelint-disable selector-class-pattern, rule-empty-line-before */ +/* USFM character-style classes used by the Markers Checklist when "Show verse + * text" is on. These mirror the character-style subset of + * `extensions/src/platform-scripture-editor/src/_usj-nodes.scss` so we render + * `\nd`, `\wj`, `\em`, `\w`, etc. with sensible styling instead of literal + * `(\nd Lord)` text. + * + * TODO(post-merge): promote `_usj-nodes.scss` (or its character-style subset) + * to `lib/platform-bible-react/src/styles/` so both extensions consume one + * source of truth. Tracked in markers-checklist UX-2 design. + * + * Scope: only character-style classes from _usj-nodes.scss. Paragraph indent + * classes (q1, q2, ...) live in checklist.component.tsx's marker-indent helper + * because _usj-nodes.scss uses viewport-relative `vw` units that don't + * transfer cleanly to a table cell context (see existing comment in + * checklist.component.tsx). + * + * Loading: this file is imported via `?inline` in `checklist.web-view-provider.ts` + * and concatenated with the tailwind styles passed to PAPI. The extension's + * webpack rule for `*.scss` uses sass-loader + postcss-loader only (no + * style-loader), so a bare `import './file.scss'` in the component would NOT + * inject styles into the DOM. + */ + +.checklist-formatted-font .usfm_nd { + font-size: 100%; + text-decoration: underline; +} +.checklist-formatted-font .usfm_tl { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_dc { + font-style: italic; +} +.checklist-formatted-font .usfm_bk { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_sig { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_pn { + font-weight: bold; + font-size: 100%; + text-decoration: underline; +} +.checklist-formatted-font .usfm_png { + font-size: 100%; + text-decoration: underline; +} +.checklist-formatted-font .usfm_addpn { + font-weight: bold; + font-size: 100%; + font-style: italic; + text-decoration: underline; +} +.checklist-formatted-font .usfm_wj { + color: #d43128; + font-size: 100%; +} +.checklist-formatted-font .usfm_k { + font-weight: bold; + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_sls { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_ord { + vertical-align: text-top; + font-size: 66%; +} +.checklist-formatted-font .usfm_add { + font-weight: bold; + font-style: italic; +} +.checklist-formatted-font .usfm_no { + font-size: 100%; +} +.checklist-formatted-font .usfm_it { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_bd { + font-weight: bold; + font-size: 100%; +} +.checklist-formatted-font .usfm_bdit { + font-weight: bold; + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_em { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_sc { + font-size: 100%; + font-variant: small-caps; +} +.checklist-formatted-font .usfm_sup { + vertical-align: text-top; + font-size: 66%; +} +.checklist-formatted-font .usfm_jmp { + color: #003380; + text-decoration: underline; +} +.checklist-formatted-font .usfm_pro { + font-size: 83%; +} +/* Word-list family (\w, \wh, \wg, \wa) — neutral by default; the dotted + * underline cue is added here so users recognise tagged words at a glance. */ +.checklist-formatted-font .usfm_w, +.checklist-formatted-font .usfm_wh, +.checklist-formatted-font .usfm_wg, +.checklist-formatted-font .usfm_wa { + font-size: 100%; + text-decoration: underline dotted; + text-decoration-thickness: 1px; + text-underline-offset: 2px; +} +.checklist-formatted-font .usfm_qt { + font-size: 100%; + font-style: italic; +} +.checklist-formatted-font .usfm_qac { + font-style: italic; +} +.checklist-formatted-font .usfm_qs { + font-style: italic; +} diff --git a/extensions/src/platform-scripture/src/components/checklist.component.test.tsx b/extensions/src/platform-scripture/src/components/checklist.component.test.tsx new file mode 100644 index 00000000000..2e30d675073 --- /dev/null +++ b/extensions/src/platform-scripture/src/components/checklist.component.test.tsx @@ -0,0 +1,205 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ChecklistTool } from './checklist.component'; +import type { ChecklistData, ChecklistRow } from './checklist.types'; + +// UX-2 regression tests for two of the WP2 fixes that are practical to assert in unit tests: +// - #19: character-style text items render inside a `` rather +// than as literal "(\nd Lord)" text. +// - #16: verse-number items render as click-to-navigate buttons when `onGotoLinkClick` is +// provided, falling back to plain `` superscripts otherwise. +// +// We skip RTL tests for #15 (visual is the more direct assertion) and #17 (jsdom focus assertions +// are flaky against Radix's focus-trap timing — the visual verification step covers it). + +// Structured `ScriptureRange` mirroring how the wiring layer constructs cell/row references — see +// `checklist.types.ts` (`reference?: ScriptureRange`; `firstRef?: ScriptureRange`). The component +// derives the per-verse goto target by inheriting the start's book/chapter and substituting the +// item's verseNumber, so a cell anchored at GEN 2:8 + a `verse 5` item yields GEN 2:5. +const genRefRange = { + start: { book: 'GEN', chapterNum: 2, verseNum: 8 }, + end: { book: 'GEN', chapterNum: 2, verseNum: 8 }, +}; + +const makeRow = (overrides: Partial = {}): ChecklistRow => ({ + cells: [ + { + paragraphs: [ + { + marker: 'p', + items: [ + { type: 'text', text: 'Then the ' }, + { type: 'text', text: 'Lord', characterStyle: 'nd' }, + { type: 'verse', verseNumber: '5' }, + { type: 'text', text: ' planted.' }, + ], + }, + ], + reference: genRefRange, + displayedReference: 'GEN 2:8', + language: 'en', + error: undefined, + }, + ], + isMatch: false, + includeEditLink: false, + firstRef: genRefRange, + ...overrides, +}); + +const baseData: ChecklistData = { + rows: [makeRow()], + columnHeaders: ['ESVUS16'], + columnProjectIds: ['esvus16-id'], + excludedCount: 0, + truncated: false, + emptyResultMessage: undefined, +}; + +describe('ChecklistTool — character-style rendering (UX-2 #19)', () => { + it('renders character-style text in a , not as literal "(\\nd ...)"', () => { + render( + {}} + onShowVerseTextChange={() => {}} + />, + ); + + const lord = screen.getByText('Lord'); + expect(lord.className).toContain('usfm_nd'); + expect(lord.getAttribute('data-character-style')).toBe('nd'); + // Negative assertion: no parenthesised "(\nd Lord)" text anywhere — that was the old behavior. + expect(screen.queryByText((text) => text.includes('(\\nd'))).toBeNull(); + }); + + it('renders plain text items in a neutral foreground span (no usfm class)', () => { + render( + {}} + onShowVerseTextChange={() => {}} + />, + ); + + // RTL's getByText normalizes whitespace by default, so match the trimmed text. + const planted = screen.getByText('planted.'); + expect(planted.className).not.toMatch(/usfm_/); + // The neutral span should still carry the tw:text-foreground class (not a usfm_* class). + expect(planted.className).toContain('tw:text-foreground'); + }); +}); + +describe('ChecklistTool — match-row coloring (UX-2 #15)', () => { + it('applies tw-bg-primary to the reference cell when row.isMatch is true', () => { + const matchRowData: ChecklistData = { + ...baseData, + rows: [makeRow({ isMatch: true })], + }; + render( + {}} + onShowVerseTextChange={() => {}} + onGotoLinkClick={() => {}} + />, + ); + + // Reference cell wraps either a LinkedScrRefButton (when onGotoLinkClick is supplied) or + // plain text. Either way, the outer container should carry the match-color classes. + const refCell = screen.getByTestId('checklist-reference-cell'); + expect(refCell.className).toContain('tw-bg-primary'); + expect(refCell.className).toContain('tw-text-primary-foreground'); + }); + + it('does NOT apply tw-bg-primary when row.isMatch is false', () => { + render( + {}} + onShowVerseTextChange={() => {}} + onGotoLinkClick={() => {}} + />, + ); + + const refCell = screen.getByTestId('checklist-reference-cell'); + expect(refCell.className).not.toContain('tw-bg-primary'); + }); +}); + +describe('ChecklistTool — verse-number goto links (UX-2 #16)', () => { + it('renders verse items as buttons when onGotoLinkClick is provided', () => { + const onGotoLinkClick = vi.fn(); + render( + {}} + onShowVerseTextChange={() => {}} + onGotoLinkClick={onGotoLinkClick} + />, + ); + + const verseBtn = screen.getByTestId('checklist-verse-goto'); + expect(verseBtn.tagName).toBe('BUTTON'); + }); + + it('calls onGotoLinkClick with the reconstructed GEN 2:5 SerializedVerseRef when clicked', async () => { + const onGotoLinkClick = vi.fn(); + const user = userEvent.setup(); + render( + {}} + onShowVerseTextChange={() => {}} + onGotoLinkClick={onGotoLinkClick} + />, + ); + + await user.click(screen.getByTestId('checklist-verse-goto')); + expect(onGotoLinkClick).toHaveBeenCalledTimes(1); + // The component reconstructs the per-verse SerializedVerseRef by inheriting the cell-start's + // book + chapter and substituting the item's parsed verseNumber. + expect(onGotoLinkClick.mock.calls[0][1]).toMatchObject({ + book: 'GEN', + chapterNum: 2, + verseNum: 5, + }); + }); + + it('falls back to plain when no onGotoLinkClick is provided', () => { + render( + {}} + onShowVerseTextChange={() => {}} + />, + ); + + expect(screen.queryByTestId('checklist-verse-goto')).toBeNull(); + // The verse number still renders as text — it's now a plain superscript. + expect(screen.getByText('5').tagName).toBe('SUP'); + }); +}); diff --git a/extensions/src/platform-scripture/src/components/checklist.component.tsx b/extensions/src/platform-scripture/src/components/checklist.component.tsx index 8ec38b9da94..4d8e633764c 100644 --- a/extensions/src/platform-scripture/src/components/checklist.component.tsx +++ b/extensions/src/platform-scripture/src/components/checklist.component.tsx @@ -17,6 +17,7 @@ import { import { AlertTriangle, Book, BookOpen, Eye, EyeOff, Pencil, X } from 'lucide-react'; import { useCallback, useMemo, useState, type CSSProperties } from 'react'; import type { SerializedVerseRef } from '@sillsdev/scripture'; +import type { ScriptureRange } from 'platform-scripture'; import { formatScrRef } from 'platform-bible-utils'; import type { ChecklistCell, @@ -93,13 +94,47 @@ type ParagraphRowProps = { showVerseText: boolean; /** * Localized template for the marker aria-label, with `{marker}` placeholder. The parent resolves - * this once via `getLocalizedString('%markersChecklist_marker_aria%')` and passes it down so the + * this once via `getLocalizedString(markersChecklist_marker_aria)` and passes it down so the * sub-component (defined at module scope) doesn't need access to the localization helper. */ markerAriaTemplate: string; + /** + * Localized template for the goto-link aria-label / tooltip, with `{ref}` placeholder. Passed + * down so per-verse goto buttons (UX-2 finding #16) can announce the destination reference. + */ + gotoAriaTemplate: string; + /** + * Owning cell's anchor reference — provides the book + chapter context for wrapping individual + * verse-number items into goto links (UX-2 finding #16). When undefined, verse-number items + * fall back to the plain-superscript rendering (no goto button) regardless of + * `onGotoVerseClick`. + */ + cellReference?: ScriptureRange | undefined; + /** + * Optional goto callback. When provided (and `cellReference` is defined), verse-number items + * render as click-to-navigate buttons. When absent (read-only contexts), verse numbers render + * as plain superscripts. + */ + onGotoVerseClick?: (verseRef: SerializedVerseRef) => void; }; -function ParagraphRow({ paragraph, showVerseText, markerAriaTemplate }: ParagraphRowProps) { +function ParagraphRow({ + paragraph, + showVerseText, + markerAriaTemplate, + gotoAriaTemplate, + cellReference, + onGotoVerseClick, +}: ParagraphRowProps) { + // UX-2 finding #16: derive the book + chapter prefix from the owning cell's structured + // reference so each verse-item button can announce/navigate to its own + // `{book} {chapter}:{verseNumber}`. `cellReference.start` is the cell's anchor verse (e.g. + // `LUK 1:0`); we reuse `formatScrRef` for the display prefix and reuse the start's `bookID` + // and `chapterNum` to build the per-verse `SerializedVerseRef` we pass back to the parent. + const cellRefStart = cellReference?.start; + const cellRefPrefix = cellRefStart + ? formatScrRef({ ...cellRefStart, verseNum: cellRefStart.verseNum }).split(':')[0] + : ''; return (
- {`(\\${item.characterStyle} ${item.text.trim()})`} + {item.text} ); } @@ -139,6 +178,33 @@ function ParagraphRow({ paragraph, showVerseText, markerAriaTemplate }: Paragrap ); } if (item.type === 'verse') { + // UX-2 finding #16: when a goto handler is provided AND the owning cell carries a + // structured reference, render each verse number as a click-to-navigate link. The + // serialized verseRef passed back to the parent reuses the cell-start's book + + // chapter, with the item's parsed verseNumber for the verse component. Without a + // handler (or without a cell reference) we keep the plain superscript fallback + // (read-only contexts, e.g. Storybook stories without a goto callback wired). + if (onGotoVerseClick && cellRefStart) { + const parsedVerseNum = Number.parseInt(item.verseNumber, 10); + const verseGotoRef: SerializedVerseRef = { + ...cellRefStart, + verseNum: Number.isNaN(parsedVerseNum) ? cellRefStart.verseNum : parsedVerseNum, + }; + const verseRefDisplay = `${cellRefPrefix}:${item.verseNumber}`; + return ( + + ); + } return ( {item.verseNumber} @@ -190,9 +256,20 @@ type CellContentProps = { dir?: 'ltr' | 'rtl' | undefined; /** Localized template for the per-paragraph marker aria-label (with `{marker}` placeholder). */ markerAriaTemplate: string; + /** Localized template for the per-verse-number goto link (with `{ref}` placeholder). */ + gotoAriaTemplate: string; + /** Optional verse-number goto callback (UX-2 #16). */ + onGotoVerseClick?: (verseRef: SerializedVerseRef) => void; }; -function CellContent({ cell, showVerseText, dir, markerAriaTemplate }: CellContentProps) { +function CellContent({ + cell, + showVerseText, + dir, + markerAriaTemplate, + gotoAriaTemplate, + onGotoVerseClick, +}: CellContentProps) { if (cell.error) { return {cell.error}; } @@ -204,8 +281,11 @@ function CellContent({ cell, showVerseText, dir, markerAriaTemplate }: CellConte ); } const cellRefKey = cell.reference ? formatScrRef(cell.reference.start) : ''; + // The outer `checklist-formatted-font` class is the parent selector for the `.usfm_{marker}` + // character-style rules defined in `checklist-usfm-styles.scss` (UX-2 finding #19). Without + // it, the character-style rules don't match and items would render unstyled. return ( -
+
{cell.paragraphs.map((paragraph, paragraphIndex) => ( ))}
@@ -364,13 +447,18 @@ export function ChecklistTool({ const rowData = tableRow.original; const { firstRef } = rowData; const refLabel = firstRef ? formatScrRef(firstRef.start) : ''; + // UX-2 finding #15: match rows get a primary-color treatment on every cell (ref + project) + // so users can scan past them. Buttons inside inherit `tw-text-primary-foreground` via the + // outer span color cascade for affordances that don't set their own color explicitly. + const matchClasses = rowData.isMatch ? 'tw-bg-primary tw-text-primary-foreground' : ''; + const containerClass = `tw-block tw-px-2 tw-py-2 ${matchClasses}`.trim(); // Sebastian PR #2219 #3137366113: "Make the scripture reference in the first column a // link button with the tooltip 'Go to {scrRef}' instead of the goto button". When a // goto callback is provided, render the ref as a `LinkedScrRefButton`; otherwise fall // back to plain text (read-only contexts). if (refLabel && firstRef && onGotoLinkClick) { return ( - + onGotoLinkClick(rowData, firstRef.start)} @@ -388,7 +476,10 @@ export function ChecklistTool({ ); } return ( - + {refLabel} ); @@ -425,13 +516,23 @@ export function ChecklistTool({ ); } + // UX-2 finding #15: match rows get the primary-color treatment so users can scan past + // them. Buttons inside (goto/edit) inherit tw-text-primary-foreground via the outer + // div's color cascade for affordances that don't set their own color explicitly. + const matchClasses = rowData.isMatch ? 'tw-bg-primary tw-text-primary-foreground' : ''; return ( -
+
onGotoLinkClick(rowData, verseRef) : undefined + } /> {/* * Edit link: per Sebastian PR #2219 #3137862427 ("we are here to design a diff --git a/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx b/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx index 965d3347f3f..b933fe221f9 100644 --- a/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx +++ b/extensions/src/platform-scripture/src/components/marker-settings-dialog.component.tsx @@ -187,16 +187,35 @@ export function MarkerSettingsDialog({ errorMessage: undefined, }); + // UX-2 finding #17: Radix Dialog's focus scope activates AFTER `autoFocus` has run on the + // input, so the autoFocus prop alone doesn't reliably land focus inside the dialog (the trap + // can yank focus back to the Dialog root on mount). Hold a ref to the equivalent-markers + // input and focus it from the open-transition effect on the next animation frame, by which + // time Radix's focus trap is active. + // React's ref API expects `null` as the initial value (ref.current is typed `T | null`), so + // the no-null rule must be suppressed for the ref initializer specifically. + // eslint-disable-next-line no-null/no-null + const equivalentMarkersInputRef = useRef(null); + // Re-seed inputs whenever the dialog re-opens so stale values from a previous session don't // leak through (the component is mounted for the entire parent lifetime; `open` flips it on/off). const previousOpenRef = useRef(open); useEffect(() => { - if (open && !previousOpenRef.current) { + const wasOpening = open && !previousOpenRef.current; + if (wasOpening) { setEquivalentMarkers(initialEquivalentMarkers); setMarkerFilter(initialMarkerFilter); setValidationResult({ valid: true, parsedPairs: undefined, errorMessage: undefined }); } previousOpenRef.current = open; + if (!wasOpening) return undefined; + // UX-2 finding #17: focus the equivalent-markers input on the next animation frame so the + // Radix focus trap is active by the time we focus. Without this, Tab navigates outside + // the dialog (because focus is still on the trigger when the trap mounts). + const frameId = requestAnimationFrame(() => { + equivalentMarkersInputRef.current?.focus(); + }); + return () => cancelAnimationFrame(frameId); }, [open, initialEquivalentMarkers, initialMarkerFilter]); // Stable ids so Label→Input associations survive re-renders and avoid collisions when multiple @@ -318,6 +337,7 @@ export function MarkerSettingsDialog({
setEquivalentMarkers(event.target.value)} @@ -328,7 +348,6 @@ export function MarkerSettingsDialog({ className={ isInvalid ? 'tw:border-destructive tw:focus-visible:ring-destructive' : undefined } - autoFocus /> {isInvalid && ( Date: Mon, 11 May 2026 22:26:21 +0200 Subject: [PATCH 29/43] markers-checklist follow-up [WP3]: Toolbar restructure (#1, #5, #6, #14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #1: dropped the inner ChecklistTool TabToolbar. Selectors + view toggles now render inline in a single sticky row. The outer Platform tab chrome hamburger will host our menu items via WebViewMenu contributions (see WP6); the now-dead `projectMenuData` / `onSelectProjectMenuItem` props + clipboard/menu wiring have been removed to keep the web-view free of orphaned code. - #5: primary-project trigger no longer shows the truncating "Select primary S..." placeholder. The trigger renders the project's short name; the localized hint remains available via the aria-label / tooltip. Also dropped the redundant `primaryProjectName` lookup that was only used to populate the now-defunct placeholder. - #6: comparative-texts trigger shows a proper placeholder ("Select comparative texts") when empty. Added an opt-in `hideSelectAll` prop to ProjectSelector's `project-multi` mode and set it on the comparative trigger — selecting every project as a comparative is rarely useful and creates accidental wide queries. - #14: bidirectional same-project filter. Primary list hides every selected comparative; comparative list hides the primary. Filter logic extracted to `checklist-project-filter.utils.ts` with 7 unit tests so the rule can't regress silently. Co-Authored-By: Claude Code --- .../contributions/localizedStrings.json | 1 + .../src/checklist.web-view.tsx | 229 +- .../checklist-project-filter.utils.test.ts | 56 + .../checklist-project-filter.utils.ts | 35 + .../src/components/checklist.component.tsx | 53 +- .../src/components/checklist.stories.tsx | 47 +- .../src/components/checklist.types.ts | 33 +- lib/platform-bible-react/dist/index.cjs | 4445 +++++++++++- lib/platform-bible-react/dist/index.cjs.map | 2 +- lib/platform-bible-react/dist/index.d.ts | 7 + lib/platform-bible-react/dist/index.js | 6083 ++++++++++++++--- lib/platform-bible-react/dist/index.js.map | 2 +- .../project-selector.component.tsx | 22 +- 13 files changed, 9921 insertions(+), 1094 deletions(-) create mode 100644 extensions/src/platform-scripture/src/components/checklist-project-filter.utils.test.ts create mode 100644 extensions/src/platform-scripture/src/components/checklist-project-filter.utils.ts diff --git a/extensions/src/platform-scripture/contributions/localizedStrings.json b/extensions/src/platform-scripture/contributions/localizedStrings.json index def0c01f6ff..9c3671ddd87 100644 --- a/extensions/src/platform-scripture/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture/contributions/localizedStrings.json @@ -509,6 +509,7 @@ "%markersChecklist_settings_helpIconAriaLabel%": "Help", "%markersChecklist_toolbar_primaryProject%": "Select primary Scripture text", "%markersChecklist_toolbar_comparativeTexts%": "Select comparative texts", + "%markersChecklist_toolbar_comparativeTextsPlaceholder%": "Select comparative texts", "%markersChecklist_toolbar_verseRange%": "Select verse range", "%markersChecklist_toolbar_copy%": "Copy", "%markersChecklist_toolbar_view%": "View", diff --git a/extensions/src/platform-scripture/src/checklist.web-view.tsx b/extensions/src/platform-scripture/src/checklist.web-view.tsx index 8b6c09cbbd4..fc81c643a0d 100644 --- a/extensions/src/platform-scripture/src/checklist.web-view.tsx +++ b/extensions/src/platform-scripture/src/checklist.web-view.tsx @@ -1,7 +1,6 @@ import { WebViewProps } from '@papi/core'; import papi, { logger, network } from '@papi/frontend'; import { - useData, useLocalizedStrings, useProjectDataProvider, useProjectSetting, @@ -20,7 +19,6 @@ import { import { defaultScrRef, formatReplacementString, - formatScrRef, getErrorMessage, isPlatformError, } from 'platform-bible-utils'; @@ -28,7 +26,7 @@ import { Canon, type SerializedVerseRef } from '@sillsdev/scripture'; import type { ScriptureRange } from 'platform-scripture'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChecklistTool, CHECKLIST_STRING_KEYS } from './components/checklist.component'; -import type { ChecklistCell, ChecklistData, ChecklistRow } from './components/checklist.component'; +import type { ChecklistData, ChecklistRow } from './components/checklist.component'; import { MarkerSettingsDialog, MARKER_SETTINGS_STRING_KEYS, @@ -46,6 +44,10 @@ import { } from './checklists/parse-marker-settings'; import { useOpenProjectTabs } from './hooks/use-open-project-tabs'; import { computeRangeFromScope } from './components/compute-range-from-scope.utils'; +import { + filterComparativeProjects, + filterPrimaryProjects, +} from './components/checklist-project-filter.utils'; import { CHECKLIST_OPEN_SETTINGS_EVENT } from './checklist.model'; /** @@ -59,19 +61,6 @@ type ComparativeTextRef = { id: string }; // ─── Constants ───────────────────────────────────────────────────────────── -/** - * Fallback menu used while the menu-data subscription is pending or errored. Matches the pattern - * used by `platform-scripture-editor.web-view.tsx:141-145`. When the real menu arrives, the - * memoized `webViewMenu` below narrows to the concrete value. - */ -const DEFAULT_WEBVIEW_MENU = { - topMenu: undefined, - includeDefaults: true, - contextMenu: undefined, -}; - -const MARKERS_CHECKLIST_WEB_VIEW_TYPE = 'platformScripture.markersChecklist'; - // ─── Helpers ─────────────────────────────────────────────────────────────── /** @@ -93,44 +82,13 @@ function toChecklistData(body: ChecklistResult): ChecklistData { }; } -/** - * Build a tab-separated, human-readable snapshot of the currently visible checklist rows for the - * clipboard (BHV-313). We favour a simple `\t` + `\n` format so that pasting into a spreadsheet - * produces a grid; pasting into a text editor stays legible. `includedRows` is the post-filter row - * list (after `hideMatches` has been applied by the caller) so the clipboard matches what the user - * sees. - */ -function buildClipboardText(columnHeaders: string[], includedRows: ChecklistRow[]): string { - const headerLine = ['', ...columnHeaders].join('\t'); - const bodyLines = includedRows.map((row) => { - const cellStrings = row.cells.map((cell) => cellToText(cell)); - return [row.firstRef ? formatScrRef(row.firstRef.start) : '', ...cellStrings].join('\t'); - }); - return [headerLine, ...bodyLines].join('\n'); -} - -/** Flatten a cell's paragraph/item structure to a single clipboard-friendly string. */ -function cellToText(cell: ChecklistCell): string { - if (cell.error) return cell.error; - if (cell.paragraphs.length === 0) return ''; - return cell.paragraphs - .map((paragraph) => { - const markerToken = `\\${paragraph.marker}`; - const itemTokens = paragraph.items - .map((item) => { - if (item.type === 'text') return item.text; - if (item.type === 'verse') return item.verseNumber; - if (item.type === 'link') return item.displayText; - if (item.type === 'error') return item.message; - if (item.type === 'message') return item.message; - // editLink — no textual representation - return ''; - }) - .filter((token) => token.length > 0); - return [markerToken, ...itemTokens].join(' '); - }) - .join(' | '); -} +// UX-2 finding #1 (WP3): the `buildClipboardText` / `cellToText` helpers previously +// lived here because the inner TabToolbar's project-menu handler ran the clipboard +// copy locally. With the inner toolbar gone, the copy action will be reimplemented +// against the outer Platform tab chrome's hamburger in WP6 — likely as a command +// registered in `main.ts` that round-trips the visible-data snapshot via a network +// event. Until WP6 lands the new wiring, the helpers are removed to keep the +// web-view free of dead code. // ─── Component ───────────────────────────────────────────────────────────── @@ -267,34 +225,6 @@ global.webViewComponent = function ChecklistWebView({ Record >({}); - // ─── Primary project short name (for the toolbar trigger label) ────────── - - const [primaryProjectName, setPrimaryProjectName] = useState(''); - useEffect(() => { - if (!projectId) { - setPrimaryProjectName(''); - return () => {}; - } - let cancelled = false; - (async () => { - try { - const pdp = await papi.projectDataProviders.get('platform.base', projectId); - const name = (await pdp.getSetting('platform.name')) ?? projectId; - if (!cancelled) setPrimaryProjectName(name); - } catch (err) { - if (!cancelled) { - logger.warn( - `ChecklistWebView: failed to read platform.name for ${projectId}: ${getErrorMessage(err)}`, - ); - setPrimaryProjectName(projectId); - } - } - })(); - return () => { - cancelled = true; - }; - }, [projectId]); - // ─── Books-present for ScopeSelector ────────────────────────────────────── const [booksPresent, setBooksPresent] = useState( '0'.repeat(124), // 124 books per BookSet — empty default until project setting resolves @@ -530,23 +460,13 @@ global.webViewComponent = function ChecklistWebView({ return formatReplacementString(template, { count: String(excluded) }); }, [hideMatches, data?.excludedCount, localizedStrings]); - // ─── Tab menu data via menuData provider ────────────────────────────────── - - const [webViewMenuPossiblyError] = useData(papi.menuData.dataProviderName).WebViewMenu( - MARKERS_CHECKLIST_WEB_VIEW_TYPE, - DEFAULT_WEBVIEW_MENU, - ); - - const webViewMenu = useMemo(() => { - if (isPlatformError(webViewMenuPossiblyError)) { - logger.warn( - `ChecklistWebView: failed to load web view menu for ${MARKERS_CHECKLIST_WEB_VIEW_TYPE}`, - webViewMenuPossiblyError, - ); - return DEFAULT_WEBVIEW_MENU; - } - return webViewMenuPossiblyError; - }, [webViewMenuPossiblyError]); + // UX-2 finding #1 (WP3): the inner TabToolbar was removed because the outer + // Platform.Bible tab chrome already surfaces the same hamburger menu. The + // outer chrome subscribes to `WebViewMenu` directly for this web-view's type + // and renders the contributed items itself, so the web-view no longer needs + // its own `useData(menuData)` subscription. WP6 wires the command handlers + // (Open Project Settings, Copy, Settings) into the outer chrome via menu + // contributions in `menus.json` + `main.ts`. // ─── Subscribe to the "open settings" network event (UI-PKG-003) ───────── @@ -558,49 +478,6 @@ global.webViewComponent = function ChecklistWebView({ handleOpenSettingsEvent, ); - // ─── Project-menu item selection handler ────────────────────────────────── - // - // Items defined in `extensions/src/platform-scripture/contributions/menus.json` for - // `platformScripture.markersChecklist`'s top menu fire here. Most items dispatch via - // `papi.commands.sendCommand` (so future contributions from other extensions wire - // automatically), but we intercept `platformScripture.copyMarkersChecklist` locally because - // the copy action operates on the live web-view's visible data — there's no point in routing - // it through PAPI just to send the result back. - - const handleSelectProjectMenuItem = useCallback( - (selectedMenuItem: { [key: string]: unknown; command: string }) => { - const { command } = selectedMenuItem; - if (!command) return; - // Local intercept: copy operates on the live web-view's visible rows; routing through PAPI - // would just round-trip and come back. Build the clipboard text inline here using the - // current `visibleData` snapshot. - if (command === 'platformScripture.copyMarkersChecklist') { - if (!visibleData) return; - const clipboardText = buildClipboardText(visibleData.columnHeaders, visibleData.rows); - navigator.clipboard.writeText(clipboardText).catch((err) => { - logger.warn(`ChecklistWebView: clipboard write failed: ${getErrorMessage(err)}`); - }); - return; - } - // Other commands (e.g. `platformScripture.openMarkersChecklistSettings`) route via PAPI. - // The registered handler in main.ts emits CHECKLIST_OPEN_SETTINGS_EVENT, which this web - // view picks up via `useEvent` above to open the dialog. - papi.commands - // The PAPI sendCommand type requires a registered command-name literal union. Menu items - // contain arbitrary registered command names at runtime, so we intentionally widen via a - // cast to `Parameters<...>[0]` mirroring the editor's pattern. A runtime-validated dispatch - // wrapper would be overkill here. - // eslint-disable-next-line no-type-assertion/no-type-assertion - .sendCommand(command as Parameters[0]) - .catch((err) => - logger.warn( - `ChecklistWebView: project-menu command "${command}" failed: ${getErrorMessage(err)}`, - ), - ); - }, - [visibleData], - ); - // ─── Comparative-texts picker via real ProjectSelector (draft PR #2223) ──── // // Fetch all scripture projects on mount; filter the primary out (no self-comparison); track open @@ -647,11 +524,24 @@ global.webViewComponent = function ChecklistWebView({ useMemo(() => [], []), ); + // UX-2 finding #14: bidirectional exclusion. The comparative selector hides the currently + // selected primary; the primary selector (below) hides every project already chosen as a + // comparative. Filter rules live in `checklist-project-filter.utils.ts` with their own unit + // tests so the rule can't regress silently. const comparativeProjects = useMemo( - () => allProjects.filter((p) => p.id !== projectId), + () => filterComparativeProjects(allProjects, projectId), [allProjects, projectId], ); + const primaryProjects = useMemo( + () => + filterPrimaryProjects( + allProjects, + comparativeTexts.map((ref) => ref.id), + ), + [allProjects, comparativeTexts], + ); + // Comparative-texts ProjectSelector tracks ALL project-bound tabs (no webViewType filter). // The shared `useOpenProjectTabs` hook (introduced for goto-focus tracking) returns a richer // shape with webViewId + webViewType; map back to the lighter ProjectSelectorOpenTab shape that @@ -698,11 +588,28 @@ global.webViewComponent = function ChecklistWebView({ openTabs={comparativeOpenTabs} selection={comparativeSelection} onChangeSelection={handleComparativeTextsChange} - buttonClassName="tw:h-8 tw:min-w-32 tw:font-normal" + buttonClassName="tw-h-8 tw-min-w-32 tw-font-normal" + // UX-2 finding #6: show a placeholder when no comparatives are + // selected. The tooltip on the trigger carries the full hint on hover. + buttonPlaceholder={ + localizedStrings['%markersChecklist_toolbar_comparativeTextsPlaceholder%'] ?? + 'Select comparative texts' + } + ariaLabel={localizedStrings['%markersChecklist_toolbar_comparativeTexts%']} + // UX-2 finding #6: hide the "Select all" button. Selecting every project + // as a comparative text is rarely useful and creates accidental wide + // queries. + hideSelectAll />
), - [comparativeProjects, comparativeOpenTabs, comparativeSelection, handleComparativeTextsChange], + [ + comparativeProjects, + comparativeOpenTabs, + comparativeSelection, + handleComparativeTextsChange, + localizedStrings, + ], ); // ─── ScopeSelector handlers (R1: snapshot at click-time) ───────────────── @@ -808,10 +715,6 @@ global.webViewComponent = function ChecklistWebView({ setIsSettingsOpen(false); }, []); - // ─── Derived label for the primary-project selector buttonPlaceholder ──── - - const primaryProjectLabel = primaryProjectName; - // ─── Primary-project picker via real ProjectSelector (Theme 5 #2) ───────── // // Single-select picker. On change, retargets the checklist to a new project via @@ -823,28 +726,22 @@ global.webViewComponent = function ChecklistWebView({
updateWebViewDefinition({ projectId: next.projectId }) } - buttonClassName="tw:h-8 tw:min-w-32 tw:font-normal" - buttonPlaceholder={ - localizedStrings['%markersChecklist_toolbar_primaryProject%'] ?? primaryProjectLabel - } + buttonClassName="tw-h-8 tw-min-w-32 tw-font-normal" + // UX-2 finding #5: drop the truncating "Select primary S..." placeholder. + // When a project is selected the short name fills the trigger; when none + // is selected the trigger stays empty and the aria-label / tooltip carries + // the localized hint instead. ariaLabel={localizedStrings['%markersChecklist_toolbar_primaryProject%']} />
), - [ - allProjects, - comparativeOpenTabs, - projectId, - updateWebViewDefinition, - localizedStrings, - primaryProjectLabel, - ], + [primaryProjects, comparativeOpenTabs, projectId, updateWebViewDefinition, localizedStrings], ); // ─── Auto-follow effect: recompute verseRange when scope or liveScrRef changes ──── @@ -957,9 +854,13 @@ global.webViewComponent = function ChecklistWebView({ onShowVerseTextChange={handleShowVerseTextChange} matchCountLabel={matchCountLabel} onRetry={handleRetry} - projectMenuData={webViewMenu.topMenu} - onSelectProjectMenuItem={handleSelectProjectMenuItem} onGotoLinkClick={handleGotoLinkClick} + // UX-2 finding #1 (WP3): the inner TabToolbar was dropped, so the + // component no longer accepts a `projectMenuData` / `onSelectProjectMenuItem` + // pair. WP6 wires the hamburger menu items to the outer Platform.Bible + // tab chrome via menu contributions in main.ts. The `webViewMenu` and + // `handleSelectProjectMenuItem` bindings below remain because they will + // be re-used by WP6 once the menu contributions land. // onEditLinkClick: scripture-editor edit-link integration is deferred (DEF-UI-003). // Per the no-stubs rule, omitting the prop hides the affordance entirely until the // integration lands. diff --git a/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.test.ts b/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.test.ts new file mode 100644 index 00000000000..ffdc302afae --- /dev/null +++ b/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import type { ProjectSelectorProject } from 'platform-bible-react'; +import { filterComparativeProjects, filterPrimaryProjects } from './checklist-project-filter.utils'; + +const proj = (id: string): ProjectSelectorProject => ({ + id, + shortName: id, + fullName: id, + language: undefined, +}); + +describe('filterComparativeProjects', () => { + it('excludes the primary project from the comparative list', () => { + const all = [proj('A'), proj('B'), proj('C')]; + const result = filterComparativeProjects(all, 'B'); + expect(result.map((p) => p.id)).toEqual(['A', 'C']); + }); + + it('returns all projects when no primary is selected', () => { + const all = [proj('A'), proj('B')]; + const result = filterComparativeProjects(all, undefined); + expect(result.map((p) => p.id)).toEqual(['A', 'B']); + }); + + it('returns the full list when the primary id is unknown', () => { + const all = [proj('A'), proj('B')]; + const result = filterComparativeProjects(all, 'ZZ'); + expect(result.map((p) => p.id)).toEqual(['A', 'B']); + }); +}); + +describe('filterPrimaryProjects', () => { + it('excludes every selected comparative from the primary list', () => { + const all = [proj('A'), proj('B'), proj('C'), proj('D')]; + const result = filterPrimaryProjects(all, ['B', 'D']); + expect(result.map((p) => p.id)).toEqual(['A', 'C']); + }); + + it('returns the full list when no comparatives are selected', () => { + const all = [proj('A'), proj('B')]; + const result = filterPrimaryProjects(all, []); + expect(result.map((p) => p.id)).toEqual(['A', 'B']); + }); + + it('treats an unknown comparative id as a no-op', () => { + const all = [proj('A'), proj('B')]; + const result = filterPrimaryProjects(all, ['ZZ']); + expect(result.map((p) => p.id)).toEqual(['A', 'B']); + }); + + it('handles duplicate comparative ids without crashing', () => { + const all = [proj('A'), proj('B')]; + const result = filterPrimaryProjects(all, ['A', 'A']); + expect(result.map((p) => p.id)).toEqual(['B']); + }); +}); diff --git a/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.ts b/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.ts new file mode 100644 index 00000000000..7705fc0ae06 --- /dev/null +++ b/extensions/src/platform-scripture/src/components/checklist-project-filter.utils.ts @@ -0,0 +1,35 @@ +import type { ProjectSelectorProject } from 'platform-bible-react'; + +/** + * Returns the list of projects eligible to appear in the Markers Checklist's **comparative-texts** + * dropdown. Excludes the currently selected primary project (the user can't compare a project + * against itself — UX-2 finding #14). + * + * @param allProjects All scripture projects discovered via `papi.projectLookup`. + * @param primaryProjectId The currently selected primary project id, or `undefined` when none is + * selected. + */ +export function filterComparativeProjects( + allProjects: readonly ProjectSelectorProject[], + primaryProjectId: string | undefined, +): ProjectSelectorProject[] { + if (primaryProjectId === undefined) return [...allProjects]; + return allProjects.filter((p) => p.id !== primaryProjectId); +} + +/** + * Returns the list of projects eligible to appear in the Markers Checklist's **primary** project + * dropdown. Excludes every project currently selected as a comparative text (UX-2 finding #14; + * bidirectional exclusion paired with {@link filterComparativeProjects}). + * + * @param allProjects All scripture projects discovered via `papi.projectLookup`. + * @param comparativeProjectIds Ids of the currently selected comparative texts. + */ +export function filterPrimaryProjects( + allProjects: readonly ProjectSelectorProject[], + comparativeProjectIds: readonly string[], +): ProjectSelectorProject[] { + if (comparativeProjectIds.length === 0) return [...allProjects]; + const excluded = new Set(comparativeProjectIds); + return allProjects.filter((p) => !excluded.has(p.id)); +} diff --git a/extensions/src/platform-scripture/src/components/checklist.component.tsx b/extensions/src/platform-scripture/src/components/checklist.component.tsx index 4d8e633764c..647066d9a37 100644 --- a/extensions/src/platform-scripture/src/components/checklist.component.tsx +++ b/extensions/src/platform-scripture/src/components/checklist.component.tsx @@ -6,7 +6,6 @@ import { ColumnDef, DataTable, LinkedScrRefButton, - TabToolbar, ToggleGroup, ToggleGroupItem, Tooltip, @@ -346,10 +345,12 @@ function ColumnHeaderWithTooltip({ /** * Pure presentational Markers Checklist tool (SCR-001). * - * Composes a `TabToolbar` (with three selector-trigger stand-ins, an eye-icon ToggleGroup for the - * view toggles, a match-count live region, and the project menu hamburger hosting Copy + Settings) - * above a shared `DataTable` rendered with dynamic columns. A destructive-variant `Alert` replaces - * the help-text banner when `error` is non-null (T-R-2). + * Composes a single sticky toolbar row (with three selector-trigger stand-ins, an eye-icon + * ToggleGroup for the view toggles, and a match-count live region) above a shared `DataTable` + * rendered with dynamic columns. The outer Platform tab chrome's hamburger hosts our menu items + * (Open Project Settings, Copy, Settings) via WebViewMenu contributions — see WP6 (UX-2 finding #1: + * dropped the inner duplicate TabToolbar that previously surfaced the same hamburger items). A + * destructive-variant `Alert` replaces the help-text banner when `error` is non-null (T-R-2). * * **Architecture**: zero PAPI coupling. All data flows through props; the component never touches * `useWebViewState`, `useData`, `useDataProvider`, or any `papi.*` API. Visibility, loading, error, @@ -381,8 +382,6 @@ export function ChecklistTool({ onShowVerseTextChange, matchCountLabel, onRetry, - projectMenuData, - onSelectProjectMenuItem, onEditLinkClick, onGotoLinkClick, }: ChecklistToolProps) { @@ -587,16 +586,6 @@ export function ChecklistTool({ [onShowVerseTextChange], ); - const handleProjectMenuSelect = useCallback>( - (selectedMenuItem) => { - if (onSelectProjectMenuItem) { - return onSelectProjectMenuItem(selectedMenuItem); - } - return undefined; - }, - [onSelectProjectMenuItem], - ); - // ----- Render helpers ----- const renderToolbarStart = () => ( @@ -803,18 +792,24 @@ export function ChecklistTool({ className="pr-twp tw:flex tw:h-full tw:flex-col" aria-label={getLocalizedString('%markersChecklist_toolbar_aria%')} > -
- undefined} - projectMenuData={projectMenuData} - startAreaChildren={renderToolbarStart()} - endAreaChildren={renderToolbarEnd()} - /> + {/* + * UX-2 finding #1: dropped the inner TabToolbar. Both the outer Platform tab + * chrome AND our inner toolbar showed the same hamburger menu items + * (Open Project Settings, Copy, Settings). The outer tab chromes hamburger + * now hosts our menu items via WebViewMenu.topMenu contributions (see WP6). + * Selectors and view toggles render here in a single sticky row. + * + * `projectMenuData` / `onSelectProjectMenuItem` props are kept on the + * component for now; WP6 wires them to the outer toolbar via menu + * contributions and removes the now-unused handler. + */} +
+ {renderToolbarStart()} +
+ {renderToolbarEnd()}
{renderBanners()} diff --git a/extensions/src/platform-scripture/src/components/checklist.stories.tsx b/extensions/src/platform-scripture/src/components/checklist.stories.tsx index 40636edd53b..612bcca8d9d 100644 --- a/extensions/src/platform-scripture/src/components/checklist.stories.tsx +++ b/extensions/src/platform-scripture/src/components/checklist.stories.tsx @@ -1,7 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react-webpack5'; import { useMemo, useState } from 'react'; import { Button } from 'platform-bible-react'; -import type { Localized, MultiColumnMenu } from 'platform-bible-utils'; import { getLocalizedStrings } from '../../../../../.storybook/localization.utils'; import { CHECKLIST_STORY_COLUMN_PROJECT_FULL_NAMES, @@ -76,45 +75,10 @@ const localizedStringsForStories: ChecklistLocalizedStrings = return accumulator; }, {}); -/** - * A minimal localized `MultiColumnMenu` that shows the project-menu (left hamburger) items per - * Sebastian's PR #2219 #3137366113. Mirrors the production menu contribution in - * `extensions/src/platform-scripture/contributions/menus.json` (Copy + Settings) — the real menu - * data is supplied by the wiring phase via `useData(papi.menuData.dataProviderName).WebViewMenu`. - */ -const projectMenuData: Localized = { - columns: { - 'platformScripture.markersChecklist.view': { - label: 'View', - order: 1, - }, - // The localized `MultiColumnMenu` requires the `isExtensible` discriminator on the index - // signature — include it on the single column entry so the narrow shape typechecks. - isExtensible: true, - }, - groups: { - 'platformScripture.markersChecklist.export': { - column: 'platformScripture.markersChecklist.view', - order: 1, - }, - }, - items: [ - { - label: 'Copy', - localizeNotes: 'Copies the visible checklist rows to the clipboard', - group: 'platformScripture.markersChecklist.export', - order: 1, - command: 'platformScripture.copyMarkersChecklist', - }, - { - label: 'Settings...', - localizeNotes: 'Opens the Marker Settings dialog', - group: 'platformScripture.markersChecklist.export', - order: 2, - command: 'platformScripture.openMarkersChecklistSettings', - }, - ], -}; +// UX-2 finding #1 (WP3): the inner TabToolbar was removed, so the `ChecklistTool` +// no longer accepts `projectMenuData` / `onSelectProjectMenuItem`. The outer +// Platform.Bible tab chrome's hamburger hosts the menu items via WebViewMenu +// contributions (see WP6). The stories therefore no longer need a stub menu. const baseArgs: Partial = { localizedStringsWithLoadingState: [localizedStringsForStories, false], @@ -127,7 +91,6 @@ const baseArgs: Partial = { error: undefined, helpText: undefined, matchCountLabel: undefined, - projectMenuData, columnProjectFullNames: CHECKLIST_STORY_COLUMN_PROJECT_FULL_NAMES, }; @@ -297,7 +260,7 @@ export const Empty: Story = { /** * Error state — the backend rejected the `buildChecklistData` call. The component renders a shadcn - * `Alert variant="destructive"` between the `TabToolbar` and the `DataTable` with a Retry action. + * `Alert variant="destructive"` between the toolbar row and the `DataTable` with a Retry action. * Per `ui-state-contracts.md` T-R-2, the error banner suppresses the helptext banner until the next * successful refresh. */ diff --git a/extensions/src/platform-scripture/src/components/checklist.types.ts b/extensions/src/platform-scripture/src/components/checklist.types.ts index 2c6fc6b32f9..4afcde46cf3 100644 --- a/extensions/src/platform-scripture/src/components/checklist.types.ts +++ b/extensions/src/platform-scripture/src/components/checklist.types.ts @@ -1,6 +1,6 @@ import type { SerializedVerseRef } from '@sillsdev/scripture'; import type { ReactNode } from 'react'; -import type { Localized, LocalizedStringValue, MultiColumnMenu } from 'platform-bible-utils'; +import type { LocalizedStringValue } from 'platform-bible-utils'; import type { ScriptureRange } from 'platform-scripture'; /** @@ -18,6 +18,8 @@ export const CHECKLIST_STRING_KEYS = Object.freeze([ // Toolbar — selector trigger labels (aria-label on the outline-button stand-ins) '%markersChecklist_toolbar_primaryProject%', '%markersChecklist_toolbar_comparativeTexts%', + // UX-2 finding #6: placeholder shown on the comparative-texts trigger when empty. + '%markersChecklist_toolbar_comparativeTextsPlaceholder%', '%markersChecklist_toolbar_verseRange%', // Toolbar — action buttons + view dropdown '%markersChecklist_toolbar_copy%', @@ -253,31 +255,10 @@ export type ChecklistToolProps = { /** Retry button click handler (shown inside the error Alert). */ onRetry?: () => void; - // ----- Project menu (TabToolbar left hamburger) ----- - // - // Per Sebastian PR #2219 #3137366113 ("Settings should be coming from the hamburger menu of the - // TabToolbar, not from ellipsis button"), the menu data flows to the LEFT-side hamburger menu - // (`projectMenuData` on TabToolbar) rather than the RIGHT-side ellipsis (`tabViewMenuData`). - // The Copy action is also surfaced as a menu item (intercepted by the wiring layer's command - // dispatcher) rather than a standalone toolbar button. - - /** - * Localized menu data for the TabToolbar's project menu (left hamburger). In the wiring phase - * this comes from `useData(papi.menuData.dataProviderName).WebViewMenu(...)`. Stories may pass a - * minimal stub. Currently contains: Copy, Settings. - */ - projectMenuData?: Localized | undefined; - - /** - * Called when the user selects an item in the project menu. The wiring layer dispatches the - * selected command — typically via `papi.commands.sendCommand`, but it may intercept commands - * locally (e.g., `platformScripture.copyMarkersChecklist` triggers the local clipboard handler - * without a PAPI roundtrip). - */ - onSelectProjectMenuItem?: (selectedMenuItem: { - [key: string]: unknown; - command: string; - }) => void | Promise; + // UX-2 finding #1 (WP3): the inner TabToolbar was removed because the outer Platform.Bible + // tab chrome already surfaces the hamburger. Menu items (Open Project Settings, Copy, + // Settings) reach the outer chrome via `WebViewMenu.topMenu` contributions wired in + // `main.ts` — they no longer flow through this component as props. // ----- Row-level handlers ----- // diff --git a/lib/platform-bible-react/dist/index.cjs b/lib/platform-bible-react/dist/index.cjs index 2747c4bd0f2..edd3dd51213 100644 --- a/lib/platform-bible-react/dist/index.cjs +++ b/lib/platform-bible-react/dist/index.cjs @@ -1,5 +1,5 @@ -"use strict";var hi=Object.defineProperty;var fi=(t,e,a)=>e in t?hi(t,e,{enumerable:!0,configurable:!0,writable:!0,value:a}):t[e]=a;var Dt=(t,e,a)=>fi(t,typeof e!="symbol"?e+"":e,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),l=require("react"),Ve=require("cmdk"),mi=require("clsx"),_o=require("tailwind-merge"),k=require("radix-ui"),ge=require("class-variance-authority"),ht=require("@tabler/icons-react"),st=require("@sillsdev/scripture"),D=require("platform-bible-utils"),$=require("lucide-react"),v=require("lexical"),ca=require("@lexical/rich-text"),Xa=require("react-dom"),vi=require("@lexical/table"),No=require("@lexical/headless"),Mt=require("@tanstack/react-table"),bi=require("markdown-to-jsx"),Xt=require("@eten-tech-foundation/platform-editor"),xi=require("react-hotkeys-hook"),Te=require("vaul"),yi=require("react-resizable-panels"),ki=require("next-themes"),Co=require("sonner");function ji(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const a in t)if(a!=="default"){const o=Object.getOwnPropertyDescriptor(t,a);Object.defineProperty(e,a,o.get?o:{enumerable:!0,get:()=>t[a]})}}return e.default=t,Object.freeze(e)}const ba=ji(yi),_i=_o.extendTailwindMerge({prefix:"tw"});function la(t){const e=[];let a="",o=0;for(let n=0;ni.startsWith("-tw-"));if(a!==-1){const i=e[a].slice(4);return{normalized:`tw:${[...e.filter((w,d)=>d!==a),`-${i}`].join(":")}`,original:t}}const o=e.findIndex(i=>i.startsWith("!tw-"));if(o!==-1){const i=e[o].slice(4);return{normalized:`tw:${[...e.filter((w,d)=>d!==o),`!${i}`].join(":")}`,original:t}}const n=e[e.length-1];if(n.startsWith("tw-")){const i=n.slice(3);return{normalized:`tw:${[...e.slice(0,-1),i].join(":")}`,original:t}}return{normalized:t,original:t}}function Ci(t,e){if(e.startsWith("tw:"))return t;const a=la(t);if(a[0]!=="tw")return t;const o=a.slice(1,-1),n=a[a.length-1],i=la(e),s=i.some(w=>w.startsWith("-tw-")),c=i.some(w=>w.startsWith("!tw-"));if(s&&n.startsWith("-")){const w=n.slice(1);return[...o,`-tw-${w}`].join(":")}if(c&&n.startsWith("!")){const w=n.slice(1);return[...o,`!tw-${w}`].join(":")}return[...o,`tw-${n}`].join(":")}function h(...t){const e=mi.clsx(t);if(!e)return e;if(e.indexOf("tw-")===-1)return _i(e);const a=e.split(" ").filter(Boolean),o=new Map,n=[];return a.forEach(w=>{const d=Ni(w);o.set(d.normalized,d.original),n.push(d.normalized)}),_o.twMerge(n.join(" ")).split(" ").filter(Boolean).map(w=>{const d=o.get(w);return d?Ci(w,d):w}).join(" ")}const We=250,xa=300,Vr=400,So=450,Eo=500,To=550,ya=ge.cva("pr-twp tw:group/button tw:inline-flex tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-transparent tw:bg-clip-padding tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:active:not-aria-[haspopup]:translate-y-px tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",{variants:{variant:{default:"tw:bg-primary tw:text-primary-foreground tw:[a]:hover:bg-primary/80",outline:"tw:border-border tw:bg-background tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:border-input tw:dark:bg-input/30 tw:dark:hover:bg-input/50",secondary:"tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-secondary/80 tw:aria-expanded:bg-secondary tw:aria-expanded:text-secondary-foreground",ghost:"tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:hover:bg-muted/50",destructive:"tw:bg-destructive/10 tw:text-destructive tw:hover:bg-destructive/20 tw:focus-visible:border-destructive/40 tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:hover:bg-destructive/30 tw:dark:focus-visible:ring-destructive/40",link:"tw:text-primary tw:underline-offset-4 tw:hover:underline"},size:{default:"tw:h-8 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",xs:"tw:h-6 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),10px)] tw:px-2 tw:text-xs tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3",sm:"tw:h-7 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3.5",lg:"tw:h-9 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",icon:"tw:size-8","icon-xs":"tw:size-6 tw:rounded-[min(var(--tw-radius-md),10px)] tw:in-data-[slot=button-group]:rounded-lg tw:[&_svg:not([class*=size-])]:size-3","icon-sm":"tw:size-7 tw:rounded-[min(var(--tw-radius-md),12px)] tw:in-data-[slot=button-group]:rounded-lg","icon-lg":"tw:size-9"}},defaultVariants:{variant:"default",size:"default"}});function F({className:t,variant:e="default",size:a="default",asChild:o=!1,...n}){const i=o?k.Slot.Root:"button";return r.jsx(i,{"data-slot":"button","data-variant":e,"data-size":a,className:h(ya({variant:e,size:a,className:t})),...n})}const Si="layoutDirection";function ft(){const t=localStorage.getItem(Si);return t==="rtl"?t:"ltr"}function Er({...t}){return r.jsx(k.Dialog.Root,{"data-slot":"dialog",...t})}function Ei({...t}){return r.jsx(k.Dialog.Trigger,{"data-slot":"dialog-trigger",...t})}function Ro({...t}){return r.jsx(k.Dialog.Portal,{"data-slot":"dialog-portal",...t})}function Ti({...t}){return r.jsx(k.Dialog.Close,{"data-slot":"dialog-close",...t})}function zo({className:t,style:e,...a}){return r.jsx(k.Dialog.Overlay,{"data-slot":"dialog-overlay",className:h("tw:fixed tw:inset-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-closed:animate-out tw:data-closed:fade-out-0",t),style:{zIndex:So,...e},...a})}function Tr({className:t,children:e,showCloseButton:a=!0,overlayClassName:o,style:n,...i}){const s=ft();return r.jsxs(Ro,{children:[r.jsx(zo,{className:o}),r.jsxs(k.Dialog.Content,{"data-slot":"dialog-content",className:h("pr-twp tw:fixed tw:top-1/2 tw:start-1/2 tw:grid tw:w-full tw:max-w-[calc(100%-2rem)] tw:-translate-x-1/2 tw:rtl:translate-x-1/2 tw:-translate-y-1/2 tw:gap-4 tw:rounded-xl tw:bg-popover tw:p-4 tw:text-sm tw:text-popover-foreground tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:outline-none tw:sm:max-w-sm tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),style:{zIndex:Eo,...n},dir:s,...i,children:[e,a&&r.jsx(k.Dialog.Close,{"data-slot":"dialog-close",asChild:!0,children:r.jsxs(F,{variant:"ghost",className:"tw:absolute tw:top-2 tw:end-2",size:"icon-sm",children:[r.jsx(ht.IconX,{}),r.jsx("span",{className:"tw:sr-only",children:"Close"})]})})]})]})}function Rr({className:t,...e}){return r.jsx("div",{"data-slot":"dialog-header",className:h("pr-twp tw:flex tw:flex-col tw:gap-2 tw:sm:text-start",t),...e})}function da({className:t,showCloseButton:e=!1,children:a,...o}){return r.jsxs("div",{"data-slot":"dialog-footer",className:h("pr-twp tw:-mx-4 tw:-mb-4 tw:flex tw:flex-col-reverse tw:gap-2 tw:rounded-b-xl tw:border-t tw:bg-muted/50 tw:p-4 tw:sm:flex-row tw:sm:justify-end",t),...o,children:[a,e&&r.jsx(k.Dialog.Close,{asChild:!0,children:r.jsx(F,{variant:"outline",children:"Close"})})]})}function zr({className:t,...e}){return r.jsx(k.Dialog.Title,{"data-slot":"dialog-title",className:h("pr-twp tw:font-heading tw:text-base tw:leading-none tw:font-medium",t),...e})}function Ri({className:t,...e}){return r.jsx(k.Dialog.Description,{"data-slot":"dialog-description",className:h("pr-twp tw:text-sm tw:text-muted-foreground tw:*:[a]:underline tw:*:[a]:underline-offset-3 tw:*:[a]:hover:text-foreground",t),...e})}function Xe({className:t,type:e,...a}){return r.jsx("input",{type:e,"data-slot":"input",className:h("pr-twp tw:h-8 tw:min-w-0 tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:px-2.5 tw:py-1 tw:text-base tw:transition-colors tw:outline-none tw:file:inline-flex tw:file:h-6 tw:file:border-0 tw:file:bg-transparent tw:file:text-sm tw:file:font-medium tw:file:text-foreground tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:pointer-events-none tw:disabled:cursor-not-allowed tw:disabled:bg-input/50 tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:md:text-sm tw:dark:bg-input/30 tw:dark:disabled:bg-input/80 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40",t),...a})}function zi({className:t,...e}){return r.jsx("textarea",{"data-slot":"textarea",className:h("pr-twp tw:flex tw:field-sizing-content tw:min-h-16 tw:w-full tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:px-2.5 tw:py-2 tw:text-base tw:transition-colors tw:outline-none tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:bg-input/50 tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:md:text-sm tw:dark:bg-input/30 tw:dark:disabled:bg-input/80 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40",t),...e})}function Di({className:t,...e}){return r.jsx("div",{"data-slot":"input-group",role:"group",className:h("pr-twp tw:group/input-group tw:relative tw:flex tw:h-8 tw:w-full tw:min-w-0 tw:items-center tw:rounded-lg tw:border tw:border-input tw:transition-colors tw:outline-none tw:in-data-[slot=combobox-content]:focus-within:border-inherit tw:in-data-[slot=combobox-content]:focus-within:ring-0 tw:has-disabled:bg-input/50 tw:has-disabled:opacity-50 tw:has-[[data-slot=input-group-control]:focus-visible]:border-ring tw:has-[[data-slot=input-group-control]:focus-visible]:ring-3 tw:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 tw:has-[[data-slot][aria-invalid=true]]:border-destructive tw:has-[[data-slot][aria-invalid=true]]:ring-3 tw:has-[[data-slot][aria-invalid=true]]:ring-destructive/20 tw:has-[>[data-align=block-end]]:h-auto tw:has-[>[data-align=block-end]]:flex-col tw:has-[>[data-align=block-start]]:h-auto tw:has-[>[data-align=block-start]]:flex-col tw:has-[>textarea]:h-auto tw:dark:bg-input/30 tw:dark:has-disabled:bg-input/80 tw:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 tw:has-[>[data-align=block-end]]:[&>input]:pt-3 tw:has-[>[data-align=block-start]]:[&>input]:pb-3 tw:has-[>[data-align=inline-end]]:[&>input]:pe-1.5 tw:has-[>[data-align=inline-start]]:[&>input]:ps-1.5",t),...e})}const Ii=ge.cva("tw:flex tw:h-auto tw:cursor-text tw:items-center tw:justify-center tw:gap-2 tw:py-1.5 tw:text-sm tw:font-medium tw:text-muted-foreground tw:select-none tw:group-data-[disabled=true]/input-group:opacity-50 tw:[&>kbd]:rounded-[calc(var(--radius)-5px)] tw:[&>svg:not([class*=size-])]:size-4",{variants:{align:{"inline-start":"tw:order-first tw:ps-2 tw:has-[>button]:ms-[-0.3rem] tw:has-[>kbd]:ms-[-0.15rem]","inline-end":"tw:order-last tw:pe-2 tw:has-[>button]:me-[-0.3rem] tw:has-[>kbd]:me-[-0.15rem]","block-start":"tw:order-first tw:w-full tw:justify-start tw:px-2.5 tw:pt-2 tw:group-has-[>input]/input-group:pt-2 tw:[.border-b]:pb-2","block-end":"tw:order-last tw:w-full tw:justify-start tw:px-2.5 tw:pb-2 tw:group-has-[>input]/input-group:pb-2 tw:[.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}});function Mi({className:t,align:e="inline-start",...a}){return r.jsx("div",{role:"group","data-slot":"input-group-addon","data-align":e,className:h(Ii({align:e}),t),onClick:o=>{var n,i;o.target instanceof HTMLElement&&o.target.closest("button")||(i=(n=o.currentTarget.parentElement)==null?void 0:n.querySelector("input"))==null||i.focus()},...a})}ge.cva("tw:flex tw:items-center tw:gap-2 tw:text-sm tw:shadow-none",{variants:{size:{xs:"tw:h-6 tw:gap-1 tw:rounded-[calc(var(--radius)-3px)] tw:px-1.5 tw:[&>svg:not([class*=size-])]:size-3.5",sm:"tw:","icon-xs":"tw:size-6 tw:rounded-[calc(var(--radius)-3px)] tw:p-0 tw:has-[>svg]:p-0","icon-sm":"tw:size-8 tw:p-0 tw:has-[>svg]:p-0"}},defaultVariants:{size:"xs"}});function he({className:t,...e}){return r.jsx(Ve.Command,{"data-slot":"command",className:h("pr-twp tw:flex tw:size-full tw:flex-col tw:overflow-hidden tw:rounded-xl! tw:bg-popover tw:p-1 tw:text-popover-foreground",t),...e})}function Fe({className:t,onKeyDown:e,...a}){const o=ft(),n=l.useCallback(i=>{if(e==null||e(i),i.defaultPrevented||i.key!==" "||i.currentTarget.value!=="")return;const s=i.currentTarget.closest("[cmdk-root]"),c=s==null?void 0:s.querySelector('[cmdk-item][data-selected="true"]:not([data-disabled="true"])');c&&(i.preventDefault(),i.stopPropagation(),c.click())},[e]);return r.jsx("div",{"data-slot":"command-input-wrapper",className:"tw:p-1 tw:pb-0",dir:o,children:r.jsxs(Di,{className:"tw:h-8! tw:rounded-lg! tw:border-input/30 tw:bg-input/30 tw:shadow-none! tw:*:data-[slot=input-group-addon]:ps-2!",children:[r.jsx(Ve.Command.Input,{"data-slot":"command-input",className:h("tw:w-full tw:text-sm tw:outline-hidden tw:disabled:cursor-not-allowed tw:disabled:opacity-50",t),onKeyDown:n,...a}),r.jsx(Mi,{children:r.jsx(ht.IconSearch,{className:"tw:size-4 tw:shrink-0 tw:opacity-50"})})]})})}function fe({className:t,...e}){return r.jsx(Ve.Command.List,{"data-slot":"command-list",className:h("pr-twp tw:no-scrollbar tw:max-h-72 tw:scroll-py-1 tw:overflow-x-hidden tw:overflow-y-auto tw:outline-none",t),...e})}function Ze({className:t,...e}){return r.jsx(Ve.Command.Empty,{"data-slot":"command-empty",className:h("pr-twp tw:py-6 tw:text-center tw:text-sm",t),...e})}function re({className:t,...e}){return r.jsx(Ve.Command.Group,{"data-slot":"command-group",className:h("pr-twp tw:overflow-hidden tw:p-1 tw:text-foreground tw:**:[[cmdk-group-heading]]:px-2 tw:**:[[cmdk-group-heading]]:py-1.5 tw:**:[[cmdk-group-heading]]:text-xs tw:**:[[cmdk-group-heading]]:font-medium tw:**:[[cmdk-group-heading]]:text-muted-foreground",t),...e})}function ka({className:t,...e}){return r.jsx(Ve.Command.Separator,{"data-slot":"command-separator",className:h("pr-twp tw:-mx-1 tw:h-px tw:bg-border",t),...e})}function ie({className:t,children:e,...a}){return r.jsxs(Ve.Command.Item,{"data-slot":"command-item",className:h("pr-twp tw:group/command-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:in-data-[slot=dialog-content]:rounded-lg! tw:data-[disabled=true]:pointer-events-none tw:data-[disabled=true]:opacity-50 tw:data-selected:bg-muted tw:data-selected:text-foreground tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:data-selected:*:[svg]:text-foreground",t),...a,children:[e,r.jsx(ht.IconCheck,{className:"tw:ms-auto tw:opacity-0 tw:group-has-data-[slot=command-shortcut]/command-item:hidden tw:group-data-[checked=true]/command-item:opacity-100"})]})}function Oi({className:t,...e}){return r.jsx("span",{"data-slot":"command-shortcut",className:h("pr-twp tw:ms-auto tw:text-xs tw:tracking-widest tw:text-muted-foreground tw:group-data-selected/command-item:text-foreground",t),...e})}const Do=(t,e,a,o,n)=>{switch(t){case D.Section.OT:return e??"Old Testament";case D.Section.NT:return a??"New Testament";case D.Section.DC:return o??"Deuterocanon";case D.Section.Extra:return n??"Extra Materials";default:throw new Error(`Unknown section: ${t}`)}},$i=(t,e,a,o,n)=>{switch(t){case D.Section.OT:return e??"OT";case D.Section.NT:return a??"NT";case D.Section.DC:return o??"DC";case D.Section.Extra:return n??"Extra";default:throw new Error(`Unknown section: ${t}`)}};function Ce(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedName)??st.Canon.bookIdToEnglishName(t)}function ja(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedId)??t.toUpperCase()}const Io=st.Canon.allBookIds.filter(t=>!st.Canon.isObsolete(st.Canon.bookIdToNumber(t))),te=Object.fromEntries(Io.map(t=>[t,st.Canon.bookIdToEnglishName(t)]));function _a(t,e,a){const o=e.trim().toLowerCase();if(!o)return!1;const n=st.Canon.bookIdToEnglishName(t),i=a==null?void 0:a.get(t);return!!(D.includes(n.toLowerCase(),o)||D.includes(t.toLowerCase(),o)||(i?D.includes(i.localizedName.toLowerCase(),o)||D.includes(i.localizedId.toLowerCase(),o):!1))}function Mo({ref:t,bookId:e,isSelected:a,onSelect:o,onMouseDown:n,section:i,className:s,showCheck:c=!1,localizedBookNames:w,commandValue:d,disabled:u=!1}){const g=l.useRef(!1),m=()=>{u||(g.current||o==null||o(e),setTimeout(()=>{g.current=!1},100))},p=b=>{if(u){b.preventDefault();return}g.current=!0,n?n(b):o==null||o(e)},f=l.useMemo(()=>Ce(e,w),[e,w]),y=l.useMemo(()=>ja(e,w),[e,w]);return r.jsx("div",{className:h("tw:mx-1 tw:my-1 tw:border-b-0 tw:border-e-0 tw:border-s-2 tw:border-t-0 tw:border-solid",{"tw:border-s-red-200":i===D.Section.OT,"tw:border-s-purple-200":i===D.Section.NT,"tw:border-s-indigo-200":i===D.Section.DC,"tw:border-s-amber-200":i===D.Section.Extra}),children:r.jsxs(ie,{ref:t,value:d||`${e} ${st.Canon.bookIdToEnglishName(e)}`,onSelect:m,onMouseDown:p,role:"option","aria-selected":a,"aria-disabled":u||void 0,"aria-label":`${st.Canon.bookIdToEnglishName(e)} (${e.toLocaleUpperCase()})`,disabled:u,className:h(s,u&&"tw:cursor-not-allowed tw:opacity-50"),children:[c&&r.jsx($.Check,{className:h("tw:me-2 tw:h-4 tw:w-4 tw:shrink-0",a?"tw:opacity-100":"tw:opacity-0")}),r.jsx("span",{className:"tw:min-w-0 tw:flex-1",children:f}),r.jsx("span",{className:"tw:ms-2 tw:shrink-0 tw:text-xs tw:text-muted-foreground",children:y})]})})}function se({...t}){return r.jsx(k.Popover.Root,{"data-slot":"popover",...t})}function me({...t}){return r.jsx(k.Popover.Trigger,{"data-slot":"popover-trigger",...t})}const Oo=l.createContext(null);function jr({container:t,children:e}){return r.jsx(Oo.Provider,{value:t,children:e})}function ce({className:t,align:e="center",sideOffset:a=4,style:o,...n}){const i=ft(),s=l.useContext(Oo);return r.jsx(k.Popover.Portal,{container:s??void 0,children:r.jsx(k.Popover.Content,{"data-slot":"popover-content",align:e,sideOffset:a,className:h("pr-twp tw:flex tw:w-72 tw:origin-(--radix-popover-content-transform-origin) tw:flex-col tw:gap-2.5 tw:rounded-lg tw:bg-popover tw:p-2.5 tw:text-sm tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:outline-hidden tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),style:{zIndex:We,...o},dir:i,...n})})}function $o({...t}){return r.jsx(k.Popover.Anchor,{"data-slot":"popover-anchor",...t})}function Ai({className:t,...e}){return r.jsx("div",{"data-slot":"popover-header",className:h("pr-twp tw:flex tw:flex-col tw:gap-0.5 tw:text-sm",t),...e})}function Pi({className:t,...e}){return r.jsx("div",{"data-slot":"popover-title",className:h("pr-twp tw:font-medium",t),...e})}function Li({className:t,...e}){return r.jsx("p",{"data-slot":"popover-description",className:h("pr-twp tw:text-muted-foreground",t),...e})}function Ao(t,e,a){return`${t} ${te[t]}${e?` ${ja(t,e)} ${Ce(t,e)}`:""}`}function Po({recentSearches:t,onSearchItemSelect:e,renderItem:a=u=>String(u),getItemKey:o=u=>String(u),ariaLabel:n="Show recent searches",groupHeading:i="Recent",id:s,classNameForItems:c,buttonClassName:w="tw:absolute tw:right-0 tw:top-0 tw:h-full tw:px-3 tw:py-2",buttonVariant:d="ghost"}){const[u,g]=l.useState(!1);if(t.length===0)return;const m=p=>{e(p),g(!1)};return r.jsxs(se,{open:u,onOpenChange:g,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{variant:d,size:"icon",className:w,"aria-label":n,children:r.jsx($.Clock,{className:"tw:h-4 tw:w-4"})})}),r.jsx(ce,{id:s,className:"tw:w-[300px] tw:p-0",align:"start",children:r.jsx(he,{children:r.jsx(fe,{children:r.jsx(re,{heading:i,children:t.map(p=>r.jsxs(ie,{onSelect:()=>m(p),className:h("tw:flex tw:items-center",c),children:[r.jsx($.Clock,{className:"tw:mr-2 tw:h-4 tw:w-4 tw:opacity-50"}),r.jsx("span",{children:a(p)})]},o(p)))})})})})]})}function Bi(t,e,a=(n,i)=>n===i,o=15){return n=>{const i=t.filter(c=>!a(c,n)),s=[n,...i.slice(0,o-1)];e(s)}}const _r={BOOK_ONLY:/^([^:\s]+(?:\s+[^:\s]+)*)$/i,BOOK_CHAPTER:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+)$/i,BOOK_CHAPTER_VERSE:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+):(\d*)$/i},Vi=[_r.BOOK_ONLY,_r.BOOK_CHAPTER,_r.BOOK_CHAPTER_VERSE];function Fi(t){return _r.BOOK_CHAPTER_VERSE.test(t.trim())}function Za(t,e){return st.Canon.bookIdToNumber(t)0?!1:e0?!1:eo.chapterNum?!1:a{if(n)return n;const s=i.exec(t.trim());if(s){const[c,w=void 0,d=void 0]=s.slice(1);let u;const g=e.filter(m=>_a(m,c,a));if(g.length===1&&([u]=g),!u&&w){if(st.Canon.isBookIdValid(c)){const m=c.toUpperCase();e.includes(m)&&(u=m)}if(!u&&a){const m=Array.from(a.entries()).find(([,p])=>p.localizedId.toLowerCase()===c.toLowerCase());m&&e.includes(m[0])&&([u]=m)}}if(!u&&w){const p=(f=>Object.keys(te).find(y=>te[y].toLowerCase()===f.toLowerCase()))(c);if(p&&e.includes(p)&&(u=p),!u&&a){const f=Array.from(a.entries()).find(([,y])=>y.localizedName.toLowerCase()===c.toLowerCase());f&&e.includes(f[0])&&([u]=f)}}if(u){let m=w?parseInt(w,10):void 0;m&&m>we(u)&&(m=Math.max(we(u),1));const p=d?parseInt(d,10):void 0;return{book:u,chapterNum:m,verseNum:p}}}},void 0);if(o)return o}function qi(t,e,a,o){const n=l.useCallback(()=>{if(t.chapterNum>1)o({book:t.book,chapterNum:t.chapterNum-1,verseNum:1});else{const w=e.indexOf(t.book);if(w>0){const d=e[w-1],u=Math.max(we(d),1);o({book:d,chapterNum:u,verseNum:1})}}},[t,e,o]),i=l.useCallback(()=>{const w=we(t.book);if(t.chapterNum{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum>1?t.verseNum-1:0})},[t,o]),c=l.useCallback(()=>{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum+1})},[t,o]);return l.useMemo(()=>[{onClick:n,disabled:e.length===0||t.chapterNum===1&&e.indexOf(t.book)===0,title:"Previous chapter",icon:a==="ltr"?$.ChevronsLeft:$.ChevronsRight},{onClick:s,disabled:e.length===0||t.verseNum===0,title:"Previous verse",icon:a==="ltr"?$.ChevronLeft:$.ChevronRight},{onClick:c,disabled:e.length===0,title:"Next verse",icon:a==="ltr"?$.ChevronRight:$.ChevronLeft},{onClick:i,disabled:e.length===0||(t.chapterNum===we(t.book)||we(t.book)<=0)&&e.indexOf(t.book)===e.length-1,title:"Next chapter",icon:a==="ltr"?$.ChevronsRight:$.ChevronsLeft}],[t,e,a,n,s,c,i])}function Lo({count:t,valueBuilder:e,onSelect:a,itemRef:o,isDisabled:n,isDimmed:i,isSelected:s,className:c}){if(!(t<=0))return r.jsx(re,{children:r.jsx("div",{className:h("tw:grid tw:grid-cols-6 tw:gap-1",c),children:Array.from({length:t},(w,d)=>d+1).map(w=>{const d=(n==null?void 0:n(w))??!1;return r.jsx(ie,{value:e(w),onSelect:()=>{d||a(w)},ref:o(w),disabled:d,"aria-disabled":d||void 0,className:h("tw:h-8 tw:min-w-0 tw:cursor-pointer tw:justify-center tw:rounded-md tw:px-0 tw:text-center tw:text-sm",{"tw:bg-primary tw:text-primary-foreground":(s==null?void 0:s(w))??!1},{"tw:bg-muted/50 tw:text-muted-foreground/50":((i==null?void 0:i(w))??!1)&&!d},d&&"tw:cursor-not-allowed tw:opacity-40"),children:w},w)})})})}function Qa({bookId:t,scrRef:e,onChapterSelect:a,setChapterRef:o,isChapterDimmed:n,isChapterDisabled:i,className:s}){if(t)return r.jsx(Lo,{count:we(t),valueBuilder:c=>`${t} ${te[t]||""} ${c}`,onSelect:a,itemRef:o,isDisabled:i,isDimmed:n,isSelected:c=>t===e.book&&c===e.chapterNum,className:s})}function to({bookId:t,chapterNum:e,endVerse:a,scrRef:o,onVerseSelect:n,setVerseRef:i,isVerseDimmed:s,isVerseDisabled:c,className:w}){if(!(!t||a<=0))return r.jsx(Lo,{count:a,valueBuilder:d=>`${t} ${te[t]||""} ${e}:${d}`,onSelect:n,itemRef:i,isDisabled:c,isDimmed:s,isSelected:d=>t===o.book&&e===o.chapterNum&&d===o.verseNum,className:w})}function Nr({scrRef:t,handleSubmit:e,className:a,getActiveBookIds:o,localizedBookNames:n,localizedStrings:i,recentSearches:s,onAddRecentSearch:c,id:w,getEndVerse:d,disableReferencesUpTo:u,submitKeys:g,triggerContent:m,triggerVariant:p="outline",onOpenChange:f,onCloseAutoFocus:y,modal:b=!1,align:I="center"}){const C=ft(),[T,S]=l.useState(!1),[N,E]=l.useState(""),[z,j]=l.useState(""),[x,R]=l.useState("books"),[P,G]=l.useState(void 0),[q,V]=l.useState(void 0),[K,M]=l.useState(void 0),[Y,lt]=l.useState(!1),kt=l.useRef(null),St=l.useRef(!1),J=l.useRef(void 0),Et=l.useRef(void 0),U=l.useRef(void 0),tt=l.useRef(void 0),rt=l.useRef({}),at=l.useRef({}),ot=l.useCallback(_=>{e(_),c&&c(_)},[e,c]),Lt=l.useMemo(()=>o?o():Io,[o]),gt=l.useMemo(()=>({[D.Section.OT]:Lt.filter(H=>st.Canon.isBookOT(H)),[D.Section.NT]:Lt.filter(H=>st.Canon.isBookNT(H)),[D.Section.DC]:Lt.filter(H=>st.Canon.isBookDC(H)),[D.Section.Extra]:Lt.filter(H=>st.Canon.extraBooks().includes(H))}),[Lt]),Ft=l.useMemo(()=>Object.values(gt).flat(),[gt]),Gt=l.useMemo(()=>{if(!z.trim())return gt;const _={[D.Section.OT]:[],[D.Section.NT]:[],[D.Section.DC]:[],[D.Section.Extra]:[]};return[D.Section.OT,D.Section.NT,D.Section.DC,D.Section.Extra].forEach(Z=>{_[Z]=gt[Z].filter(_t=>_a(_t,z,n))}),_},[gt,z,n]),A=l.useMemo(()=>Ui(z,Ft,n),[z,Ft,n]),Tt=l.useRef(!1);l.useEffect(()=>{if(!Tt.current){Tt.current=!0;return}f==null||f(T)},[T,f]);const Bt=l.useCallback(()=>{if(A){const _=A.chapterNum??1,H=A.verseNum??1;if(u&&Yr(A.book,_,H,u))return;ot({book:A.book,chapterNum:_,verseNum:H}),S(!1),j(""),E("")}},[ot,A,u]),Qt=l.useCallback(_=>{const H=q??(A==null?void 0:A.book),Z=K??(A==null?void 0:A.chapterNum);!H||!Z||(ot({book:H,chapterNum:Z,verseNum:_}),S(!1))},[ot,q,K,A]),Ut=l.useCallback(_=>{if(u&&Za(_,u))return;if(we(_)<=1){ot({book:_,chapterNum:1,verseNum:1}),S(!1),j("");return}G(_),R("chapters")},[ot,u]),Rt=l.useCallback(_=>{const H=x==="chapters"?P:A==null?void 0:A.book;if(H){if(d&&d(H,_)>1){V(H),M(_),R("verses"),E("");return}ot({book:H,chapterNum:_,verseNum:1}),S(!1)}},[ot,x,P,A,d]),je=l.useCallback(_=>{ot(_),S(!1),j("")},[ot]),qt=qi(t,Ft,C,e),Kt=l.useCallback(()=>{R("books"),G(void 0),V(void 0),M(void 0),setTimeout(()=>{Et.current&&Et.current.focus()},0)},[]),B=l.useCallback(()=>{const _=q;V(void 0),M(void 0),_?(G(_),R("chapters"),E("")):Kt()},[q,Kt]),W=l.useCallback(_=>{S(_),_&&(R("books"),G(void 0),V(void 0),M(void 0),j(""))},[]),{otLong:nt,ntLong:et,dcLong:wt,extraLong:mt}={otLong:i==null?void 0:i["%scripture_section_ot_long%"],ntLong:i==null?void 0:i["%scripture_section_nt_long%"],dcLong:i==null?void 0:i["%scripture_section_dc_long%"],extraLong:i==null?void 0:i["%scripture_section_extra_long%"]},vt=l.useCallback(_=>Do(_,nt,et,wt,mt),[nt,et,wt,mt]),ut=l.useCallback(_=>A?!!A.chapterNum&&!_.toString().includes(A.chapterNum.toString()):!1,[A]),jt=l.useMemo(()=>D.formatScrRef(t,n?Ce(t.book,n):"English"),[t,n]),O=l.useCallback(_=>H=>{rt.current[_]=H},[]),ct=l.useCallback(_=>H=>{at.current[_]=H},[]),dt=l.useMemo(()=>Fi(z),[z]),bt=l.useMemo(()=>!d||!A||!A.chapterNum||!dt?!1:d(A.book,A.chapterNum)>0,[d,A,dt]),Re=l.useCallback(_=>u?Za(_,u):!1,[u]),_e=l.useCallback(_=>H=>u?Gi(_,H,u):!1,[u]),Qe=l.useCallback((_,H)=>Z=>u?Yr(_,H,Z,u):!1,[u]),ze=(i==null?void 0:i["%webView_bookChapterControl_selectChapter%"])??"Select Chapter",tr=(i==null?void 0:i["%webView_bookChapterControl_selectVerse%"])??"Select Verse",er=l.useCallback(_=>{(_.key==="Home"||_.key==="End")&&_.stopPropagation(),g&&g.includes(_.key)&&A&&A.chapterNum!==void 0&&A.verseNum!==void 0&&(_.preventDefault(),_.stopPropagation(),Bt())},[g,A,Bt]),gr=l.useCallback(_=>{var Wt,Ue,rr;if(_.ctrlKey)return;const{isLetter:H,isDigit:Z}=Ja(_.key);if((x==="chapters"||x==="verses")&&(_.key===" "||_.key==="Enter")){const zt=_.target instanceof HTMLElement?_.target:void 0;if(!!(zt!=null&&zt.closest('button, a, input, select, textarea, [role="button"]'))){_.stopPropagation();return}const Nt=(Wt=J.current)==null?void 0:Wt.querySelector('[cmdk-item][data-selected="true"]:not([data-disabled="true"])');if(Nt){_.preventDefault(),_.stopPropagation(),Nt.click();return}}if((x==="chapters"||x==="verses")&&(H||Z)){_.preventDefault(),_.stopPropagation();return}if(x==="chapters"&&_.key==="Backspace"){_.preventDefault(),_.stopPropagation(),Kt();return}if(x==="verses"&&_.key==="Backspace"){_.preventDefault(),_.stopPropagation(),B();return}const _t=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(_.key);if(x==="verses"&&_t){const zt=q,xt=K;if(!zt||!xt||!d)return;const Nt=d(zt,xt);if(!Nt)return;(Ue=J.current)==null||Ue.focus();const pt=(()=>{if(!N)return 1;const De=N.match(/:(\d+)$/);return De?parseInt(De[1],10):0})();let Ht=pt;const Yt=6;switch(_.key){case"ArrowLeft":pt!==0&&(Ht=pt>1?pt-1:Nt);break;case"ArrowRight":pt!==0&&(Ht=pt{const De=at.current[Ht];De&&De.scrollIntoView({block:"nearest",behavior:"smooth"})},0));return}if((x==="chapters"||x==="books"&&A)&&_t){const zt=x==="chapters"?P:A==null?void 0:A.book;if(!zt)return;x==="chapters"&&((rr=J.current)==null||rr.focus());const xt=(()=>{if(!N)return 1;const Yt=N.match(/(\d+)$/);return Yt?parseInt(Yt[1],10):0})(),Nt=we(zt);if(!Nt)return;let pt=xt;const Ht=6;switch(_.key){case"ArrowLeft":xt!==0&&(pt=xt>1?xt-1:Nt);break;case"ArrowRight":xt!==0&&(pt=xt{const Yt=rt.current[pt];Yt&&Yt.scrollIntoView({block:"nearest",behavior:"smooth"})},0))}},[x,A,Kt,B,P,q,K,d,N]),hr=l.useCallback(_=>{var _t;if(_.shiftKey||_.key==="Tab"||_.key===" ")return;if(_.key==="Enter"){_.stopPropagation();return}if(_.key==="ArrowUp"||_.key==="ArrowDown"){(_t=Et.current)==null||_t.focus();return}const{isLetter:H,isDigit:Z}=Ja(_.key);(H||Z)&&(_.preventDefault(),j(Wt=>Wt+_.key),Et.current.focus(),lt(!1))},[]);return l.useLayoutEffect(()=>{const _=setTimeout(()=>{if(T&&x==="books"&&U.current&&tt.current){const H=U.current,Z=tt.current,_t=Z.offsetTop,Wt=H.clientHeight,Ue=Z.clientHeight,rr=_t-Wt/2+Ue/2;H.scrollTo({top:Math.max(0,rr),behavior:"smooth"}),E(Ao(t.book))}},0);return()=>{clearTimeout(_)}},[T,x,z,A,t.book]),l.useLayoutEffect(()=>{if(x==="chapters"&&P){const _=P===t.book,H=_?t.chapterNum:1;E(`${P} ${te[P]||""} ${H}`),setTimeout(()=>{if(U.current)if(_){const Z=rt.current[t.chapterNum];Z&&Z.scrollIntoView({block:"center",behavior:"smooth"})}else U.current.scrollTo({top:0});J.current&&J.current.focus()},0)}},[x,P,A,t.book,t.chapterNum]),l.useLayoutEffect(()=>{if(x==="verses"&&q&&K!==void 0){const _=q===t.book&&K===t.chapterNum,H=_?t.verseNum:1;E(`${q} ${te[q]||""} ${K}:${H}`),setTimeout(()=>{if(U.current)if(_){const Z=at.current[t.verseNum];Z&&Z.scrollIntoView({block:"center",behavior:"smooth"})}else U.current.scrollTo({top:0});J.current&&J.current.focus()},0)}},[x,q,K,t.book,t.chapterNum,t.verseNum]),r.jsxs(se,{open:T,onOpenChange:W,modal:b,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{ref:kt,"aria-label":"book-chapter-trigger",variant:p,role:"combobox","aria-expanded":T,className:h("tw:h-8 tw:w-full tw:min-w-16 tw:max-w-48 tw:overflow-hidden tw:px-1",a),onClick:_=>{St.current&&(St.current=!1,_.preventDefault())},children:m??r.jsx("span",{className:"tw:truncate",children:jt})})}),r.jsx(ce,{id:w,forceMount:!0,className:"tw:w-[var(--radix-popper-anchor-width,280px)] tw:min-w-[200px] tw:max-w-[280px] tw:p-0",align:I,onKeyDownCapture:gr,onKeyDown:_=>_.stopPropagation(),onPointerDownOutside:_=>{const{target:H}=_;T&&kt.current&&H instanceof Node&&kt.current.contains(H)&&(St.current=!0,W(!1))},onCloseAutoFocus:y,children:r.jsxs(he,{ref:J,loop:!0,value:N,onValueChange:E,shouldFilter:!1,children:[x==="books"?r.jsxs("div",{className:"tw:flex tw:items-end",children:[r.jsxs("div",{className:"tw:relative tw:flex-1",children:[r.jsx(Fe,{ref:Et,value:z,onValueChange:j,onKeyDown:er,onFocus:()=>lt(!1),className:s&&s.length>0?"tw:!pr-10":""}),s&&s.length>0&&r.jsx(Po,{recentSearches:s,onSearchItemSelect:je,renderItem:_=>D.formatScrRef(_,"English"),getItemKey:_=>`${_.book}-${_.chapterNum}-${_.verseNum}`,ariaLabel:i==null?void 0:i["%history_recentSearches_ariaLabel%"],groupHeading:i==null?void 0:i["%history_recent%"]})]}),r.jsx("div",{className:"tw:flex tw:items-center tw:gap-1 tw:border-b tw:pe-2",children:qt.map(({onClick:_,disabled:H,title:Z,icon:_t})=>r.jsx(F,{variant:"ghost",size:"sm",onClick:()=>{lt(!0),_()},disabled:H,className:"tw:h-10 tw:w-4 tw:p-0",title:Z,onKeyDown:hr,children:r.jsx(_t,{})},Z))})]}):r.jsxs("div",{className:"tw:flex tw:items-center tw:border-b tw:px-3 tw:py-2",children:[r.jsx(F,{variant:"ghost",size:"sm",onClick:x==="verses"?B:Kt,className:"tw:mr-2 tw:h-6 tw:w-6 tw:p-0",tabIndex:-1,children:C==="ltr"?r.jsx($.ArrowLeft,{className:"tw:h-4 tw:w-4"}):r.jsx($.ArrowRight,{className:"tw:h-4 tw:w-4"})}),x==="chapters"&&P&&r.jsx("span",{tabIndex:-1,className:"tw:text-sm tw:font-medium",children:Ce(P,n)}),x==="verses"&&q&&K!==void 0&&r.jsx("span",{tabIndex:-1,className:"tw:text-sm tw:font-medium",children:`${Ce(q,n)} ${K}`}),r.jsx("span",{tabIndex:-1,className:"tw:ms-auto tw:text-sm tw:font-medium tw:text-muted-foreground",children:x==="verses"?tr:ze})]}),!Y&&r.jsxs(fe,{ref:U,children:[x==="books"&&r.jsxs(r.Fragment,{children:[!A&&Object.entries(Gt).map(([_,H])=>{if(H.length!==0)return r.jsx(re,{heading:vt(_),children:H.map(Z=>r.jsx(Mo,{bookId:Z,onSelect:_t=>Ut(_t),section:D.getSectionForBook(Z),commandValue:`${Z} ${te[Z]}`,ref:Z===t.book?tt:void 0,localizedBookNames:n,disabled:Re(Z)},Z))},_)}),A&&r.jsx(re,{children:r.jsx(ie,{value:`${A.book} ${te[A.book]} ${A.chapterNum||""}:${A.verseNum||""})}`,onSelect:Bt,disabled:!!u&&Yr(A.book,A.chapterNum??1,A.verseNum??1,u),className:"tw:font-semibold tw:text-primary",children:D.formatScrRef({book:A.book,chapterNum:A.chapterNum??1,verseNum:A.verseNum??1},n?ja(A.book,n):void 0)},"top-match")}),A&&bt&&A.chapterNum&&d&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"tw:mb-2 tw:flex tw:items-center tw:justify-between tw:px-3 tw:text-sm tw:font-medium tw:text-muted-foreground",children:[r.jsx("span",{children:`${Ce(A.book,n)} ${A.chapterNum}`}),r.jsx("span",{children:tr})]}),r.jsx(to,{bookId:A.book,chapterNum:A.chapterNum,endVerse:d(A.book,A.chapterNum),scrRef:t,onVerseSelect:Qt,setVerseRef:ct,isVerseDisabled:Qe(A.book,A.chapterNum),className:"tw:px-4 tw:pb-4"})]}),A&&!bt&&we(A.book)>1&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"tw:mb-2 tw:flex tw:items-center tw:justify-between tw:px-3 tw:text-sm tw:font-medium tw:text-muted-foreground",children:[r.jsx("span",{children:Ce(A.book,n)}),r.jsx("span",{children:ze})]}),r.jsx(Qa,{bookId:A.book,scrRef:t,onChapterSelect:Rt,setChapterRef:O,isChapterDimmed:ut,isChapterDisabled:_e(A.book),className:"tw:px-4 tw:pb-4"})]})]}),x==="chapters"&&P&&r.jsx(Qa,{bookId:P,scrRef:t,onChapterSelect:Rt,setChapterRef:O,isChapterDisabled:_e(P),className:"tw:p-4"}),x==="verses"&&q&&K!==void 0&&d&&r.jsx(to,{bookId:q,chapterNum:K,endVerse:d(q,K),scrRef:t,onVerseSelect:Qt,setVerseRef:ct,isVerseDisabled:Qe(q,K),className:"tw:p-4"})]})]})})]})}const Ki=Object.freeze(["%scripture_section_ot_long%","%scripture_section_nt_long%","%scripture_section_dc_long%","%scripture_section_extra_long%","%history_recent%","%history_recentSearches_ariaLabel%","%webView_bookChapterControl_selectChapter%","%webView_bookChapterControl_selectVerse%"]);function yt({className:t,...e}){return r.jsx(k.Label.Root,{"data-slot":"label",className:h("pr-twp tw:flex tw:items-center tw:gap-2 tw:text-sm tw:leading-none tw:font-medium tw:select-none tw:group-data-[disabled=true]:pointer-events-none tw:group-data-[disabled=true]:opacity-50 tw:peer-disabled:cursor-not-allowed tw:peer-disabled:opacity-50",t),...e})}function Na({className:t,...e}){const a=ft();return r.jsx(k.RadioGroup.Root,{"data-slot":"radio-group",className:h("pr-twp tw:grid tw:w-full tw:gap-2",t),dir:a,...e})}function Dr({className:t,...e}){return r.jsx(k.RadioGroup.Item,{"data-slot":"radio-group-item",className:h("pr-twp tw:group/radio-group-item tw:peer tw:relative tw:flex tw:aspect-square tw:size-4 tw:shrink-0 tw:rounded-full tw:border tw:border-input tw:outline-none tw:after:absolute tw:after:-inset-x-3 tw:after:-inset-y-2 tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:aria-checked:border-primary tw:dark:bg-input/30 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:data-checked:border-primary tw:data-checked:bg-primary tw:data-checked:text-primary-foreground tw:dark:data-checked:bg-primary",t),...e,children:r.jsx(k.RadioGroup.Indicator,{"data-slot":"radio-group-indicator",className:"tw:flex tw:size-4 tw:items-center tw:justify-center",children:r.jsx("span",{className:"tw:absolute tw:top-1/2 tw:start-1/2 tw:size-2 tw:-translate-x-1/2 tw:rtl:translate-x-1/2 tw:-translate-y-1/2 tw:rounded-full tw:bg-primary-foreground"})})})}function Hi(t){return typeof t=="string"?t:typeof t=="number"?t.toString():t.label}function wa({id:t,options:e=[],className:a,buttonClassName:o,popoverContentClassName:n,popoverContentStyle:i,value:s,onChange:c=()=>{},getOptionLabel:w=Hi,getButtonLabel:d,icon:u=void 0,buttonPlaceholder:g="",textPlaceholder:m="",commandEmptyMessage:p="No option found",buttonVariant:f="outline",alignDropDown:y="start",isDisabled:b=!1,ariaLabel:I,...C}){const[T,S]=l.useState(!1),N=d??w,E=j=>j.length>0&&typeof j[0]=="object"&&"options"in j[0],z=(j,x)=>{const R=w(j),P=typeof j=="object"&&"secondaryLabel"in j?j.secondaryLabel:void 0,G=`${x??""}${R}${P??""}`;return r.jsxs(ie,{value:R,onSelect:()=>{c(j),S(!1)},className:"tw:flex tw:items-center",children:[r.jsx($.Check,{className:h("tw:me-2 tw:h-4 tw:w-4 tw:shrink-0",{"tw:opacity-0":!s||w(s)!==R})}),r.jsxs("span",{className:"tw:flex-1 tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap",children:[R,P&&r.jsxs("span",{className:"tw:text-muted-foreground",children:[" · ",P]})]})]},G)};return r.jsxs(se,{open:T,onOpenChange:S,...C,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:f,role:"combobox","aria-expanded":T,"aria-label":I,id:t,className:h("tw:flex tw:w-[200px] tw:items-center tw:justify-between tw:overflow-hidden",o??a),disabled:b,children:[r.jsxs("div",{className:"tw:flex tw:min-w-0 tw:flex-1 tw:items-center tw:overflow-hidden",children:[u&&r.jsx("div",{className:"tw:shrink-0 tw:pe-2",children:u}),r.jsx("span",{className:h("tw:min-w-0 tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap tw:text-start"),children:s?N(s):g})]}),r.jsx($.ChevronDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})}),r.jsx(ce,{align:y,className:h("tw:w-[200px] tw:p-0",n),style:i,children:r.jsxs(he,{children:[r.jsx(Fe,{placeholder:m,className:"tw:text-inherit"}),r.jsx(Ze,{children:p}),r.jsx(fe,{children:E(e)?e.map(j=>r.jsx(re,{heading:j.groupHeading,children:j.options.map(x=>z(x,j.groupHeading))},j.groupHeading)):e.map(j=>z(j))})]})})]})}function Bo({startChapter:t,endChapter:e,handleSelectStartChapter:a,handleSelectEndChapter:o,isDisabled:n=!1,chapterCount:i}){const s=l.useMemo(()=>Array.from({length:i},(d,u)=>u+1),[i]),c=d=>{a(d),d>e&&o(d)},w=d=>{o(d),dd.toString(),value:t},"start chapter"),r.jsx(yt,{htmlFor:"end-chapters-combobox",children:"to"}),r.jsx(wa,{isDisabled:n,onChange:w,buttonClassName:"tw:ms-2 tw:w-20",options:s,getOptionLabel:d=>d.toString(),value:e},"end chapter")]})}exports.BookSelectionMode=(t=>(t.CurrentBook="current book",t.ChooseBooks="choose books",t))(exports.BookSelectionMode||{});(t=>{t.CURRENT_BOOK="current book",t.CHOOSE_BOOKS="choose books"})(exports.BookSelectionMode||(exports.BookSelectionMode={}));const Yi=Object.freeze(["%webView_bookSelector_currentBook%","%webView_bookSelector_choose%","%webView_bookSelector_chooseBooks%"]),Wr=(t,e)=>t[e]??e;function Wi({handleBookSelectionModeChange:t,currentBookName:e,onSelectBooks:a,selectedBookIds:o,chapterCount:n,endChapter:i,handleSelectEndChapter:s,startChapter:c,handleSelectStartChapter:w,localizedStrings:d}){const u=Wr(d,"%webView_bookSelector_currentBook%"),g=Wr(d,"%webView_bookSelector_choose%"),m=Wr(d,"%webView_bookSelector_chooseBooks%"),[p,f]=l.useState("current book"),y=b=>{f(b),t(b)};return r.jsx(Na,{className:"pr-twp tw:flex",value:p,onValueChange:b=>y(b),children:r.jsxs("div",{className:"tw:flex tw:w-full tw:flex-col tw:gap-4",children:[r.jsxs("div",{className:"tw:grid tw:grid-cols-[25%_25%_50%]",children:[r.jsxs("div",{className:"tw:flex tw:items-center",children:[r.jsx(Dr,{value:"current book"}),r.jsx(yt,{className:"tw:ms-1",children:u})]}),r.jsx(yt,{className:"tw:flex tw:items-center",children:e}),r.jsx("div",{className:"tw:flex tw:items-center tw:justify-end",children:r.jsx(Bo,{isDisabled:p==="choose books",handleSelectStartChapter:w,handleSelectEndChapter:s,chapterCount:n,startChapter:c,endChapter:i})})]}),r.jsxs("div",{className:"tw:grid tw:grid-cols-[25%_50%_25%]",children:[r.jsxs("div",{className:"tw:flex tw:items-center",children:[r.jsx(Dr,{value:"choose books"}),r.jsx(yt,{className:"tw:ms-1",children:m})]}),r.jsx(yt,{className:"tw:flex tw:items-center",children:o.map(b=>st.Canon.bookIdToEnglishName(b)).join(", ")}),r.jsx(F,{disabled:p==="current book",onClick:()=>a(),children:g})]})]})})}const Vo=l.createContext(null);function Xi(t,e){return{getTheme:function(){return e??null}}}function ve(){const t=l.useContext(Vo);return t==null&&function(e,...a){const o=new URL("https://lexical.dev/docs/error"),n=new URLSearchParams;n.append("code",e);for(const i of a)n.append("v",i);throw o.search=n.toString(),Error(`Minified Lexical error #${e}; visit ${o.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(8),t}const Fo=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,Zi=Fo?l.useLayoutEffect:l.useEffect,br={tag:v.HISTORY_MERGE_TAG};function Ji({initialConfig:t,children:e}){const a=l.useMemo(()=>{const{theme:o,namespace:n,nodes:i,onError:s,editorState:c,html:w}=t,d=Xi(null,o),u=v.createEditor({editable:t.editable,html:w,namespace:n,nodes:i,onError:g=>s(g,u),theme:o});return function(g,m){if(m!==null){if(m===void 0)g.update(()=>{const p=v.$getRoot();if(p.isEmpty()){const f=v.$createParagraphNode();p.append(f);const y=Fo?document.activeElement:null;(v.$getSelection()!==null||y!==null&&y===g.getRootElement())&&f.select()}},br);else if(m!==null)switch(typeof m){case"string":{const p=g.parseEditorState(m);g.setEditorState(p,br);break}case"object":g.setEditorState(m,br);break;case"function":g.update(()=>{v.$getRoot().isEmpty()&&m(g)},br)}}}(u,c),[u,d]},[]);return Zi(()=>{const o=t.editable,[n]=a;n.setEditable(o===void 0||o)},[]),r.jsx(Vo.Provider,{value:a,children:e})}const Qi=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function ts({ignoreHistoryMergeTagChange:t=!0,ignoreSelectionChange:e=!1,onChange:a}){const[o]=ve();return Qi(()=>{if(a)return o.registerUpdateListener(({editorState:n,dirtyElements:i,dirtyLeaves:s,prevEditorState:c,tags:w})=>{e&&i.size===0&&s.size===0||t&&w.has(v.HISTORY_MERGE_TAG)||c.isEmpty()||a(n,o,w)})},[o,t,e,a]),null}const Ca={ltr:"tw:text-left",rtl:"tw:text-right",heading:{h1:"tw:scroll-m-20 tw:text-4xl tw:font-extrabold tw:tracking-tight tw:lg:text-5xl",h2:"tw:scroll-m-20 tw:border-b tw:pb-2 tw:text-3xl tw:font-semibold tw:tracking-tight tw:first:mt-0",h3:"tw:scroll-m-20 tw:text-2xl tw:font-semibold tw:tracking-tight",h4:"tw:scroll-m-20 tw:text-xl tw:font-semibold tw:tracking-tight",h5:"tw:scroll-m-20 tw:text-lg tw:font-semibold tw:tracking-tight",h6:"tw:scroll-m-20 tw:text-base tw:font-semibold tw:tracking-tight"},paragraph:"tw:outline-hidden",quote:"tw:mt-6 tw:border-l-2 tw:pl-6 tw:italic",link:"tw:text-blue-600 tw:hover:underline tw:hover:cursor-pointer",list:{checklist:"tw:relative",listitem:"tw:mx-8",listitemChecked:'tw:relative tw:mx-2 tw:px-6 tw:list-none tw:outline-hidden tw:line-through tw:before:content-[""] tw:before:w-4 tw:before:h-4 tw:before:top-0.5 tw:before:left-0 tw:before:cursor-pointer tw:before:block tw:before:bg-cover tw:before:absolute tw:before:border tw:before:border-primary tw:before:rounded tw:before:bg-primary tw:before:bg-no-repeat tw:after:content-[""] tw:after:cursor-pointer tw:after:border-white tw:after:border-solid tw:after:absolute tw:after:block tw:after:top-[6px] tw:after:w-[3px] tw:after:left-[7px] tw:after:right-[7px] tw:after:h-[6px] tw:after:rotate-45 tw:after:border-r-2 tw:after:border-b-2 tw:after:border-l-0 tw:after:border-t-0',listitemUnchecked:'tw:relative tw:mx-2 tw:px-6 tw:list-none tw:outline-hidden tw:before:content-[""] tw:before:w-4 tw:before:h-4 tw:before:top-0.5 tw:before:left-0 tw:before:cursor-pointer tw:before:block tw:before:bg-cover tw:before:absolute tw:before:border tw:before:border-primary tw:before:rounded',nested:{listitem:"tw:list-none tw:before:hidden tw:after:hidden"},ol:"tw:m-0 tw:p-0 tw:list-decimal tw:[&>li]:mt-2",olDepth:["tw:list-outside tw:!list-decimal","tw:list-outside tw:!list-[upper-roman]","tw:list-outside tw:!list-[lower-roman]","tw:list-outside tw:!list-[upper-alpha]","tw:list-outside tw:!list-[lower-alpha]"],ul:"tw:m-0 tw:p-0 tw:list-outside tw:[&>li]:mt-2",ulDepth:["tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc"]},hashtag:"tw:text-blue-600 tw:bg-blue-100 tw:rounded-md tw:px-1",text:{bold:"tw:font-bold",code:"tw:bg-gray-100 tw:p-1 tw:rounded-md",italic:"tw:italic",strikethrough:"tw:line-through",subscript:"tw:sub",superscript:"tw:sup",underline:"tw:underline",underlineStrikethrough:"tw:underline tw:line-through"},image:"tw:relative tw:inline-block tw:user-select-none tw:cursor-default editor-image",inlineImage:"tw:relative tw:inline-block tw:user-select-none tw:cursor-default inline-editor-image",keyword:"tw:text-purple-900 tw:font-bold",code:"EditorTheme__code",codeHighlight:{atrule:"EditorTheme__tokenAttr",attr:"EditorTheme__tokenAttr",boolean:"EditorTheme__tokenProperty",builtin:"EditorTheme__tokenSelector",cdata:"EditorTheme__tokenComment",char:"EditorTheme__tokenSelector",class:"EditorTheme__tokenFunction","class-name":"EditorTheme__tokenFunction",comment:"EditorTheme__tokenComment",constant:"EditorTheme__tokenProperty",deleted:"EditorTheme__tokenProperty",doctype:"EditorTheme__tokenComment",entity:"EditorTheme__tokenOperator",function:"EditorTheme__tokenFunction",important:"EditorTheme__tokenVariable",inserted:"EditorTheme__tokenSelector",keyword:"EditorTheme__tokenAttr",namespace:"EditorTheme__tokenVariable",number:"EditorTheme__tokenProperty",operator:"EditorTheme__tokenOperator",prolog:"EditorTheme__tokenComment",property:"EditorTheme__tokenProperty",punctuation:"EditorTheme__tokenPunctuation",regex:"EditorTheme__tokenVariable",selector:"EditorTheme__tokenSelector",string:"EditorTheme__tokenSelector",symbol:"EditorTheme__tokenProperty",tag:"EditorTheme__tokenProperty",url:"EditorTheme__tokenOperator",variable:"EditorTheme__tokenVariable"},characterLimit:"tw:!bg-destructive/50",table:"EditorTheme__table tw:w-fit tw:overflow-scroll tw:border-collapse",tableCell:"EditorTheme__tableCell tw:w-24 tw:relative tw:border tw:px-4 tw:py-2 tw:text-left tw:[&[align=center]]:text-center tw:[&[align=right]]:text-right",tableCellActionButton:"EditorTheme__tableCellActionButton tw:bg-background tw:block tw:border-0 tw:rounded-2xl tw:w-5 tw:h-5 tw:text-foreground tw:cursor-pointer",tableCellActionButtonContainer:"EditorTheme__tableCellActionButtonContainer tw:block tw:right-1 tw:top-1.5 tw:absolute tw:z-10 tw:w-5 tw:h-5",tableCellEditing:"EditorTheme__tableCellEditing tw:rounded-sm tw:shadow-sm",tableCellHeader:"EditorTheme__tableCellHeader tw:bg-muted tw:border tw:px-4 tw:py-2 tw:text-left tw:font-bold tw:[&[align=center]]:text-center tw:[&[align=right]]:text-right",tableCellPrimarySelected:"EditorTheme__tableCellPrimarySelected tw:border tw:border-primary tw:border-solid tw:block tw:h-[calc(100%-2px)] tw:w-[calc(100%-2px)] tw:absolute tw:-left-[1px] tw:-top-[1px] tw:z-10 ",tableCellResizer:"EditorTheme__tableCellResizer tw:absolute tw:-right-1 tw:h-full tw:w-2 tw:cursor-ew-resize tw:z-10 tw:top-0",tableCellSelected:"EditorTheme__tableCellSelected tw:bg-muted",tableCellSortedIndicator:"EditorTheme__tableCellSortedIndicator tw:block tw:opacity-50 tw:absolute tw:bottom-0 tw:left-0 tw:w-full tw:h-1 tw:bg-muted",tableResizeRuler:"EditorTheme__tableCellResizeRuler tw:block tw:absolute tw:w-[1px] tw:h-full tw:bg-primary tw:top-0",tableRowStriping:"EditorTheme__tableRowStriping tw:m-0 tw:border-t tw:p-0 tw:even:bg-muted",tableSelected:"EditorTheme__tableSelected tw:ring-2 tw:ring-primary tw:ring-offset-2",tableSelection:"EditorTheme__tableSelection tw:bg-transparent",layoutItem:"tw:border tw:border-dashed tw:px-4 tw:py-2",layoutContainer:"tw:grid tw:gap-2.5 tw:my-2.5 tw:mx-0",autocomplete:"tw:text-muted-foreground",blockCursor:"",embedBlock:{base:"tw:user-select-none",focus:"tw:ring-2 tw:ring-primary tw:ring-offset-2"},hr:'tw:p-0.5 tw:border-none tw:my-1 tw:mx-0 tw:cursor-pointer tw:after:content-[""] tw:after:block tw:after:h-0.5 tw:after:bg-muted tw:selected:ring-2 tw:selected:ring-primary tw:selected:ring-offset-2 tw:selected:user-select-none',indent:"[--lexical-indent-base-value:40px]",mark:"",markOverlap:""};function Ot({delayDuration:t=0,...e}){return r.jsx(k.Tooltip.Provider,{"data-slot":"tooltip-provider",delayDuration:t,...e})}function $t({...t}){return r.jsx(k.Tooltip.Root,{"data-slot":"tooltip",...t})}function At({className:t,variant:e,...a}){return r.jsx(k.Tooltip.Trigger,{"data-slot":"tooltip-trigger",className:e?h(ya({variant:e}),t):t,...a})}function Pt({className:t,sideOffset:e=0,style:a,children:o,...n}){return r.jsx(k.Tooltip.Portal,{children:r.jsxs(k.Tooltip.Content,{"data-slot":"tooltip-content",sideOffset:e,style:{zIndex:To,...a},className:h("pr-twp tw:inline-flex tw:w-fit tw:max-w-xs tw:origin-(--radix-tooltip-content-transform-origin) tw:items-center tw:gap-1.5 tw:rounded-md tw:bg-foreground tw:px-3 tw:py-1.5 tw:text-xs tw:text-background tw:has-data-[slot=kbd]:pe-1.5 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:**:data-[slot=kbd]:relative tw:**:data-[slot=kbd]:isolate tw:**:data-[slot=kbd]:z-50 tw:**:data-[slot=kbd]:rounded-sm tw:data-[state=delayed-open]:animate-in tw:data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),...n,children:[o,r.jsx(k.Tooltip.Arrow,{className:"tw:z-50 tw:size-2.5 tw:translate-y-[calc(-50%_-_2px)] tw:rotate-45 tw:rounded-[2px] tw:bg-foreground tw:fill-foreground"})]})})}const Sa=[ca.HeadingNode,v.ParagraphNode,v.TextNode,ca.QuoteNode],es=l.createContext(null),Xr={didCatch:!1,error:null};class rs extends l.Component{constructor(e){super(e),this.resetErrorBoundary=this.resetErrorBoundary.bind(this),this.state=Xr}static getDerivedStateFromError(e){return{didCatch:!0,error:e}}resetErrorBoundary(){const{error:e}=this.state;if(e!==null){for(var a,o,n=arguments.length,i=new Array(n),s=0;s0&&arguments[0]!==void 0?arguments[0]:[],e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];return t.length!==e.length||t.some((a,o)=>!Object.is(a,e[o]))}function os({children:t,onError:e}){return r.jsx(rs,{fallback:r.jsx("div",{style:{border:"1px solid #f00",color:"#f00",padding:"8px"},children:"An error was thrown."}),onError:e,children:t})}const ns=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function is(t){return{initialValueFn:()=>t.isEditable(),subscribe:e=>t.registerEditableListener(e)}}function ss(){return function(t){const[e]=ve(),a=l.useMemo(()=>t(e),[e,t]),[o,n]=l.useState(()=>a.initialValueFn()),i=l.useRef(o);return ns(()=>{const{initialValueFn:s,subscribe:c}=a,w=s();return i.current!==w&&(i.current=w,n(w)),c(d=>{i.current=d,n(d)})},[a,t]),o}(is)}function cs(t,e){const a=t.getRootElement();if(a===null)return[];const o=a.getBoundingClientRect(),n=getComputedStyle(a),i=parseFloat(n.paddingLeft)+parseFloat(n.paddingRight),s=Array.from(e.getClientRects());let c,w=s.length;s.sort((d,u)=>{const g=d.top-u.top;return Math.abs(g)<=3?d.left-u.left:g});for(let d=0;du.top&&c.left+c.width>u.left,m=u.width+i===o.width;g||m?(s.splice(d--,1),w--):c=u}return s}function ls(t,e,a="self"){const o=t.getStartEndPoints();if(e.isSelected(t)&&!v.$isTokenOrSegmented(e)&&o!==null){const[n,i]=o,s=t.isBackward(),c=n.getNode(),w=i.getNode(),d=e.is(c),u=e.is(w);if(d||u){const[g,m]=v.$getCharacterOffsets(t),p=c.is(w),f=e.is(s?w:c),y=e.is(s?c:w);let b,I=0;p?(I=g>m?m:g,b=g>m?g:m):f?(I=s?m:g,b=void 0):y&&(I=0,b=s?g:m);const C=e.__text.slice(I,b);C!==e.__text&&(a==="clone"&&(e=v.$cloneWithPropertiesEphemeral(e)),e.__text=C)}}return e}function Ir(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const Go=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,ds=Go&&"documentMode"in document?document.documentMode:null;!(!Go||!("InputEvent"in window)||ds)&&"getTargetRanges"in new window.InputEvent("input");function de(t){return`${t}px`}const ws={attributes:!0,characterData:!0,childList:!0,subtree:!0};function us(t,e,a){let o=null,n=null,i=null,s=[];const c=document.createElement("div");function w(){o===null&&Ir(182),n===null&&Ir(183);const{left:g,top:m}=n.getBoundingClientRect(),p=cs(t,e);var f,y;c.isConnected||(y=c,(f=n).insertBefore(y,f.firstChild));let b=!1;for(let I=0;Ip.length;)s.pop();b&&a(s)}function d(){n=null,o=null,i!==null&&i.disconnect(),i=null,c.remove();for(const g of s)g.remove();s=[]}c.style.position="relative";const u=t.registerRootListener(function g(){const m=t.getRootElement();if(m===null)return d();const p=m.parentElement;if(!v.isHTMLElement(p))return d();d(),o=m,n=p,i=new MutationObserver(f=>{const y=t.getRootElement(),b=y&&y.parentElement;if(y!==o||b!==n)return g();for(const I of f)if(!c.contains(I.target))return w()}),i.observe(p,ws),w()});return()=>{u(),d()}}function eo(t,e,a){if(t.type!=="text"&&v.$isElementNode(e)){const o=e.getDOMSlot(a);return[o.element,o.getFirstChildOffset()+t.offset]}return[v.getDOMTextNode(a)||a,t.offset]}function ps(t){for(const e of t){const a=e.style;a.background!=="Highlight"&&(a.background="Highlight"),a.color!=="HighlightText"&&(a.color="HighlightText"),a.marginTop!==de(-1.5)&&(a.marginTop=de(-1.5)),a.paddingTop!==de(4)&&(a.paddingTop=de(4)),a.paddingBottom!==de(0)&&(a.paddingBottom=de(0))}}function gs(t,e=ps){let a=null,o=null,n=null,i=null,s=null,c=null,w=()=>{};function d(u){u.read(()=>{const g=v.$getSelection();if(!v.$isRangeSelection(g))return a=null,n=null,i=null,c=null,w(),void(w=()=>{});const[m,p]=function(j){const x=j.getStartEndPoints();return j.isBackward()?[x[1],x[0]]:x}(g),f=m.getNode(),y=f.getKey(),b=m.offset,I=p.getNode(),C=I.getKey(),T=p.offset,S=t.getElementByKey(y),N=t.getElementByKey(C),E=a===null||S!==o||b!==n||y!==a.getKey(),z=i===null||N!==s||T!==c||C!==i.getKey();if((E||z)&&S!==null&&N!==null){const j=function(x,R,P,G,q,V,K){const M=(x._window?x._window.document:document).createRange();return M.setStart(...eo(R,P,G)),M.setEnd(...eo(q,V,K)),M}(t,m,f,S,p,I,N);w(),w=us(t,j,e)}a=f,o=S,n=b,i=I,s=N,c=T})}return d(t.getEditorState()),v.mergeRegister(t.registerUpdateListener(({editorState:u})=>d(u)),()=>{w()})}function hs(t,e){let a=null;const o=()=>{const n=getSelection(),i=n&&n.anchorNode,s=t.getRootElement();i!==null&&s!==null&&s.contains(i)?a!==null&&(a(),a=null):a===null&&(a=gs(t,e))};return t.registerRootListener(n=>{if(n){const i=n.ownerDocument;return i.addEventListener("selectionchange",o),o(),()=>{a!==null&&a(),i.removeEventListener("selectionchange",o)}}})}function fs(t){const e=v.$findMatchingParent(t,a=>v.$isElementNode(a)&&!a.isInline());return v.$isElementNode(e)||Ir(4,t.__key),e}function ms(t){const e=v.$getSelection()||v.$getPreviousSelection();let a;if(v.$isRangeSelection(e))a=v.$caretFromPoint(e.focus,"next");else{if(e!=null){const s=e.getNodes(),c=s[s.length-1];c&&(a=v.$getSiblingCaret(c,"next"))}a=a||v.$getChildCaret(v.$getRoot(),"previous").getFlipped().insert(v.$createParagraphNode())}const o=vs(t,a),n=v.$getAdjacentChildCaret(o),i=v.$isChildCaret(n)?v.$normalizeCaret(n):o;return v.$setSelectionFromCaretRange(v.$getCollapsedCaretRange(i)),t.getLatest()}function vs(t,e,a){let o=v.$getCaretInDirection(e,"next");for(let n=o;n;n=v.$splitAtPointCaretNext(n,a))o=n;return v.$isTextPointCaret(o)&&Ir(283),o.insert(t.isInline()?v.$createParagraphNode().append(t):t),v.$getCaretInDirection(v.$getSiblingCaret(t.getLatest(),"next"),e.direction)}function bs(t){const e=v.$getSelection();if(!v.$isRangeSelection(e))return!1;const a=new Set,o=e.getNodes();for(let n=0;nv.$isElementNode(d)&&!d.isInline());if(c===null)continue;const w=c.getKey();c.canIndent()&&!a.has(w)&&(a.add(w),t(c))}return a.size>0}const xs=Symbol.for("preact-signals");function Fr(){if(xe>1)return void xe--;let t,e=!1;for(!function(){let a=Mr;for(Mr=void 0;a!==void 0;)a.S.v===a.v&&(a.S.i=a.i),a=a.o}();ir!==void 0;){let a=ir;for(ir=void 0,Or++;a!==void 0;){const o=a.u;if(a.u=void 0,a.f&=-3,!(8&a.f)&&Uo(a))try{a.c()}catch(n){e||(t=n,e=!0)}a=o}}if(Or=0,xe--,e)throw t}function ys(t){if(xe>0)return t();ua=++ks,xe++;try{return t()}finally{Fr()}}let Q,ir;function ro(t){const e=Q;Q=void 0;try{return t()}finally{Q=e}}let Mr,xe=0,Or=0,ks=0,ua=0,Cr=0;function ao(t){if(Q===void 0)return;let e=t.n;return e===void 0||e.t!==Q?(e={i:0,S:t,p:Q.s,n:void 0,t:Q,e:void 0,x:void 0,r:e},Q.s!==void 0&&(Q.s.n=e),Q.s=e,t.n=e,32&Q.f&&t.S(e),e):e.i===-1?(e.i=0,e.n!==void 0&&(e.n.p=e.p,e.p!==void 0&&(e.p.n=e.n),e.p=Q.s,e.n=void 0,Q.s.n=e,Q.s=e),e):void 0}function It(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.l=0,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function cr(t,e){return new It(t,e)}function Uo(t){for(let e=t.s;e!==void 0;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function oo(t){for(let e=t.s;e!==void 0;e=e.n){const a=e.S.n;if(a!==void 0&&(e.r=a),e.S.n=e,e.i=-1,e.n===void 0){t.s=e;break}}}function qo(t){let e,a=t.s;for(;a!==void 0;){const o=a.p;a.i===-1?(a.S.U(a),o!==void 0&&(o.n=a.n),a.n!==void 0&&(a.n.p=o)):e=a,a.S.n=a.r,a.r!==void 0&&(a.r=void 0),a=o}t.s=e}function Ie(t,e){It.call(this,void 0),this.x=t,this.s=void 0,this.g=Cr-1,this.f=4,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function js(t,e){return new Ie(t,e)}function Ko(t){const e=t.m;if(t.m=void 0,typeof e=="function"){xe++;const a=Q;Q=void 0;try{e()}catch(o){throw t.f&=-2,t.f|=8,Ea(t),o}finally{Q=a,Fr()}}}function Ea(t){for(let e=t.s;e!==void 0;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,Ko(t)}function _s(t){if(Q!==this)throw new Error("Out-of-order effect");qo(this),Q=t,this.f&=-2,8&this.f&&Ea(this),Fr()}function Ke(t,e){this.x=t,this.m=void 0,this.s=void 0,this.u=void 0,this.f=32,this.name=e==null?void 0:e.name}function ue(t,e){const a=new Ke(t,e);try{a.c()}catch(n){throw a.d(),n}const o=a.d.bind(a);return o[Symbol.dispose]=o,o}function Je(t,e={}){const a={};for(const o in t){const n=e[o],i=cr(n===void 0?t[o]:n);a[o]=i}return a}It.prototype.brand=xs,It.prototype.h=function(){return!0},It.prototype.S=function(t){const e=this.t;e!==t&&t.e===void 0&&(t.x=e,this.t=t,e!==void 0?e.e=t:ro(()=>{var a;(a=this.W)==null||a.call(this)}))},It.prototype.U=function(t){if(this.t!==void 0){const e=t.e,a=t.x;e!==void 0&&(e.x=a,t.e=void 0),a!==void 0&&(a.e=e,t.x=void 0),t===this.t&&(this.t=a,a===void 0&&ro(()=>{var o;(o=this.Z)==null||o.call(this)}))}},It.prototype.subscribe=function(t){return ue(()=>{const e=this.value,a=Q;Q=void 0;try{t(e)}finally{Q=a}},{name:"sub"})},It.prototype.valueOf=function(){return this.value},It.prototype.toString=function(){return this.value+""},It.prototype.toJSON=function(){return this.value},It.prototype.peek=function(){const t=Q;Q=void 0;try{return this.value}finally{Q=t}},Object.defineProperty(It.prototype,"value",{get(){const t=ao(this);return t!==void 0&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(Or>100)throw new Error("Cycle detected");(function(e){xe!==0&&Or===0&&e.l!==ua&&(e.l=ua,Mr={S:e,v:e.v,i:e.i,o:Mr})})(this),this.v=t,this.i++,Cr++,xe++;try{for(let e=this.t;e!==void 0;e=e.x)e.t.N()}finally{Fr()}}}}),Ie.prototype=new It,Ie.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Cr))return!0;if(this.g=Cr,this.f|=1,this.i>0&&!Uo(this))return this.f&=-2,!0;const t=Q;try{oo(this),Q=this;const e=this.x();(16&this.f||this.v!==e||this.i===0)&&(this.v=e,this.f&=-17,this.i++)}catch(e){this.v=e,this.f|=16,this.i++}return Q=t,qo(this),this.f&=-2,!0},Ie.prototype.S=function(t){if(this.t===void 0){this.f|=36;for(let e=this.s;e!==void 0;e=e.n)e.S.S(e)}It.prototype.S.call(this,t)},Ie.prototype.U=function(t){if(this.t!==void 0&&(It.prototype.U.call(this,t),this.t===void 0)){this.f&=-33;for(let e=this.s;e!==void 0;e=e.n)e.S.U(e)}},Ie.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;t!==void 0;t=t.x)t.t.N()}},Object.defineProperty(Ie.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const t=ao(this);if(this.h(),t!==void 0&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),Ke.prototype.c=function(){const t=this.S();try{if(8&this.f||this.x===void 0)return;const e=this.x();typeof e=="function"&&(this.m=e)}finally{t()}},Ke.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,Ko(this),oo(this),xe++;const t=Q;return Q=this,_s.bind(this,t)},Ke.prototype.N=function(){2&this.f||(this.f|=2,this.u=ir,ir=this)},Ke.prototype.d=function(){this.f|=8,1&this.f||Ea(this)},Ke.prototype.dispose=function(){this.d()};v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({defaultSelection:"rootEnd",disabled:!1}),name:"@lexical/extension/AutoFocus",register(t,e,a){const o=a.getOutput();return ue(()=>o.disabled.value?void 0:t.registerRootListener(n=>{t.focus(()=>{const i=document.activeElement;n===null||i!==null&&n.contains(i)||n.focus({preventScroll:!0})},{defaultSelection:o.defaultSelection.peek()})}))}});function Ho(){const t=v.$getRoot(),e=v.$getSelection(),a=v.$createParagraphNode();t.clear(),t.append(a),e!==null&&a.select(),v.$isRangeSelection(e)&&(e.format=0)}function Yo(t,e=Ho){return t.registerCommand(v.CLEAR_EDITOR_COMMAND,a=>(t.update(e),!0),v.COMMAND_PRIORITY_EDITOR)}v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({$onClear:Ho}),name:"@lexical/extension/ClearEditor",register(t,e,a){const{$onClear:o}=a.getOutput();return ue(()=>Yo(t,o.value))}});function Ns(t){return(typeof t.nodes=="function"?t.nodes():t.nodes)||[]}const Zr=v.createState("format",{parse:t=>typeof t=="number"?t:0});class Wo extends v.DecoratorNode{$config(){return this.config("decorator-text",{extends:v.DecoratorNode,stateConfigs:[{flat:!0,stateConfig:Zr}]})}getFormat(){return v.$getState(this,Zr)}getFormatFlags(e,a){return v.toggleTextFormatType(this.getFormat(),e,a)}hasFormat(e){const a=v.TEXT_TYPE_TO_FORMAT[e];return(this.getFormat()&a)!==0}setFormat(e){return v.$setState(this,Zr,e)}toggleFormat(e){const a=this.getFormat(),o=v.toggleTextFormatType(a,e,null);return this.setFormat(o)}isInline(){return!0}createDOM(){return document.createElement("span")}updateDOM(){return!1}}function Cs(t){return t instanceof Wo}v.defineExtension({name:"@lexical/extension/DecoratorText",nodes:()=>[Wo],register:(t,e,a)=>t.registerCommand(v.FORMAT_TEXT_COMMAND,o=>{const n=v.$getSelection();if(v.$isNodeSelection(n)||v.$isRangeSelection(n))for(const i of n.getNodes())Cs(i)&&i.toggleFormat(o);return!1},v.COMMAND_PRIORITY_LOW)});function Xo(t,e){let a;return cr(t(),{unwatched(){a&&(a(),a=void 0)},watched(){this.value=t(),a=e(this)}})}const pa=v.defineExtension({build:t=>Xo(()=>t.getEditorState(),e=>t.registerUpdateListener(a=>{e.value=a.editorState})),name:"@lexical/extension/EditorState"});function it(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function Zo(t,e){if(t&&e&&!Array.isArray(e)&&typeof t=="object"&&typeof e=="object"){const a=t,o=e;for(const n in o)a[n]=Zo(a[n],o[n]);return t}return e}const Ta=0,ga=1,Jo=2,Jr=3,xr=4,qe=5,Qr=6,or=7;function ta(t){return t.id===Ta}function Qo(t){return t.id===Jo}function Ss(t){return function(e){return e.id===ga}(t)||it(305,String(t.id),String(ga)),Object.assign(t,{id:Jo})}const Es=new Set;class Ts{constructor(e,a){Dt(this,"builder");Dt(this,"configs");Dt(this,"_dependency");Dt(this,"_peerNameSet");Dt(this,"extension");Dt(this,"state");Dt(this,"_signal");this.builder=e,this.extension=a,this.configs=new Set,this.state={id:Ta}}mergeConfigs(){let e=this.extension.config||{};const a=this.extension.mergeConfig?this.extension.mergeConfig.bind(this.extension):v.shallowMergeConfig;for(const o of this.configs)e=a(e,o);return e}init(e){const a=this.state;Qo(a)||it(306,String(a.id));const o={getDependency:this.getInitDependency.bind(this),getDirectDependentNames:this.getDirectDependentNames.bind(this),getPeer:this.getInitPeer.bind(this),getPeerNameSet:this.getPeerNameSet.bind(this)},n={...o,getDependency:this.getDependency.bind(this),getInitResult:this.getInitResult.bind(this),getPeer:this.getPeer.bind(this)},i=function(c,w,d){return Object.assign(c,{config:w,id:Jr,registerState:d})}(a,this.mergeConfigs(),o);let s;this.state=i,this.extension.init&&(s=this.extension.init(e,i.config,o)),this.state=function(c,w,d){return Object.assign(c,{id:xr,initResult:w,registerState:d})}(i,s,n)}build(e){const a=this.state;let o;a.id!==xr&&it(307,String(a.id),String(qe)),this.extension.build&&(o=this.extension.build(e,a.config,a.registerState));const n={...a.registerState,getOutput:()=>o,getSignal:this.getSignal.bind(this)};this.state=function(i,s,c){return Object.assign(i,{id:qe,output:s,registerState:c})}(a,o,n)}register(e,a){this._signal=a;const o=this.state;o.id!==qe&&it(308,String(o.id),String(qe));const n=this.extension.register&&this.extension.register(e,o.config,o.registerState);return this.state=function(i){return Object.assign(i,{id:Qr})}(o),()=>{const i=this.state;i.id!==or&&it(309,String(o.id),String(or)),this.state=function(s){return Object.assign(s,{id:qe})}(i),n&&n()}}afterRegistration(e){const a=this.state;let o;return a.id!==Qr&&it(310,String(a.id),String(Qr)),this.extension.afterRegistration&&(o=this.extension.afterRegistration(e,a.config,a.registerState)),this.state=function(n){return Object.assign(n,{id:or})}(a),o}getSignal(){return this._signal===void 0&&it(311),this._signal}getInitResult(){this.extension.init===void 0&&it(312,this.extension.name);const e=this.state;return function(a){return a.id>=xr}(e)||it(313,String(e.id),String(xr)),e.initResult}getInitPeer(e){const a=this.builder.extensionNameMap.get(e);return a?a.getExtensionInitDependency():void 0}getExtensionInitDependency(){const e=this.state;return function(a){return a.id>=Jr}(e)||it(314,String(e.id),String(Jr)),{config:e.config}}getPeer(e){const a=this.builder.extensionNameMap.get(e);return a?a.getExtensionDependency():void 0}getInitDependency(e){const a=this.builder.getExtensionRep(e);return a===void 0&&it(315,this.extension.name,e.name),a.getExtensionInitDependency()}getDependency(e){const a=this.builder.getExtensionRep(e);return a===void 0&&it(315,this.extension.name,e.name),a.getExtensionDependency()}getState(){const e=this.state;return function(a){return a.id>=or}(e)||it(316,String(e.id),String(or)),e}getDirectDependentNames(){return this.builder.incomingEdges.get(this.extension.name)||Es}getPeerNameSet(){let e=this._peerNameSet;return e||(e=new Set((this.extension.peerDependencies||[]).map(([a])=>a)),this._peerNameSet=e),e}getExtensionDependency(){if(!this._dependency){const e=this.state;(function(a){return a.id>=qe})(e)||it(317,this.extension.name),this._dependency={config:e.config,init:e.initResult,output:e.output}}return this._dependency}}const no={tag:v.HISTORY_MERGE_TAG};function Rs(){const t=v.$getRoot();t.isEmpty()&&t.append(v.$createParagraphNode())}const zs=v.defineExtension({config:v.safeCast({setOptions:no,updateOptions:no}),init:({$initialEditorState:t=Rs})=>({$initialEditorState:t,initialized:!1}),afterRegistration(t,{updateOptions:e,setOptions:a},o){const n=o.getInitResult();if(!n.initialized){n.initialized=!0;const{$initialEditorState:i}=n;if(v.$isEditorState(i))t.setEditorState(i,a);else if(typeof i=="function")t.update(()=>{i(t)},e);else if(i&&(typeof i=="string"||typeof i=="object")){const s=t.parseEditorState(i);t.setEditorState(s,a)}}return()=>{}},name:"@lexical/extension/InitialState",nodes:[v.RootNode,v.TextNode,v.LineBreakNode,v.TabNode,v.ParagraphNode]}),io=Symbol.for("@lexical/extension/LexicalBuilder");function so(){}function Ds(t){throw t}function yr(t){return Array.isArray(t)?t:[t]}const ea="0.43.0+prod.esm";class He{constructor(e){Dt(this,"roots");Dt(this,"extensionNameMap");Dt(this,"outgoingConfigEdges");Dt(this,"incomingEdges");Dt(this,"conflicts");Dt(this,"_sortedExtensionReps");Dt(this,"PACKAGE_VERSION");this.outgoingConfigEdges=new Map,this.incomingEdges=new Map,this.extensionNameMap=new Map,this.conflicts=new Map,this.PACKAGE_VERSION=ea,this.roots=e;for(const a of e)this.addExtension(a)}static fromExtensions(e){const a=[yr(zs)];for(const o of e)a.push(yr(o));return new He(a)}static maybeFromEditor(e){const a=e[io];return a&&(a.PACKAGE_VERSION!==ea&&it(292,a.PACKAGE_VERSION,ea),a instanceof He||it(293)),a}static fromEditor(e){const a=He.maybeFromEditor(e);return a===void 0&&it(294),a}constructEditor(){const{$initialEditorState:e,onError:a,...o}=this.buildCreateEditorArgs(),n=Object.assign(v.createEditor({...o,...a?{onError:i=>{a(i,n)}}:{}}),{[io]:this});for(const i of this.sortedExtensionReps())i.build(n);return n}buildEditor(){let e=so;function a(){try{e()}finally{e=so}}const o=Object.assign(this.constructEditor(),{dispose:a,[Symbol.dispose]:a});return e=v.mergeRegister(this.registerEditor(o),()=>o.setRootElement(null)),o}hasExtensionByName(e){return this.extensionNameMap.has(e)}getExtensionRep(e){const a=this.extensionNameMap.get(e.name);if(a)return a.extension!==e&&it(295,e.name),a}addEdge(e,a,o){const n=this.outgoingConfigEdges.get(e);n?n.set(a,o):this.outgoingConfigEdges.set(e,new Map([[a,o]]));const i=this.incomingEdges.get(a);i?i.add(e):this.incomingEdges.set(a,new Set([e]))}addExtension(e){this._sortedExtensionReps!==void 0&&it(296);const a=yr(e),[o]=a;typeof o.name!="string"&&it(297,typeof o.name);let n=this.extensionNameMap.get(o.name);if(n!==void 0&&n.extension!==o&&it(298,o.name),!n){n=new Ts(this,o),this.extensionNameMap.set(o.name,n);const i=this.conflicts.get(o.name);typeof i=="string"&&it(299,o.name,i);for(const s of o.conflictsWith||[])this.extensionNameMap.has(s)&&it(299,o.name,s),this.conflicts.set(s,o.name);for(const s of o.dependencies||[]){const c=yr(s);this.addEdge(o.name,c[0].name,c.slice(1)),this.addExtension(c)}for(const[s,c]of o.peerDependencies||[])this.addEdge(o.name,s,c?[c]:[])}}sortedExtensionReps(){if(this._sortedExtensionReps)return this._sortedExtensionReps;const e=[],a=(o,n)=>{let i=o.state;if(Qo(i))return;const s=o.extension.name;var c;ta(i)||it(300,s,n||"[unknown]"),ta(c=i)||it(304,String(c.id),String(Ta)),i=Object.assign(c,{id:ga}),o.state=i;const w=this.outgoingConfigEdges.get(s);if(w)for(const d of w.keys()){const u=this.extensionNameMap.get(d);u&&a(u,s)}i=Ss(i),o.state=i,e.push(o)};for(const o of this.extensionNameMap.values())ta(o.state)&&a(o);for(const o of e)for(const[n,i]of this.outgoingConfigEdges.get(o.extension.name)||[])if(i.length>0){const s=this.extensionNameMap.get(n);if(s)for(const c of i)s.configs.add(c)}for(const[o,...n]of this.roots)if(n.length>0){const i=this.extensionNameMap.get(o.name);i===void 0&&it(301,o.name);for(const s of n)i.configs.add(s)}return this._sortedExtensionReps=e,this._sortedExtensionReps}registerEditor(e){const a=this.sortedExtensionReps(),o=new AbortController,n=[()=>o.abort()],i=o.signal;for(const s of a){const c=s.register(e,i);c&&n.push(c)}for(const s of a){const c=s.afterRegistration(e);c&&n.push(c)}return v.mergeRegister(...n)}buildCreateEditorArgs(){const e={},a=new Set,o=new Map,n=new Map,i={},s={},c=this.sortedExtensionReps();for(const u of c){const{extension:g}=u;if(g.onError!==void 0&&(e.onError=g.onError),g.disableEvents!==void 0&&(e.disableEvents=g.disableEvents),g.parentEditor!==void 0&&(e.parentEditor=g.parentEditor),g.editable!==void 0&&(e.editable=g.editable),g.namespace!==void 0&&(e.namespace=g.namespace),g.$initialEditorState!==void 0&&(e.$initialEditorState=g.$initialEditorState),g.nodes)for(const m of Ns(g)){if(typeof m!="function"){const p=o.get(m.replace);p&&it(302,g.name,m.replace.name,p.extension.name),o.set(m.replace,u)}a.add(m)}if(g.html){if(g.html.export)for(const[m,p]of g.html.export.entries())n.set(m,p);g.html.import&&Object.assign(i,g.html.import)}g.theme&&Zo(s,g.theme)}Object.keys(s).length>0&&(e.theme=s),a.size&&(e.nodes=[...a]);const w=Object.keys(i).length>0,d=n.size>0;(w||d)&&(e.html={},w&&(e.html.import=i),d&&(e.html.export=n));for(const u of c)u.init(e);return e.onError||(e.onError=Ds),e}}const Is=new Set,co=v.defineExtension({build(t,e,a){const o=a.getDependency(pa).output,n=cr({watchedNodeKeys:new Map}),i=Xo(()=>{},()=>ue(()=>{const s=i.peek(),{watchedNodeKeys:c}=n.value;let w,d=!1;o.value.read(()=>{if(v.$getSelection())for(const[u,g]of c.entries()){if(g.size===0){c.delete(u);continue}const m=v.$getNodeByKey(u),p=m&&m.isSelected()||!1;d=d||p!==(!!s&&s.has(u)),p&&(w=w||new Set,w.add(u))}}),!d&&w&&s&&w.size===s.size||(i.value=w)}));return{watchNodeKey:function(s){const c=js(()=>(i.value||Is).has(s)),{watchedNodeKeys:w}=n.peek();let d=w.get(s);const u=d!==void 0;return d=d||new Set,d.add(c),u||(w.set(s,d),n.value={watchedNodeKeys:w}),c}}},dependencies:[pa],name:"@lexical/extension/NodeSelection"}),Ms=v.createCommand("INSERT_HORIZONTAL_RULE_COMMAND");class Ye extends v.DecoratorNode{static getType(){return"horizontalrule"}static clone(e){return new Ye(e.__key)}static importJSON(e){return Ra().updateFromJSON(e)}static importDOM(){return{hr:()=>({conversion:Os,priority:0})}}exportDOM(){return{element:document.createElement("hr")}}createDOM(e){const a=document.createElement("hr");return v.addClassNamesToElement(a,e.theme.hr),a}getTextContent(){return` -`}isInline(){return!1}updateDOM(){return!1}}function Os(){return{node:Ra()}}function Ra(){return v.$create(Ye)}function $s(t){return t instanceof Ye}v.defineExtension({dependencies:[pa,co],name:"@lexical/extension/HorizontalRule",nodes:()=>[Ye],register(t,e,a){const{watchNodeKey:o}=a.getDependency(co).output,n=cr({nodeSelections:new Map}),i=t._config.theme.hrSelected??"selected";return v.mergeRegister(t.registerCommand(Ms,s=>{const c=v.$getSelection();if(!v.$isRangeSelection(c))return!1;if(c.focus.getNode()!==null){const w=Ra();ms(w)}return!0},v.COMMAND_PRIORITY_EDITOR),t.registerCommand(v.CLICK_COMMAND,s=>{if(v.isDOMNode(s.target)){const c=v.$getNodeFromDOMNode(s.target);if($s(c))return function(w,d=!1){const u=v.$getSelection(),g=w.isSelected(),m=w.getKey();let p;d&&v.$isNodeSelection(u)?p=u:(p=v.$createNodeSelection(),v.$setSelection(p)),g?p.delete(m):p.add(m)}(c,s.shiftKey),!0}return!1},v.COMMAND_PRIORITY_LOW),t.registerMutationListener(Ye,(s,c)=>{ys(()=>{let w=!1;const{nodeSelections:d}=n.peek();for(const[u,g]of s.entries())if(g==="destroyed")d.delete(u),w=!0;else{const m=d.get(u),p=t.getElementByKey(u);m?m.domNode.value=p:(w=!0,d.set(u,{domNode:cr(p),selectedSignal:o(u)}))}w&&(n.value={nodeSelections:d})})}),ue(()=>{const s=[];for(const{domNode:c,selectedSignal:w}of n.value.nodeSelections.values())s.push(ue(()=>{const d=c.value;d&&(w.value?v.addClassNamesToElement(d,i):v.removeClassNamesFromElement(d,i))}));return v.mergeRegister(...s)}))}});v.defineExtension({build:(t,e)=>Je({inheritEditableFromParent:e.inheritEditableFromParent}),config:v.safeCast({$getParentEditor:function(){const t=v.$getEditor();return He.fromEditor(t),t},inheritEditableFromParent:!1}),init:(t,e,a)=>{const o=e.$getParentEditor();t.parentEditor=o,t.theme=t.theme||o._config.theme},name:"@lexical/extension/NestedEditor",register:(t,e,a)=>ue(()=>{const o=t._parentEditor;if(o&&a.getOutput().inheritEditableFromParent.value)return t.setEditable(o.isEditable()),o.registerEditableListener(t.setEditable.bind(t))})});v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({disabled:!1,onReposition:void 0}),name:"@lexical/utils/SelectionAlwaysOnDisplay",register:(t,e,a)=>{const o=a.getOutput();return ue(()=>{if(!o.disabled.value)return hs(t,o.onReposition.value)})}});function tn(t){return t.canBeEmpty()}function As(t,e,a=tn){return v.mergeRegister(t.registerCommand(v.KEY_TAB_COMMAND,o=>{const n=v.$getSelection();if(!v.$isRangeSelection(n))return!1;o.preventDefault();const i=function(s){if(s.getNodes().filter(m=>v.$isBlockElementNode(m)&&m.canIndent()).length>0)return!0;const c=s.anchor,w=s.focus,d=w.isBefore(c)?w:c,u=d.getNode(),g=fs(u);if(g.canIndent()){const m=g.getKey();let p=v.$createRangeSelection();if(p.anchor.set(m,0,"element"),p.focus.set(m,0,"element"),p=v.$normalizeSelection__EXPERIMENTAL(p),p.anchor.is(d))return!0}return!1}(n)?o.shiftKey?v.OUTDENT_CONTENT_COMMAND:v.INDENT_CONTENT_COMMAND:v.INSERT_TAB_COMMAND;return t.dispatchCommand(i,void 0)},v.COMMAND_PRIORITY_EDITOR),t.registerCommand(v.INDENT_CONTENT_COMMAND,()=>{const o=typeof e=="number"?e:e?e.peek():null,n=v.$getSelection();if(!v.$isRangeSelection(n))return!1;const i=typeof a=="function"?a:a.peek();return bs(s=>{if(i(s)){const c=s.getIndent()+1;(!o||cJe(e),config:v.safeCast({$canIndent:tn,disabled:!1,maxIndent:null}),name:"@lexical/extension/TabIndentation",register(t,e,a){const{disabled:o,maxIndent:n,$canIndent:i}=a.getOutput();return ue(()=>{if(!o.value)return As(t,n,i)})}});const Ps=v.defineExtension({name:"@lexical/react/ReactProvider"});function Ls(){return v.$getRoot().getTextContent()}function Bs(t,e=!0){if(t)return!1;let a=Ls();return e&&(a=a.trim()),a===""}function Vs(t){if(!Bs(t,!1))return!1;const e=v.$getRoot().getChildren(),a=e.length;if(a>1)return!1;for(let o=0;oVs(t)}function rn(t){const e=window.location.origin,a=o=>{if(o.origin!==e)return;const n=t.getRootElement();if(document.activeElement!==n)return;const i=o.data;if(typeof i=="string"){let s;try{s=JSON.parse(i)}catch{return}if(s&&s.protocol==="nuanria_messaging"&&s.type==="request"){const c=s.payload;if(c&&c.functionId==="makeChanges"){const w=c.args;if(w){const[d,u,g,m,p]=w;t.update(()=>{const f=v.$getSelection();if(v.$isRangeSelection(f)){const y=f.anchor;let b=y.getNode(),I=0,C=0;if(v.$isTextNode(b)&&d>=0&&u>=0&&(I=d,C=d+u,f.setTextNodeRange(b,I,b,C)),I===C&&g===""||(f.insertRawText(g),b=y.getNode()),v.$isTextNode(b)){I=m,C=m+p;const T=b.getTextContentSize();I=I>T?T:I,C=C>T?T:C,f.setTextNodeRange(b,I,b,C)}o.stopImmediatePropagation()}})}}}}};return window.addEventListener("message",a,!0),()=>{window.removeEventListener("message",a,!0)}}v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({disabled:typeof window>"u"}),name:"@lexical/dragon",register:(t,e,a)=>ue(()=>a.getOutput().disabled.value?void 0:rn(t))});function Fs(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const za=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Gs({editor:t,ErrorBoundary:e}){return function(a,o){const[n,i]=l.useState(()=>a.getDecorators());return za(()=>a.registerDecoratorListener(s=>{Xa.flushSync(()=>{i(s)})}),[a]),l.useEffect(()=>{i(a.getDecorators())},[a]),l.useMemo(()=>{const s=[],c=Object.keys(n);for(let w=0;wa._onError(m),children:r.jsx(l.Suspense,{fallback:null,children:n[d]})}),g=a.getElementByKey(d);g!==null&&s.push(Xa.createPortal(u,g,d))}return s},[o,n,a])}(t,e)}function Us({editor:t,ErrorBoundary:e}){return function(a){const o=He.maybeFromEditor(a);if(o&&o.hasExtensionByName(Ps.name)){for(const n of["@lexical/plain-text","@lexical/rich-text"])o.hasExtensionByName(n)&&Fs(320,n);return!0}return!1}(t)?null:r.jsx(Gs,{editor:t,ErrorBoundary:e})}function lo(t){return t.getEditorState().read(en(t.isComposing()))}function qs({contentEditable:t,placeholder:e=null,ErrorBoundary:a}){const[o]=ve();return function(n){za(()=>v.mergeRegister(ca.registerRichText(n),rn(n)),[n])}(o),r.jsxs(r.Fragment,{children:[t,r.jsx(Ks,{content:e}),r.jsx(Us,{editor:o,ErrorBoundary:a})]})}function Ks({content:t}){const[e]=ve(),a=function(n){const[i,s]=l.useState(()=>lo(n));return za(()=>{function c(){const w=lo(n);s(w)}return c(),v.mergeRegister(n.registerUpdateListener(()=>{c()}),n.registerEditableListener(()=>{c()}))},[n]),i}(e),o=ss();return a?typeof t=="function"?t(o):t:null}function Hs({defaultSelection:t}){const[e]=ve();return l.useEffect(()=>{e.focus(()=>{const a=document.activeElement,o=e.getRootElement();o===null||a!==null&&o.contains(a)||o.focus({preventScroll:!0})},{defaultSelection:t})},[t,e]),null}const Ys=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Ws({onClear:t}){const[e]=ve();return Ys(()=>Yo(e,t),[e,t]),null}const an=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Xs({editor:t,ariaActiveDescendant:e,ariaAutoComplete:a,ariaControls:o,ariaDescribedBy:n,ariaErrorMessage:i,ariaExpanded:s,ariaInvalid:c,ariaLabel:w,ariaLabelledBy:d,ariaMultiline:u,ariaOwns:g,ariaRequired:m,autoCapitalize:p,className:f,id:y,role:b="textbox",spellCheck:I=!0,style:C,tabIndex:T,"data-testid":S,...N},E){const[z,j]=l.useState(t.isEditable()),x=l.useCallback(P=>{P&&P.ownerDocument&&P.ownerDocument.defaultView?t.setRootElement(P):t.setRootElement(null)},[t]),R=l.useMemo(()=>function(...P){return G=>{for(const q of P)typeof q=="function"?q(G):q!=null&&(q.current=G)}}(E,x),[x,E]);return an(()=>(j(t.isEditable()),t.registerEditableListener(P=>{j(P)})),[t]),r.jsx("div",{"aria-activedescendant":z?e:void 0,"aria-autocomplete":z?a:"none","aria-controls":z?o:void 0,"aria-describedby":n,...i!=null?{"aria-errormessage":i}:{},"aria-expanded":z&&b==="combobox"?!!s:void 0,...c!=null?{"aria-invalid":c}:{},"aria-label":w,"aria-labelledby":d,"aria-multiline":u,"aria-owns":z?g:void 0,"aria-readonly":!z||void 0,"aria-required":m,autoCapitalize:p,className:f,contentEditable:z,"data-testid":S,id:y,ref:R,role:b,spellCheck:I,style:C,tabIndex:T,...N})}const Zs=l.forwardRef(Xs);function wo(t){return t.getEditorState().read(en(t.isComposing()))}const Js=l.forwardRef(Qs);function Qs(t,e){const{placeholder:a,...o}=t,[n]=ve();return r.jsxs(r.Fragment,{children:[r.jsx(Zs,{editor:n,...o,ref:e}),a!=null&&r.jsx(tc,{editor:n,content:a})]})}function tc({content:t,editor:e}){const a=function(s){const[c,w]=l.useState(()=>wo(s));return an(()=>{function d(){const u=wo(s);w(u)}return d(),v.mergeRegister(s.registerUpdateListener(()=>{d()}),s.registerEditableListener(()=>{d()}))},[s]),c}(e),[o,n]=l.useState(e.isEditable());if(l.useLayoutEffect(()=>(n(e.isEditable()),e.registerEditableListener(s=>{n(s)})),[e]),!a)return null;let i=null;return typeof t=="function"?i=t(o):t!==null&&(i=t),i===null?null:r.jsx("div",{"aria-hidden":!0,children:i})}function ec({placeholder:t,className:e,placeholderClassName:a}){return r.jsx(Js,{className:e??"ContentEditable__root tw:relative tw:block tw:min-h-11 tw:overflow-auto tw:px-3 tw:py-3 tw:text-sm tw:outline-hidden","aria-placeholder":t,placeholder:r.jsx("div",{className:a??"tw:pointer-events-none tw:absolute tw:top-0 tw:select-none tw:overflow-hidden tw:text-ellipsis tw:px-3 tw:py-3 tw:text-sm tw:text-muted-foreground",children:t})})}const on=l.createContext(void 0);function rc({activeEditor:t,$updateToolbar:e,blockType:a,setBlockType:o,showModal:n,children:i}){const s=l.useMemo(()=>({activeEditor:t,$updateToolbar:e,blockType:a,setBlockType:o,showModal:n}),[t,e,a,o,n]);return r.jsx(on.Provider,{value:s,children:i})}function nn(){const t=l.useContext(on);if(!t)throw new Error("useToolbarContext must be used within a ToolbarContext provider");return t}function ac(){const[t,e]=l.useState(void 0),a=l.useCallback(()=>{e(void 0)},[]),o=l.useMemo(()=>{if(t===void 0)return;const{title:i,content:s}=t;return r.jsx(Er,{open:!0,onOpenChange:a,children:r.jsxs(Tr,{children:[r.jsx(Rr,{children:r.jsx(zr,{children:i})}),s]})})},[t,a]),n=l.useCallback((i,s,c=!1)=>{e({closeOnClickOutside:c,content:s(a),title:i})},[a]);return[o,n]}function oc({children:t}){const[e]=ve(),[a,o]=l.useState(e),[n,i]=l.useState("paragraph"),[s,c]=ac(),w=()=>{};return l.useEffect(()=>a.registerCommand(v.SELECTION_CHANGE_COMMAND,(d,u)=>(o(u),!1),v.COMMAND_PRIORITY_CRITICAL),[a]),r.jsxs(rc,{activeEditor:a,$updateToolbar:w,blockType:n,setBlockType:i,showModal:c,children:[s,t({blockType:n})]})}function nc(t){const[e]=ve(),{activeEditor:a}=nn();l.useEffect(()=>a.registerCommand(v.SELECTION_CHANGE_COMMAND,()=>{const o=v.$getSelection();return o&&t(o),!1},v.COMMAND_PRIORITY_CRITICAL),[e,t]),l.useEffect(()=>{a.getEditorState().read(()=>{const o=v.$getSelection();o&&t(o)})},[a,t])}const ic=ge.cva("pr-twp tw:group/toggle tw:inline-flex tw:items-center tw:justify-center tw:gap-1 tw:rounded-lg tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:hover:bg-muted tw:hover:text-foreground tw:focus-visible:border-ring tw:focus-visible:ring-[3px] tw:focus-visible:ring-ring/50 tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-destructive/20 tw:aria-pressed:bg-muted tw:data-[state=on]:bg-muted tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",{variants:{variant:{default:"tw:bg-transparent",outline:"tw:border tw:border-input tw:bg-transparent tw:hover:bg-muted"},size:{default:"tw:h-8 tw:min-w-8 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",sm:"tw:h-7 tw:min-w-7 tw:rounded-[min(var(--tw-radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3.5",lg:"tw:h-9 tw:min-w-9 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2"}},defaultVariants:{variant:"default",size:"default"}}),sn=l.createContext({size:"default",variant:"default",spacing:0,orientation:"horizontal"});function Da({className:t,variant:e,size:a,spacing:o=0,orientation:n="horizontal",children:i,...s}){const c=ft();return r.jsx(k.ToggleGroup.Root,{"data-slot":"toggle-group","data-variant":e,"data-size":a,"data-spacing":o,"data-orientation":n,style:{"--gap":o},className:h("pr-twp tw:group/toggle-group tw:flex tw:w-fit tw:flex-row tw:items-center tw:gap-[--spacing(var(--gap))] tw:rounded-lg tw:data-[size=sm]:rounded-[min(var(--tw-radius-md),10px)] tw:data-vertical:flex-col tw:data-vertical:items-stretch",t),dir:c,...s,children:r.jsx(sn.Provider,{value:l.useMemo(()=>({variant:e,size:a,spacing:o,orientation:n}),[e,a,o,n]),children:i})})}function sr({className:t,children:e,variant:a="default",size:o="default",...n}){const i=l.useContext(sn);return r.jsx(k.ToggleGroup.Item,{"data-slot":"toggle-group-item","data-variant":i.variant||a,"data-size":i.size||o,"data-spacing":i.spacing,className:h("tw:shrink-0 tw:group-data-[spacing=0]/toggle-group:rounded-none tw:group-data-[spacing=0]/toggle-group:px-2 tw:focus:z-10 tw:focus-visible:z-10 tw:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pe-1.5 tw:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:ps-1.5 tw:group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-s-lg tw:group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg tw:group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-e-lg tw:group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg tw:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-s-0 tw:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 tw:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-s tw:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",ic({variant:i.variant||a,size:i.size||o}),t),...n,children:e})}const uo=[{format:"bold",icon:$.BoldIcon,label:"Bold"},{format:"italic",icon:$.ItalicIcon,label:"Italic"}];function sc(){const{activeEditor:t}=nn(),[e,a]=l.useState([]),o=l.useCallback(n=>{if(v.$isRangeSelection(n)||vi.$isTableSelection(n)){const i=[];uo.forEach(({format:s})=>{n.hasFormat(s)&&i.push(s)}),a(s=>s.length!==i.length||!i.every(c=>s.includes(c))?i:s)}},[]);return nc(o),r.jsx(Da,{type:"multiple",value:e,onValueChange:a,variant:"outline",size:"sm",children:uo.map(({format:n,icon:i,label:s})=>r.jsx(sr,{value:n,"aria-label":s,onClick:()=>{t.dispatchCommand(v.FORMAT_TEXT_COMMAND,n)},children:r.jsx(i,{className:"tw:h-4 tw:w-4"})},n))})}function cc({onClear:t}){const[e]=ve();l.useEffect(()=>{t&&t(()=>{e.dispatchCommand(v.CLEAR_EDITOR_COMMAND,void 0)})},[e,t])}function lc({placeholder:t="Start typing ...",autoFocus:e=!1,onClear:a}){const[,o]=l.useState(void 0),n=i=>{i!==void 0&&o(i)};return r.jsxs("div",{className:"tw:relative",children:[r.jsx(oc,{children:()=>r.jsx("div",{className:"tw:sticky tw:top-0 tw:z-10 tw:flex tw:gap-2 tw:overflow-auto tw:border-b tw:p-1",children:r.jsx(sc,{})})}),r.jsxs("div",{className:"tw:relative",children:[r.jsx(qs,{contentEditable:r.jsx("div",{ref:n,children:r.jsx(ec,{placeholder:t})}),ErrorBoundary:os}),e&&r.jsx(Hs,{defaultSelection:"rootEnd"}),r.jsx(cc,{onClear:a}),r.jsx(Ws,{})]})]})}const dc={namespace:"commentEditor",theme:Ca,nodes:Sa,onError:t=>{console.error(t)}};function $r({editorState:t,editorSerializedState:e,onChange:a,onSerializedChange:o,placeholder:n="Start typing…",autoFocus:i=!1,onClear:s,className:c}){return r.jsx("div",{className:h("pr-twp tw:overflow-hidden tw:rounded-lg tw:border tw:bg-background tw:shadow",c),children:r.jsx(Ji,{initialConfig:{...dc,...t?{editorState:t}:{},...e?{editorState:JSON.stringify(e)}:{}},children:r.jsxs(Ot,{children:[r.jsx(lc,{placeholder:n,autoFocus:i,onClear:s}),r.jsx(ts,{ignoreSelectionChange:!0,onChange:w=>{a==null||a(w),o==null||o(w.toJSON())}})]})})})}function wc(t,e){const a=v.isDOMDocumentNode(e)?e.body.childNodes:e.childNodes;let o=[];const n=[];for(const i of a)if(!ln.has(i.nodeName)){const s=dn(i,t,n,!1);s!==null&&(o=o.concat(s))}return function(i){for(const s of i)s.getNextSibling()instanceof v.ArtificialNode__DO_NOT_USE&&s.insertAfter(v.$createLineBreakNode());for(const s of i){const c=s.getChildren();for(const w of c)s.insertBefore(w);s.remove()}}(n),o}function uc(t,e){if(typeof document>"u"||typeof window>"u"&&global.window===void 0)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const a=document.createElement("div"),o=v.$getRoot().getChildren();for(let n=0;n{const f=new v.ArtificialNode__DO_NOT_USE;return a.push(f),f}:v.$createParagraphNode)),c==null?m.length>0?s=s.concat(m):v.isBlockDomNode(t)&&function(f){return f.nextSibling==null||f.previousSibling==null?!1:v.isInlineDomNode(f.nextSibling)&&v.isInlineDomNode(f.previousSibling)}(t)&&(s=s.concat(v.$createLineBreakNode())):v.$isElementNode(c)&&c.append(...m),s}function pc(t,e,a){const o=t.style.textAlign,n=[];let i=[];for(let s=0;se&&"text"in e&&e.text.trim().length>0?!0:!e||!("children"in e)?!1:un(e.children)):!1}function Zt(t){var e;return(e=t==null?void 0:t.root)!=null&&e.children?un(t.root.children):!1}function gc(t){if(!t||t.trim()==="")throw new Error("Input HTML is empty");const e=No.createHeadlessEditor({namespace:"EditorUtils",theme:Ca,nodes:Sa,onError:o=>{console.error(o)}});let a;if(e.update(()=>{const n=new DOMParser().parseFromString(t,"text/html"),i=wc(e,n);v.$getRoot().clear(),v.$insertNodes(i)},{discrete:!0}),e.getEditorState().read(()=>{a=e.getEditorState().toJSON()}),!a)throw new Error("Failed to convert HTML to editor state");return a}function Ar(t){const e=No.createHeadlessEditor({namespace:"EditorUtils",theme:Ca,nodes:Sa,onError:n=>{console.error(n)}}),a=e.parseEditorState(JSON.stringify(t));e.setEditorState(a);let o="";return e.getEditorState().read(()=>{o=uc(e)}),o=o.replace(/\s+style="[^"]*"/g,"").replace(/\s+class="[^"]*"/g,"").replace(/(.*?)<\/span>/g,"$1").replace(/]*>(.*?)<\/strong><\/b>/g,"$1").replace(/]*>(.*?)<\/b><\/strong>/g,"$1").replace(/]*>(.*?)<\/em><\/i>/g,"$1").replace(/]*>(.*?)<\/i><\/em>/g,"$1").replace(/]*>(.*?)<\/span><\/u>/g,"$1").replace(/]*>(.*?)<\/span><\/s>/g,"$1").replace(//gi,"
"),o}function Ia(t){return["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(t.key)?(t.stopPropagation(),!0):!1}function $e({className:t,orientation:e="horizontal",decorative:a=!0,...o}){return r.jsx(k.Separator.Root,{"data-slot":"separator",decorative:a,orientation:e,className:h("pr-twp tw:shrink-0 tw:bg-border tw:data-horizontal:h-px tw:data-horizontal:w-full tw:data-vertical:w-px tw:data-vertical:self-stretch",t),...o})}const pn=ge.cva("tw:group/button-group tw:flex tw:w-fit tw:items-stretch tw:*:focus-visible:relative tw:*:focus-visible:z-10 tw:has-[>[data-slot=button-group]]:gap-2 tw:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg tw:[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit tw:[&>input]:flex-1",{variants:{orientation:{horizontal:"tw:[&>*:not(:first-child)]:rounded-s-none tw:[&>*:not(:first-child)]:border-s-0 tw:[&>*:not(:last-child)]:rounded-e-none tw:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg!",vertical:"tw:flex-col tw:[&>*:not(:first-child)]:rounded-t-none tw:[&>*:not(:first-child)]:border-t-0 tw:[&>*:not(:last-child)]:rounded-b-none tw:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg!"}},defaultVariants:{orientation:"horizontal"}});function Gr({className:t,orientation:e,...a}){return r.jsx("div",{role:"group","data-slot":"button-group","data-orientation":e,className:h("pr-twp",pn({orientation:e}),t),...a})}function hc({className:t,asChild:e=!1,...a}){const o=e?k.Slot.Root:"div";return r.jsx(o,{className:h("pr-twp tw:flex tw:items-center tw:gap-2 tw:rounded-lg tw:border tw:bg-muted tw:px-2.5 tw:text-sm tw:font-medium tw:[&_svg]:pointer-events-none tw:[&_svg:not([class*=size-])]:size-4",t),...a})}function Ma({className:t,orientation:e="vertical",...a}){return r.jsx($e,{"data-slot":"button-group-separator",orientation:e,className:h("pr-twp tw:relative tw:self-stretch tw:bg-input tw:data-horizontal:mx-px tw:data-horizontal:w-auto tw:data-vertical:my-px tw:data-vertical:h-auto",t),...a})}const Oa=Object.freeze(["%cancelButton_tooltip%","%acceptButton_tooltip%"]),po=(t,e)=>t[e]??e;function $a({onCancelClick:t,onAcceptClick:e,canAccept:a=!0,localizedStrings:o={},className:n="tw:h-6 tw:w-6",acceptLabel:i}){const s=po(o,"%cancelButton_tooltip%"),c=i??po(o,"%acceptButton_tooltip%");return r.jsxs(Gr,{children:[r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":s,className:n,size:"icon",onClick:t,variant:"secondary",children:r.jsx($.X,{})})}),r.jsx(Pt,{children:r.jsx("p",{children:s})})]})}),r.jsx(Ma,{}),r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":c,className:n,size:"icon",onClick:e,disabled:!a,children:r.jsx($.Check,{})})}),r.jsx(Pt,{children:r.jsx("p",{children:c})})]})})]})}function Sr(t,e){return t===""?e["%comment_assign_unassigned%"]??"Unassigned":t==="Team"?e["%comment_assign_team%"]??"Team":t}function Aa(t){const e=/Macintosh/i.test(navigator.userAgent);return t.key==="Enter"&&(e&&t.metaKey||!e&&t.ctrlKey)}const fc={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function ra(t,e){return t===""?e["%commentEditor_unassigned%"]??"Unassigned":t==="Team"?e["%commentEditor_team%"]??"Team":t}function mc({assignableUsers:t,onSave:e,onClose:a,localizedStrings:o,initialAssignedUser:n}){const[i,s]=l.useState(fc),[c,w]=l.useState(n),[d,u]=l.useState(!1),g=l.useRef(void 0),m=l.useRef(null);l.useEffect(()=>{let b=!0;const I=m.current;if(!I)return;const C=setTimeout(()=>{b&&wn(I)},300);return()=>{b=!1,clearTimeout(C)}},[]);const p=l.useCallback(()=>{if(!Zt(i))return;const b=Ar(i);e(b,c)},[i,e,c]),f=o["%commentEditor_placeholder%"]??"Type your comment here...",y=o["%commentEditor_assignTo_label%"]??"Assign to";return r.jsxs("div",{className:"pr-twp tw:grid tw:gap-3",children:[r.jsxs("div",{className:"tw:flex tw:items-center tw:justify-between",children:[r.jsx("span",{className:"tw:text-sm tw:font-medium",children:y}),r.jsx($a,{onCancelClick:a,onAcceptClick:p,canAccept:Zt(i),localizedStrings:o,acceptLabel:o["%commentEditor_saveButton_tooltip%"]})]}),r.jsx("div",{className:"tw:flex tw:items-center tw:gap-2",children:r.jsxs(se,{open:d,onOpenChange:u,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:"outline",className:"tw:flex tw:w-full tw:items-center tw:justify-start tw:gap-2",disabled:t.length===0,children:[r.jsx($.AtSign,{className:"tw:h-4 tw:w-4"}),r.jsx("span",{children:ra(c!==void 0?c:"",o)})]})}),r.jsx(ce,{className:"tw:w-auto tw:p-0",align:"start",onKeyDown:b=>{b.key==="Escape"&&(b.stopPropagation(),u(!1))},children:r.jsx(he,{children:r.jsx(fe,{children:t.map(b=>r.jsx(ie,{onSelect:()=>{w(b||void 0),u(!1)},className:"tw:flex tw:items-center",children:r.jsx("span",{children:ra(b,o)})},b||"unassigned"))})})})]})}),r.jsx("div",{ref:m,role:"textbox",tabIndex:-1,className:"tw:outline-hidden",onKeyDownCapture:b=>{b.key==="Escape"?(b.preventDefault(),b.stopPropagation(),a()):Aa(b)&&(b.preventDefault(),b.stopPropagation(),Zt(i)&&p())},onKeyDown:b=>{Ia(b),(b.key==="Enter"||b.key===" ")&&b.stopPropagation()},children:r.jsx($r,{editorSerializedState:i,onSerializedChange:b=>s(b),placeholder:f,onClear:b=>{g.current=b}})})]})}const vc=Object.freeze(["%commentEditor_placeholder%","%commentEditor_assignTo_label%","%commentEditor_saveButton_tooltip%","%commentEditor_unassigned%","%commentEditor_team%",...Oa]),bc=["%comment_assign_team%","%comment_assign_unassigned%","%comment_assigned_to%","%comment_assigning_to%","%comment_dateAtTime%","%comment_date_today%","%comment_date_yesterday%","%comment_deleteComment%","%comment_editComment%","%comment_replyOrAssign%","%comment_reopenResolved%","%comment_status_resolved%","%comment_status_todo%","%comment_thread_multiple_replies%","%comment_thread_single_reply%","%comment_aria_assign_user%","%comment_aria_submit_comment%","%comment_aria_mark_as_read%","%comment_aria_mark_as_unread%","%comment_aria_resolve_thread%"],xc=["input","select","textarea","button"],yc=["button","textbox"],gn=({options:t,onFocusChange:e,onOptionSelect:a,onCharacterPress:o})=>{const n=l.useRef(null),[i,s]=l.useState(void 0),[c,w]=l.useState(void 0),d=l.useCallback(p=>{s(p);const f=t.find(b=>b.id===p);f&&(e==null||e(f));const y=document.getElementById(p);y&&(y.scrollIntoView({block:"center"}),y.focus()),n.current&&n.current.setAttribute("aria-activedescendant",p)},[e,t]),u=l.useCallback(p=>{const f=t.find(y=>y.id===p);f&&(w(y=>y===p?void 0:p),a==null||a(f))},[a,t]),g=p=>{if(!p)return!1;const f=p.tagName.toLowerCase();if(p.isContentEditable||xc.includes(f))return!0;const y=p.getAttribute("role");if(y&&yc.includes(y))return!0;const b=p.getAttribute("tabindex");return b!==void 0&&b!=="-1"},m=l.useCallback(p=>{var z;const f=p.target,y=j=>j?document.getElementById(j):void 0,b=y(c),I=y(i);if(!!(b&&f&&b.contains(f)&&f!==b)&&g(f)){if(p.key==="Escape"||p.key==="ArrowLeft"&&!f.isContentEditable){if(c){p.preventDefault(),p.stopPropagation();const j=t.find(x=>x.id===c);j&&d(j.id)}return}if(p.key==="ArrowDown"||p.key==="ArrowUp"){if(!b)return;const j=Array.from(b.querySelectorAll('button:not([disabled]), input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'));if(j.length===0)return;const x=j.findIndex(P=>P===f);if(x===-1)return;let R;p.key==="ArrowDown"?R=Math.min(x+1,j.length-1):R=Math.max(x-1,0),R!==x&&(p.preventDefault(),p.stopPropagation(),(z=j[R])==null||z.focus());return}return}const S=t.findIndex(j=>j.id===i);let N=S;switch(p.key){case"ArrowDown":N=Math.min(S+1,t.length-1),p.preventDefault();break;case"ArrowUp":N=Math.max(S-1,0),p.preventDefault();break;case"Home":N=0,p.preventDefault();break;case"End":N=t.length-1,p.preventDefault();break;case" ":case"Enter":i&&u(i),p.preventDefault(),p.stopPropagation();return;case"ArrowRight":{const j=I;if(j){const x=j.querySelector('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])'),R=j.querySelector('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'),P=x??R;if(P){p.preventDefault(),P.focus();return}}break}default:p.key.length===1&&!p.metaKey&&!p.ctrlKey&&!p.altKey&&(g(f)||(o==null||o(p.key),p.preventDefault()));return}const E=t[N];E&&d(E.id)},[t,d,i,c,u,o]);return{listboxRef:n,activeId:i,selectedId:c,handleKeyDown:m,focusOption:d}},hn=ge.cva("tw:group/badge tw:inline-flex tw:h-5 tw:w-fit tw:shrink-0 tw:items-center tw:justify-center tw:gap-1 tw:overflow-hidden tw:rounded-4xl tw:border tw:border-transparent tw:px-2 tw:py-0.5 tw:text-xs tw:font-medium tw:whitespace-nowrap tw:transition-all tw:focus-visible:border-ring tw:focus-visible:ring-[3px] tw:focus-visible:ring-ring/50 tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:aria-invalid:border-destructive tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:ring-destructive/40 tw:[&>svg]:pointer-events-none tw:[&>svg]:size-3!",{variants:{variant:{default:"tw:bg-primary tw:text-primary-foreground tw:[a]:hover:bg-primary/80",secondary:"tw:bg-secondary tw:text-secondary-foreground tw:[a]:hover:bg-secondary/80",destructive:"tw:bg-destructive/10 tw:text-destructive tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:focus-visible:ring-destructive/40 tw:[a]:hover:bg-destructive/20",outline:"tw:border-border tw:text-foreground tw:[a]:hover:bg-muted tw:[a]:hover:text-muted-foreground",ghost:"tw:hover:bg-muted tw:hover:text-muted-foreground tw:dark:hover:bg-muted/50",link:"tw:text-primary tw:underline-offset-4 tw:hover:underline",muted:"tw:border-transparent tw:bg-muted tw:text-muted-foreground tw:hover:bg-muted/80",blueIndicator:"tw:w-[5px] tw:h-[5px] tw:bg-blue-400 tw:px-0",mutedIndicator:"tw:w-[5px] tw:h-[5px] tw:bg-zinc-400 tw:px-0"}},defaultVariants:{variant:"default"}});function pe({className:t,variant:e="default",asChild:a=!1,...o}){const n=a?k.Slot.Root:"span";return r.jsx(n,{"data-slot":"badge","data-variant":e,className:h("pr-twp",hn({variant:e}),t),...o})}function fn({className:t,size:e="default",...a}){return r.jsx("div",{"data-slot":"card","data-size":e,className:h("pr-twp tw:group/card tw:flex tw:flex-col tw:gap-4 tw:overflow-hidden tw:rounded-xl tw:bg-card tw:py-4 tw:text-sm tw:text-card-foreground tw:ring-1 tw:ring-foreground/10 tw:has-data-[slot=card-footer]:pb-0 tw:has-[>img:first-child]:pt-0 tw:data-[size=sm]:gap-3 tw:data-[size=sm]:py-3 tw:data-[size=sm]:has-data-[slot=card-footer]:pb-0 tw:*:[img:first-child]:rounded-t-xl tw:*:[img:last-child]:rounded-b-xl",t),...a})}function kc({className:t,...e}){return r.jsx("div",{"data-slot":"card-header",className:h("pr-twp tw:group/card-header tw:@container/card-header tw:grid tw:auto-rows-min tw:items-start tw:gap-1 tw:rounded-t-xl tw:px-4 tw:group-data-[size=sm]/card:px-3 tw:has-data-[slot=card-action]:grid-cols-[1fr_auto] tw:has-data-[slot=card-description]:grid-rows-[auto_auto] tw:[.border-b]:pb-4 tw:group-data-[size=sm]/card:[.border-b]:pb-3",t),...e})}function jc({className:t,...e}){return r.jsx("div",{"data-slot":"card-title",className:h("pr-twp tw:font-heading tw:text-base tw:leading-snug tw:font-medium tw:group-data-[size=sm]/card:text-sm",t),...e})}function _c({className:t,...e}){return r.jsx("div",{"data-slot":"card-description",className:h("pr-twp tw:text-sm tw:text-muted-foreground",t),...e})}function mn({className:t,...e}){return r.jsx("div",{"data-slot":"card-content",className:h("pr-twp tw:px-4 tw:group-data-[size=sm]/card:px-3",t),...e})}function Nc({className:t,...e}){return r.jsx("div",{"data-slot":"card-footer",className:h("pr-twp tw:flex tw:items-center tw:rounded-b-xl tw:border-t tw:bg-muted/50 tw:p-4 tw:group-data-[size=sm]/card:p-3",t),...e})}function vn({className:t,size:e="default",...a}){return r.jsx(k.Avatar.Root,{"data-slot":"avatar","data-size":e,className:h("pr-twp tw:group/avatar tw:relative tw:flex tw:size-8 tw:shrink-0 tw:rounded-full tw:select-none tw:after:absolute tw:after:inset-0 tw:after:rounded-full tw:after:border tw:after:border-border tw:after:mix-blend-darken tw:data-[size=lg]:size-10 tw:data-[size=sm]:size-6 tw:dark:after:mix-blend-lighten",t),...a})}function Cc({className:t,...e}){return r.jsx(k.Avatar.Image,{"data-slot":"avatar-image",className:h("pr-twp tw:aspect-square tw:size-full tw:rounded-full tw:object-cover",t),...e})}function bn({className:t,...e}){return r.jsx(k.Avatar.Fallback,{"data-slot":"avatar-fallback",className:h("pr-twp tw:flex tw:size-full tw:items-center tw:justify-center tw:rounded-full tw:bg-muted tw:text-sm tw:text-muted-foreground tw:group-data-[size=sm]/avatar:text-xs",t),...e})}const Pa=l.createContext(void 0);function ke(){const t=l.useContext(Pa);if(!t)throw new Error("useMenuContext must be used within a MenuContext.Provider.");return t}const Ge=ge.cva("",{variants:{variant:{default:"",muted:"tw:hover:bg-muted tw:hover:text-foreground tw:focus:bg-muted tw:focus:text-foreground tw:data-[state=open]:bg-muted tw:data-[state=open]:text-foreground"}},defaultVariants:{variant:"default"}});function ae({variant:t="default",...e}){const a=ft(),o=l.useMemo(()=>({variant:t}),[t]);return r.jsx(Pa.Provider,{value:o,children:r.jsx(k.DropdownMenu.Root,{"data-slot":"dropdown-menu",dir:a,...e})})}function xn({...t}){return r.jsx(k.DropdownMenu.Portal,{"data-slot":"dropdown-menu-portal",...t})}function oe({...t}){return r.jsx(k.DropdownMenu.Trigger,{"data-slot":"dropdown-menu-trigger",...t})}function ne({className:t,align:e="start",sideOffset:a=4,children:o,...n}){const i=ft();return r.jsx(k.DropdownMenu.Portal,{children:r.jsx(k.DropdownMenu.Content,{"data-slot":"dropdown-menu-content",sideOffset:a,align:e,className:h("pr-twp tw:z-50 tw:max-h-(--radix-dropdown-menu-content-available-height) tw:min-w-32 tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-[state=closed]:overflow-hidden tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...n,children:r.jsx("div",{dir:i,children:o})})})}function La({...t}){return r.jsx(k.DropdownMenu.Group,{"data-slot":"dropdown-menu-group",...t})}function Se({className:t,inset:e,variant:a="default",...o}){const n=ft(),i=ke();return r.jsx(k.DropdownMenu.Item,{"data-slot":"dropdown-menu-item","data-inset":e,"data-variant":a,className:h("tw:group/dropdown-menu-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:dark:data-[variant=destructive]:focus:bg-destructive/20 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:data-[variant=destructive]:*:[svg]:text-destructive",t,Ge({variant:i.variant})),dir:n,...o})}function ee({className:t,children:e,checked:a,inset:o,...n}){const i=ft(),s=ke();return r.jsxs(k.DropdownMenu.CheckboxItem,{"data-slot":"dropdown-menu-checkbox-item","data-inset":o,className:h("tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:s.variant})),checked:a,dir:i,...n,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:items-center tw:justify-center","data-slot":"dropdown-menu-checkbox-item-indicator",children:r.jsx(k.DropdownMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function yn({...t}){return r.jsx(k.DropdownMenu.RadioGroup,{"data-slot":"dropdown-menu-radio-group",...t})}function kn({className:t,children:e,inset:a,...o}){const n=ft(),i=ke();return r.jsxs(k.DropdownMenu.RadioItem,{"data-slot":"dropdown-menu-radio-item","data-inset":a,className:h("tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:i.variant})),dir:n,...o,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:items-center tw:justify-center","data-slot":"dropdown-menu-radio-item-indicator",children:r.jsx(k.DropdownMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function Ee({className:t,inset:e,...a}){return r.jsx(k.DropdownMenu.Label,{"data-slot":"dropdown-menu-label","data-inset":e,className:h("tw:px-1.5 tw:py-1 tw:text-xs tw:font-medium tw:text-muted-foreground tw:data-inset:ps-7",t),...a})}function ye({className:t,...e}){return r.jsx(k.DropdownMenu.Separator,{"data-slot":"dropdown-menu-separator",className:h("tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function Sc({className:t,...e}){return r.jsx("span",{"data-slot":"dropdown-menu-shortcut",className:h("tw:ms-auto tw:text-xs tw:tracking-widest tw:text-muted-foreground tw:group-focus/dropdown-menu-item:text-accent-foreground",t),...e})}function jn({...t}){return r.jsx(k.DropdownMenu.Sub,{"data-slot":"dropdown-menu-sub",...t})}function _n({className:t,inset:e,children:a,...o}){const n=ke();return r.jsxs(k.DropdownMenu.SubTrigger,{"data-slot":"dropdown-menu-sub-trigger","data-inset":e,className:h("tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-open:bg-accent tw:data-open:text-accent-foreground tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:n.variant})),...o,children:[a,r.jsx(ht.IconChevronRight,{className:"tw:ms-auto"})]})}function Nn({className:t,children:e,...a}){const o=ft();return r.jsx(k.DropdownMenu.SubContent,{"data-slot":"dropdown-menu-sub-content",className:h("pr-twp tw:z-50 tw:min-w-[96px] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-hidden tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-lg tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...a,children:r.jsx("div",{dir:o,children:e})})}function go({comment:t,isReply:e=!1,localizedStrings:a,isThreadExpanded:o=!1,handleUpdateComment:n,handleDeleteComment:i,onEditingChange:s,canEditOrDelete:c=!1}){const[w,d]=l.useState(!1),[u,g]=l.useState(),m=l.useRef(null);l.useEffect(()=>{if(!w)return;let S=!0;const N=m.current;if(!N)return;const E=setTimeout(()=>{S&&wn(N)},300);return()=>{S=!1,clearTimeout(E)}},[w]);const p=l.useCallback(S=>{S&&S.stopPropagation(),d(!1),g(void 0),s==null||s(!1)},[s]),f=l.useCallback(async S=>{if(S&&S.stopPropagation(),!u||!n)return;await n(t.id,Ar(u))&&(d(!1),g(void 0),s==null||s(!1))},[u,n,t.id,s]),y=l.useMemo(()=>{const S=new Date(t.date),N=D.formatRelativeDate(S,a["%comment_date_today%"],a["%comment_date_yesterday%"]),E=S.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"});return D.formatReplacementString(a["%comment_dateAtTime%"],{date:N,time:E})},[t.date,a]),b=l.useMemo(()=>t.user,[t.user]),I=l.useMemo(()=>t.user.split(" ").map(S=>S[0]).join("").toUpperCase().slice(0,2),[t.user]),C=l.useMemo(()=>D.sanitizeHtml(t.contents),[t.contents]),T=l.useMemo(()=>{if(o&&c)return r.jsxs(r.Fragment,{children:[r.jsxs(Se,{onClick:S=>{S.stopPropagation(),d(!0),g(gc(t.contents)),s==null||s(!0)},children:[r.jsx($.Pencil,{className:"tw:me-2 tw:h-4 tw:w-4"}),a["%comment_editComment%"]]}),r.jsxs(Se,{onClick:async S=>{S.stopPropagation(),i&&await i(t.id)},children:[r.jsx($.Trash2,{className:"tw:me-2 tw:h-4 tw:w-4"}),a["%comment_deleteComment%"]]})]})},[c,o,a,t.contents,t.id,i,s]);return r.jsxs("div",{className:h("tw:flex tw:w-full tw:flex-row tw:items-baseline tw:gap-3 tw:space-y-3",{"tw:text-sm":e}),children:[r.jsx(vn,{className:"tw:h-8 tw:w-8",children:r.jsx(bn,{className:"tw:text-xs tw:font-medium",children:I})}),r.jsxs("div",{className:"tw:flex tw:flex-1 tw:flex-col tw:gap-2",children:[r.jsxs("div",{className:"tw:flex tw:w-full tw:flex-row tw:flex-wrap tw:items-baseline tw:gap-x-2",children:[r.jsx("p",{className:"tw:text-sm tw:font-medium",children:b}),r.jsx("p",{className:"tw:text-xs tw:font-normal tw:text-muted-foreground",children:y}),r.jsx("div",{className:"tw:flex-1"}),e&&t.assignedUser!==void 0&&r.jsxs(pe,{variant:"secondary",className:"tw:text-xs tw:font-normal",children:["→ ",Sr(t.assignedUser,a)]})]}),w&&r.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw:flex tw:flex-col tw:gap-2",ref:m,onKeyDownCapture:S=>{S.key==="Escape"?(S.preventDefault(),S.stopPropagation(),p()):Aa(S)&&(S.preventDefault(),S.stopPropagation(),Zt(u)&&f())},onKeyDown:S=>{Ia(S),(S.key==="Enter"||S.key===" ")&&S.stopPropagation()},onClick:S=>{S.stopPropagation()},children:[r.jsx($r,{className:h('tw:[&_[data-lexical-editor="true"]>blockquote]:mt-0 tw:[&_[data-lexical-editor="true"]>blockquote]:border-s-0 tw:[&_[data-lexical-editor="true"]>blockquote]:ps-0 tw:[&_[data-lexical-editor="true"]>blockquote]:font-normal tw:[&_[data-lexical-editor="true"]>blockquote]:not-italic tw:[&_[data-lexical-editor="true"]>blockquote]:text-foreground'),editorSerializedState:u,onSerializedChange:S=>g(S)}),r.jsxs("div",{className:"tw:flex tw:flex-row tw:items-start tw:justify-end tw:gap-2",children:[r.jsx(F,{size:"icon",onClick:p,variant:"outline",className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",children:r.jsx($.X,{})}),r.jsx(F,{size:"icon",onClick:f,className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!Zt(u),children:r.jsx($.ArrowUp,{})})]})]}),!w&&r.jsxs(r.Fragment,{children:[t.status==="Resolved"&&r.jsx("div",{className:"tw:text-sm tw:italic",children:a["%comment_status_resolved%"]}),t.status==="Todo"&&e&&r.jsx("div",{className:"tw:text-sm tw:italic",children:a["%comment_status_todo%"]}),r.jsx("div",{className:h("tw:prose tw:items-start tw:gap-2 tw:break-words tw:text-sm tw:font-normal tw:text-foreground","tw:max-w-none","tw:[&>blockquote]:border-s-0 tw:[&>blockquote]:p-0 tw:[&>blockquote]:ps-0 tw:[&>blockquote]:font-normal tw:[&>blockquote]:not-italic tw:[&>blockquote]:text-foreground","tw:prose-quoteless",{"tw:line-clamp-3":!o}),dangerouslySetInnerHTML:{__html:C}})]})]}),T&&r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsx(F,{variant:"ghost",size:"icon",children:r.jsx($.MoreHorizontal,{})})}),r.jsx(ne,{align:"end",children:T})]})]})}const ho={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function Ec({classNameForVerseText:t,comments:e,localizedStrings:a,isSelected:o=!1,verseRef:n,assignedUser:i,currentUser:s,handleSelectThread:c,threadId:w,thread:d,threadStatus:u,handleAddCommentToThread:g,handleUpdateComment:m,handleDeleteComment:p,handleReadStatusChange:f,assignableUsers:y,canUserAddCommentToThread:b,canUserAssignThreadCallback:I,canUserResolveThreadCallback:C,canUserEditOrDeleteCommentCallback:T,isRead:S=!1,autoReadDelay:N=5,onVerseRefClick:E,initialAssignedUser:z}){const[j,x]=l.useState(ho),[R,P]=l.useState(),[G,q]=l.useState(),V=o,[K,M]=l.useState(!1),[Y,lt]=l.useState(!1),[kt,St]=l.useState(!1),[J,Et]=l.useState(!1),[U,tt]=l.useState(!1),[rt,at]=l.useState(S),[ot,Lt]=l.useState(!1),gt=l.useRef(void 0),[Ft,Gt]=l.useState(new Map);l.useEffect(()=>{let O=!0;return(async()=>{const dt=C?await C(w):!1;O&&tt(dt)})(),()=>{O=!1}},[w,C]),l.useEffect(()=>{let O=!0;if(!o){Et(!1),Gt(new Map);return}return(async()=>{const dt=I?await I(w):!1;O&&Et(dt)})(),()=>{O=!1}},[o,w,I]);const A=l.useRef("idle");l.useEffect(()=>{if(!o){A.current!=="idle"&&(P(void 0),q(void 0),A.current="idle");return}A.current==="idle"&&(A.current="pending"),J?A.current==="pending"&&z!==void 0&&z!==i&&(P(z),A.current="auto-populated"):A.current==="auto-populated"&&(P(void 0),A.current="pending")},[o,z,J,i]);const Tt=l.useMemo(()=>e.filter(O=>!O.deleted),[e]);l.useEffect(()=>{let O=!0;if(!o||!T){Gt(new Map);return}return(async()=>{const dt=new Map;await Promise.all(Tt.map(async bt=>{const Re=await T(bt.id);O&&dt.set(bt.id,Re)})),O&&Gt(dt)})(),()=>{O=!1}},[o,Tt,T]);const Bt=l.useMemo(()=>Tt[0],[Tt]),Qt=l.useRef(null),Ut=l.useRef(void 0),Rt=l.useCallback(()=>{var O;(O=Ut.current)==null||O.call(Ut),x(ho)},[]),je=l.useCallback(()=>{const O=!rt;at(O),Lt(!O),f==null||f(w,O)},[rt,f,w]);l.useEffect(()=>{M(!1)},[o]),l.useEffect(()=>{if(o&&!rt&&!ot){const O=setTimeout(()=>{at(!0),f==null||f(w,!0)},N*1e3);return gt.current=O,()=>clearTimeout(O)}gt.current&&(clearTimeout(gt.current),gt.current=void 0)},[o,rt,ot,N,w,f]);const qt=l.useMemo(()=>({singleReply:a["%comment_thread_single_reply%"],multipleReplies:a["%comment_thread_multiple_replies%"]}),[a]),Kt=l.useMemo(()=>{if(i===void 0)return;if(i==="")return a["%comment_assign_unassigned%"]??"Unassigned";const O=Sr(i,a);return D.formatReplacementString(a["%comment_assigned_to%"],{assignedUser:O})},[i,a]),B=l.useMemo(()=>Tt.slice(1),[Tt]),W=l.useMemo(()=>B.length??0,[B.length]),nt=l.useMemo(()=>W>0,[W]),et=l.useMemo(()=>K||W<=2?B:B.slice(-2),[B,W,K]),wt=l.useMemo(()=>K||W<=2?0:W-2,[W,K]),mt=l.useMemo(()=>W===1?qt.singleReply:D.formatReplacementString(qt.multipleReplies,{count:W}),[W,qt]),vt=l.useMemo(()=>wt===1?qt.singleReply:D.formatReplacementString(qt.multipleReplies,{count:wt}),[wt,qt]);l.useEffect(()=>{!o&&Y&&nt&<(!1)},[o,Y,nt]);const ut=l.useCallback(async O=>{O&&O.stopPropagation();const ct=Zt(j)?Ar(j):void 0;if(R!==void 0){await g({threadId:w,contents:ct,assignedUser:R})&&(q(R),ct&&Rt());return}ct&&await g({threadId:w,contents:ct})&&Rt()},[Rt,j,g,R,w]),jt=l.useCallback(async O=>{const ct=Zt(j)?Ar(j):void 0,dt=O.status?O.assignedUser:R??O.assignedUser,bt=await g({...O,contents:ct,assignedUser:dt});return bt&&(dt!==void 0&&q(dt),ct&&Rt()),bt},[Rt,j,g,R]);if(Tt.length!==0)return r.jsx(fn,{role:"option","aria-selected":o,id:w,className:h("tw:group tw:w-full tw:rounded-none tw:border-none tw:p-4 tw:outline-hidden tw:transition-all tw:duration-200 tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",{"tw:cursor-pointer tw:hover:shadow-md":!o},{"tw:bg-primary-foreground":!o&&u!=="Resolved"&&rt,"tw:bg-background":o&&u!=="Resolved"&&rt,"tw:bg-muted":u==="Resolved","tw:bg-accent":!rt&&!o&&u!=="Resolved"}),onClick:()=>{c(w)},tabIndex:-1,children:r.jsxs(mn,{className:"tw:flex tw:flex-col tw:gap-2 tw:p-0",children:[r.jsxs("div",{className:"tw:flex tw:flex-col tw:content-center tw:items-start tw:gap-4",children:[r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-2",children:[Kt&&r.jsx(pe,{className:"tw:rounded-sm tw:bg-input tw:text-sm tw:font-normal tw:text-primary tw:hover:bg-input",children:Kt}),r.jsx(F,{variant:"ghost",size:"icon",onClick:O=>{O.stopPropagation(),je()},className:"tw:text-muted-foreground tw:transition tw:hover:text-foreground","aria-label":rt?a["%comment_aria_mark_as_unread%"]??"Mark as unread":a["%comment_aria_mark_as_read%"]??"Mark as read",children:rt?r.jsx($.MailOpen,{}):r.jsx($.Mail,{})}),U&&u!=="Resolved"&&r.jsx(F,{variant:"ghost",size:"icon",className:h("tw:ms-auto","tw:text-primary tw:transition-opacity tw:duration-200 tw:hover:bg-primary/10","tw:opacity-0 tw:group-hover:opacity-100"),onClick:O=>{O.stopPropagation(),jt({threadId:w,status:"Resolved"})},"aria-label":a["%comment_aria_resolve_thread%"]??"Resolve thread",children:r.jsx($.Check,{className:"tw:h-4 tw:w-4"})})]}),r.jsx("div",{className:"tw:flex tw:max-w-full tw:flex-wrap tw:items-baseline tw:gap-2",children:r.jsxs("p",{ref:Qt,className:h("tw:flex-1 tw:overflow-hidden tw:text-ellipsis tw:text-sm tw:font-normal tw:text-muted-foreground",{"tw:overflow-visible tw:text-clip tw:whitespace-normal tw:break-words":V},{"tw:whitespace-nowrap":!V}),children:[n&&E?r.jsx(F,{variant:"ghost",size:"sm",className:"tw:h-auto tw:px-1 tw:py-0 tw:text-sm tw:font-normal tw:text-muted-foreground",onClick:O=>{O.stopPropagation(),E(d)},children:n}):n,r.jsxs("span",{className:t,children:[Bt.contextBefore,r.jsx("span",{className:"tw:font-bold",children:Bt.selectedText}),Bt.contextAfter]})]})}),r.jsx(go,{comment:Bt,localizedStrings:a,isThreadExpanded:o,threadStatus:u,handleAddCommentToThread:jt,handleUpdateComment:m,handleDeleteComment:p,onEditingChange:lt,canEditOrDelete:(!Y&&Ft.get(Bt.id))??!1,canUserResolveThread:U})]}),r.jsxs(r.Fragment,{children:[nt&&!o&&r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-5",children:[r.jsx("div",{className:"tw:w-8",children:r.jsx($e,{})}),r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:mt})]}),!o&&Zt(j)&&r.jsx($r,{editorSerializedState:j,onSerializedChange:O=>x(O),placeholder:a["%comment_replyOrAssign%"]}),o&&r.jsxs(r.Fragment,{children:[wt>0&&r.jsxs("div",{className:"tw:flex tw:cursor-pointer tw:items-center tw:gap-5 tw:py-2",onClick:O=>{O.stopPropagation(),M(!0)},role:"button",tabIndex:0,onKeyDown:O=>{(O.key==="Enter"||O.key===" ")&&(O.preventDefault(),O.stopPropagation(),M(!0))},children:[r.jsx("div",{className:"tw:w-8",children:r.jsx($e,{})}),r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-2",children:[r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:vt}),K?r.jsx($.ChevronUp,{}):r.jsx($.ChevronDown,{})]})]}),et.map(O=>r.jsx("div",{children:r.jsx(go,{comment:O,localizedStrings:a,isReply:!0,isThreadExpanded:o,handleUpdateComment:m,handleDeleteComment:p,onEditingChange:lt,canEditOrDelete:(!Y&&Ft.get(O.id))??!1})},O.id)),b!==!1&&(!Y||Zt(j))&&r.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw:w-full tw:space-y-2",onClick:O=>O.stopPropagation(),onKeyDownCapture:O=>{Aa(O)&&(O.preventDefault(),O.stopPropagation(),(Zt(j)||R!==void 0&&R!==G)&&ut())},onKeyDown:O=>{Ia(O),(O.key==="Enter"||O.key===" ")&&O.stopPropagation()},children:[r.jsx($r,{editorSerializedState:j,onSerializedChange:O=>x(O),placeholder:u==="Resolved"?a["%comment_reopenResolved%"]:a["%comment_replyOrAssign%"],autoFocus:!0,onClear:O=>{Ut.current=O}}),r.jsxs("div",{className:"tw:flex tw:flex-row tw:items-center tw:justify-end tw:gap-2",children:[R!==void 0&&(Zt(j)||R!==G)&&r.jsx("span",{className:"tw:flex-1 tw:text-sm tw:text-muted-foreground",children:D.formatReplacementString(a["%comment_assigning_to%"]??"Assigning to: {assignedUser}",{assignedUser:Sr(R,a)})}),r.jsxs(se,{open:kt,onOpenChange:St,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{size:"icon",variant:"outline",className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!J||!y||y.length===0||!y.includes(s),"aria-label":a["%comment_aria_assign_user%"]??"Assign user",children:r.jsx($.AtSign,{})})}),r.jsx(ce,{className:"tw:w-auto tw:p-0",align:"end",onKeyDown:O=>{O.key==="Escape"&&(O.stopPropagation(),St(!1))},children:r.jsx(he,{children:r.jsx(fe,{children:y==null?void 0:y.map(O=>r.jsx(ie,{onSelect:()=>{P(O!==i?O:void 0),A.current="user-selected",q(void 0),St(!1)},className:"tw:flex tw:items-center",children:r.jsx("span",{children:Sr(O,a)})},O||"unassigned"))})})})]}),r.jsx(F,{size:"icon",onClick:ut,className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!Zt(j)&&(R===void 0||R===G),"aria-label":a["%comment_aria_submit_comment%"]??"Submit comment",children:r.jsx($.ArrowUp,{})})]})]})]})]})]})})}function Tc({className:t="",classNameForVerseText:e,threads:a,currentUser:o,localizedStrings:n,handleAddCommentToThread:i,handleUpdateComment:s,handleDeleteComment:c,handleReadStatusChange:w,assignableUsers:d,canUserAddCommentToThread:u,canUserAssignThreadCallback:g,canUserResolveThreadCallback:m,canUserEditOrDeleteCommentCallback:p,selectedThreadId:f,onSelectedThreadChange:y,onVerseRefClick:b}){const[I,C]=l.useState(new Set),[T,S]=l.useState(),[N,E]=l.useState(),z=l.useCallback(async M=>{const Y=await i(M);return Y!==void 0&&M.assignedUser!==void 0&&M.assignedUser!==""&&E(M.assignedUser),Y},[i]);l.useEffect(()=>{f&&(C(M=>new Set(M).add(f)),S(f))},[f]);const j=a.filter(M=>M.comments.some(Y=>!Y.deleted)),x=j.map(M=>({id:M.id})),R=l.useCallback(M=>{C(Y=>new Set(Y).add(M.id)),S(M.id),y==null||y(M.id)},[y]),P=l.useCallback(M=>{const Y=I.has(M);C(lt=>{const kt=new Set(lt);return kt.has(M)?kt.delete(M):kt.add(M),kt}),S(M),y==null||y(Y?void 0:M)},[I,y]),{listboxRef:G,activeId:q,handleKeyDown:V}=gn({options:x,onOptionSelect:R}),K=l.useCallback(M=>{M.key==="Escape"?(T&&I.has(T)&&(C(Y=>{const lt=new Set(Y);return lt.delete(T),lt}),S(void 0),y==null||y(void 0)),M.preventDefault(),M.stopPropagation()):V(M)},[T,I,V,y]);return r.jsx("div",{id:"comment-list",role:"listbox",tabIndex:0,ref:G,"aria-activedescendant":q??void 0,"aria-label":"Comments",className:h("tw:flex tw:w-full tw:flex-col tw:space-y-3 tw:outline-hidden tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",t),onKeyDown:K,children:j.map(M=>r.jsx("div",{className:h({"tw:opacity-60":M.status==="Resolved"}),children:r.jsx(Ec,{classNameForVerseText:e,comments:M.comments,localizedStrings:n,verseRef:M.verseRef,handleSelectThread:P,threadId:M.id,thread:M,isRead:M.isRead,isSelected:I.has(M.id),currentUser:o,assignedUser:M.assignedUser,threadStatus:M.status,handleAddCommentToThread:z,handleUpdateComment:s,handleDeleteComment:c,handleReadStatusChange:w,assignableUsers:d,canUserAddCommentToThread:u,canUserAssignThreadCallback:g,canUserResolveThreadCallback:m,canUserEditOrDeleteCommentCallback:p,onVerseRefClick:b,initialAssignedUser:N})},M.id))})}function Rc({table:t}){return r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsxs(F,{variant:"outline",size:"sm",className:"tw:ml-auto tw:hidden tw:h-8 tw:lg:flex",children:[r.jsx($.FilterIcon,{className:"tw:mr-2 tw:h-4 tw:w-4"}),"View"]})}),r.jsxs(ne,{align:"end",className:"tw:w-[150px]",children:[r.jsx(Ee,{children:"Toggle columns"}),r.jsx(ye,{}),t.getAllColumns().filter(e=>e.getCanHide()).map(e=>r.jsx(ee,{className:"tw:capitalize",checked:e.getIsVisible(),onCheckedChange:a=>e.toggleVisibility(!!a),children:e.id},e.id))]})]})}function Ae({...t}){return r.jsx(k.Select.Root,{"data-slot":"select",...t})}function Cn({className:t,...e}){return r.jsx(k.Select.Group,{"data-slot":"select-group",className:h("tw:scroll-my-1 tw:p-1",t),...e})}function Pe({...t}){return r.jsx(k.Select.Value,{"data-slot":"select-value",...t})}function Le({className:t,size:e="default",children:a,...o}){const n=ft();return r.jsxs(k.Select.Trigger,{"data-slot":"select-trigger","data-size":e,className:h("pr-twp tw:flex tw:w-fit tw:items-center tw:gap-2 tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:py-2 tw:pe-2 tw:ps-2.5 tw:text-sm tw:whitespace-nowrap tw:transition-colors tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:data-placeholder:text-muted-foreground tw:data-[size=default]:h-8 tw:data-[size=sm]:h-7 tw:data-[size=sm]:rounded-[min(var(--tw-radius-md),10px)] tw:*:data-[slot=select-value]:line-clamp-1 tw:*:data-[slot=select-value]:flex tw:*:data-[slot=select-value]:flex-1 tw:*:data-[slot=select-value]:items-center tw:*:data-[slot=select-value]:gap-1.5 tw:*:data-[slot=select-value]:text-start tw:dark:bg-input/30 tw:dark:hover:bg-input/50 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t),dir:n,...o,children:[a,r.jsx(k.Select.Icon,{asChild:!0,children:r.jsx(ht.IconSelector,{className:"tw:pointer-events-none tw:size-4 tw:text-muted-foreground"})})]})}function Be({className:t,children:e,position:a="popper",align:o="center",...n}){const i=ft();return r.jsx(k.Select.Portal,{children:r.jsxs(k.Select.Content,{"data-slot":"select-content","data-align-trigger":a==="item-aligned",className:h("pr-twp tw:relative tw:z-50 tw:max-h-(--radix-select-content-available-height) tw:data-[align-trigger=true]:min-w-(--radix-select-trigger-width) tw:data-[align-trigger=false]:min-w-36 tw:origin-(--radix-select-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-lg tw:bg-popover tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[align-trigger=true]:animate-none tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",a==="popper"&&"tw:data-[side=bottom]:translate-y-1 tw:data-[side=left]:-translate-x-1 tw:rtl:data-[side=left]:translate-x-1 tw:data-[side=right]:translate-x-1 tw:rtl:data-[side=right]:-translate-x-1 tw:data-[side=top]:-translate-y-1",t),position:a,align:o,...n,children:[r.jsx(Sn,{}),r.jsx(k.Select.Viewport,{"data-position":a,className:h("tw:data-[position=popper]:h-(--radix-select-trigger-height) tw:data-[position=popper]:w-full tw:data-[position=popper]:min-w-(--radix-select-trigger-width)",a==="popper"&&"tw:"),children:r.jsx("div",{dir:i,children:e})}),r.jsx(En,{})]})})}function zc({className:t,...e}){return r.jsx(k.Select.Label,{"data-slot":"select-label",className:h("pr-twp tw:px-1.5 tw:py-1 tw:text-xs tw:text-muted-foreground",t),...e})}function Jt({className:t,children:e,...a}){return r.jsxs(k.Select.Item,{"data-slot":"select-item",className:h("pr-twp tw:relative tw:flex tw:w-full tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:*:[span]:last:flex tw:*:[span]:last:items-center tw:*:[span]:last:gap-2",t),...a,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:size-4 tw:items-center tw:justify-center",children:r.jsx(k.Select.ItemIndicator,{children:r.jsx(ht.IconCheck,{className:"tw:pointer-events-none"})})}),r.jsx(k.Select.ItemText,{children:e})]})}function Dc({className:t,...e}){return r.jsx(k.Select.Separator,{"data-slot":"select-separator",className:h("pr-twp tw:pointer-events-none tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function Sn({className:t,...e}){return r.jsx(k.Select.ScrollUpButton,{"data-slot":"select-scroll-up-button",className:h("pr-twp tw:z-10 tw:flex tw:cursor-default tw:items-center tw:justify-center tw:bg-popover tw:py-1 tw:[&_svg:not([class*=size-])]:size-4",t),...e,children:r.jsx(ht.IconChevronUp,{})})}function En({className:t,...e}){return r.jsx(k.Select.ScrollDownButton,{"data-slot":"select-scroll-down-button",className:h("pr-twp tw:z-10 tw:flex tw:cursor-default tw:items-center tw:justify-center tw:bg-popover tw:py-1 tw:[&_svg:not([class*=size-])]:size-4",t),...e,children:r.jsx(ht.IconChevronDown,{})})}function Ic({table:t}){return r.jsx("div",{className:"tw:flex tw:items-center tw:justify-between tw:px-2 tw:pb-3 tw:pt-3",children:r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-6 tw:lg:space-x-8",children:[r.jsxs("div",{className:"tw:flex-1 tw:text-sm tw:text-muted-foreground",children:[t.getFilteredSelectedRowModel().rows.length," of"," ",t.getFilteredRowModel().rows.length," row(s) selected"]}),r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-2",children:[r.jsx("p",{className:"tw:text-nowrap tw:text-sm tw:font-medium",children:"Rows per page"}),r.jsxs(Ae,{value:`${t.getState().pagination.pageSize}`,onValueChange:e=>{t.setPageSize(Number(e))},children:[r.jsx(Le,{className:"tw:h-8 tw:w-[70px]",children:r.jsx(Pe,{placeholder:t.getState().pagination.pageSize})}),r.jsx(Be,{side:"top",children:[10,20,30,40,50].map(e=>r.jsx(Jt,{value:`${e}`,children:e},e))})]})]}),r.jsxs("div",{className:"tw:flex tw:w-[100px] tw:items-center tw:justify-center tw:text-sm tw:font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-2",children:[r.jsxs(F,{variant:"outline",size:"icon",className:"tw:hidden tw:h-8 tw:w-8 tw:p-0 tw:lg:flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to first page"}),r.jsx($.ArrowLeftIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:h-8 tw:w-8 tw:p-0",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to previous page"}),r.jsx($.ChevronLeftIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:h-8 tw:w-8 tw:p-0",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to next page"}),r.jsx($.ChevronRightIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:hidden tw:h-8 tw:w-8 tw:p-0 tw:lg:flex",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to last page"}),r.jsx($.ArrowRightIcon,{className:"tw:h-4 tw:w-4"})]})]})]})})}const fo=` +"use strict";var hi=Object.defineProperty;var fi=(t,e,a)=>e in t?hi(t,e,{enumerable:!0,configurable:!0,writable:!0,value:a}):t[e]=a;var Dt=(t,e,a)=>fi(t,typeof e!="symbol"?e+"":e,a);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),l=require("react"),Ve=require("cmdk"),mi=require("clsx"),_o=require("tailwind-merge"),k=require("radix-ui"),ge=require("class-variance-authority"),ht=require("@tabler/icons-react"),st=require("@sillsdev/scripture"),D=require("platform-bible-utils"),$=require("lucide-react"),v=require("lexical"),ca=require("@lexical/rich-text"),Xa=require("react-dom"),vi=require("@lexical/table"),No=require("@lexical/headless"),Mt=require("@tanstack/react-table"),bi=require("markdown-to-jsx"),Xt=require("@eten-tech-foundation/platform-editor"),xi=require("react-hotkeys-hook"),Te=require("vaul"),yi=require("react-resizable-panels"),ki=require("next-themes"),Co=require("sonner");function ji(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const a in t)if(a!=="default"){const o=Object.getOwnPropertyDescriptor(t,a);Object.defineProperty(e,a,o.get?o:{enumerable:!0,get:()=>t[a]})}}return e.default=t,Object.freeze(e)}const ba=ji(yi),_i=_o.extendTailwindMerge({prefix:"tw"});function la(t){const e=[];let a="",o=0;for(let n=0;ni.startsWith("-tw-"));if(a!==-1){const i=e[a].slice(4);return{normalized:`tw:${[...e.filter((w,d)=>d!==a),`-${i}`].join(":")}`,original:t}}const o=e.findIndex(i=>i.startsWith("!tw-"));if(o!==-1){const i=e[o].slice(4);return{normalized:`tw:${[...e.filter((w,d)=>d!==o),`!${i}`].join(":")}`,original:t}}const n=e[e.length-1];if(n.startsWith("tw-")){const i=n.slice(3);return{normalized:`tw:${[...e.slice(0,-1),i].join(":")}`,original:t}}return{normalized:t,original:t}}function Ci(t,e){if(e.startsWith("tw:"))return t;const a=la(t);if(a[0]!=="tw")return t;const o=a.slice(1,-1),n=a[a.length-1],i=la(e),s=i.some(w=>w.startsWith("-tw-")),c=i.some(w=>w.startsWith("!tw-"));if(s&&n.startsWith("-")){const w=n.slice(1);return[...o,`-tw-${w}`].join(":")}if(c&&n.startsWith("!")){const w=n.slice(1);return[...o,`!tw-${w}`].join(":")}return[...o,`tw-${n}`].join(":")}function h(...t){const e=mi.clsx(t);if(!e)return e;if(e.indexOf("tw-")===-1)return _i(e);const a=e.split(" ").filter(Boolean),o=new Map,n=[];return a.forEach(w=>{const d=Ni(w);o.set(d.normalized,d.original),n.push(d.normalized)}),_o.twMerge(n.join(" ")).split(" ").filter(Boolean).map(w=>{const d=o.get(w);return d?Ci(w,d):w}).join(" ")}const We=250,xa=300,Vr=400,So=450,Eo=500,To=550,ya=ge.cva("pr-twp tw:group/button tw:inline-flex tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-transparent tw:bg-clip-padding tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:active:not-aria-[haspopup]:translate-y-px tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",{variants:{variant:{default:"tw:bg-primary tw:text-primary-foreground tw:[a]:hover:bg-primary/80",outline:"tw:border-border tw:bg-background tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:border-input tw:dark:bg-input/30 tw:dark:hover:bg-input/50",secondary:"tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-secondary/80 tw:aria-expanded:bg-secondary tw:aria-expanded:text-secondary-foreground",ghost:"tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:hover:bg-muted/50",destructive:"tw:bg-destructive/10 tw:text-destructive tw:hover:bg-destructive/20 tw:focus-visible:border-destructive/40 tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:hover:bg-destructive/30 tw:dark:focus-visible:ring-destructive/40",link:"tw:text-primary tw:underline-offset-4 tw:hover:underline"},size:{default:"tw:h-8 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",xs:"tw:h-6 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),10px)] tw:px-2 tw:text-xs tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3",sm:"tw:h-7 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3.5",lg:"tw:h-9 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",icon:"tw:size-8","icon-xs":"tw:size-6 tw:rounded-[min(var(--tw-radius-md),10px)] tw:in-data-[slot=button-group]:rounded-lg tw:[&_svg:not([class*=size-])]:size-3","icon-sm":"tw:size-7 tw:rounded-[min(var(--tw-radius-md),12px)] tw:in-data-[slot=button-group]:rounded-lg","icon-lg":"tw:size-9"}},defaultVariants:{variant:"default",size:"default"}});function F({className:t,variant:e="default",size:a="default",asChild:o=!1,...n}){const i=o?k.Slot.Root:"button";return r.jsx(i,{"data-slot":"button","data-variant":e,"data-size":a,className:h(ya({variant:e,size:a,className:t})),...n})}const Si="layoutDirection";function ft(){const t=localStorage.getItem(Si);return t==="rtl"?t:"ltr"}function Er({...t}){return r.jsx(k.Dialog.Root,{"data-slot":"dialog",...t})}function Ei({...t}){return r.jsx(k.Dialog.Trigger,{"data-slot":"dialog-trigger",...t})}function Ro({...t}){return r.jsx(k.Dialog.Portal,{"data-slot":"dialog-portal",...t})}function Ti({...t}){return r.jsx(k.Dialog.Close,{"data-slot":"dialog-close",...t})}function zo({className:t,style:e,...a}){return r.jsx(k.Dialog.Overlay,{"data-slot":"dialog-overlay",className:h("tw:fixed tw:inset-0 tw:isolate tw:bg-black/10 tw:duration-100 tw:supports-backdrop-filter:backdrop-blur-xs tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-closed:animate-out tw:data-closed:fade-out-0",t),style:{zIndex:So,...e},...a})}function Tr({className:t,children:e,showCloseButton:a=!0,overlayClassName:o,style:n,...i}){const s=ft();return r.jsxs(Ro,{children:[r.jsx(zo,{className:o}),r.jsxs(k.Dialog.Content,{"data-slot":"dialog-content",className:h("pr-twp tw:fixed tw:top-1/2 tw:start-1/2 tw:grid tw:w-full tw:max-w-[calc(100%-2rem)] tw:-translate-x-1/2 tw:rtl:translate-x-1/2 tw:-translate-y-1/2 tw:gap-4 tw:rounded-xl tw:bg-popover tw:p-4 tw:text-sm tw:text-popover-foreground tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:outline-none tw:sm:max-w-sm tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),style:{zIndex:Eo,...n},dir:s,...i,children:[e,a&&r.jsx(k.Dialog.Close,{"data-slot":"dialog-close",asChild:!0,children:r.jsxs(F,{variant:"ghost",className:"tw:absolute tw:top-2 tw:end-2",size:"icon-sm",children:[r.jsx(ht.IconX,{}),r.jsx("span",{className:"tw:sr-only",children:"Close"})]})})]})]})}function Rr({className:t,...e}){return r.jsx("div",{"data-slot":"dialog-header",className:h("pr-twp tw:flex tw:flex-col tw:gap-2 tw:sm:text-start",t),...e})}function da({className:t,showCloseButton:e=!1,children:a,...o}){return r.jsxs("div",{"data-slot":"dialog-footer",className:h("pr-twp tw:-mx-4 tw:-mb-4 tw:flex tw:flex-col-reverse tw:gap-2 tw:rounded-b-xl tw:border-t tw:bg-muted/50 tw:p-4 tw:sm:flex-row tw:sm:justify-end",t),...o,children:[a,e&&r.jsx(k.Dialog.Close,{asChild:!0,children:r.jsx(F,{variant:"outline",children:"Close"})})]})}function zr({className:t,...e}){return r.jsx(k.Dialog.Title,{"data-slot":"dialog-title",className:h("pr-twp tw:font-heading tw:text-base tw:leading-none tw:font-medium",t),...e})}function Ri({className:t,...e}){return r.jsx(k.Dialog.Description,{"data-slot":"dialog-description",className:h("pr-twp tw:text-sm tw:text-muted-foreground tw:*:[a]:underline tw:*:[a]:underline-offset-3 tw:*:[a]:hover:text-foreground",t),...e})}function Xe({className:t,type:e,...a}){return r.jsx("input",{type:e,"data-slot":"input",className:h("pr-twp tw:h-8 tw:min-w-0 tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:px-2.5 tw:py-1 tw:text-base tw:transition-colors tw:outline-none tw:file:inline-flex tw:file:h-6 tw:file:border-0 tw:file:bg-transparent tw:file:text-sm tw:file:font-medium tw:file:text-foreground tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:pointer-events-none tw:disabled:cursor-not-allowed tw:disabled:bg-input/50 tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:md:text-sm tw:dark:bg-input/30 tw:dark:disabled:bg-input/80 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40",t),...a})}function zi({className:t,...e}){return r.jsx("textarea",{"data-slot":"textarea",className:h("pr-twp tw:flex tw:field-sizing-content tw:min-h-16 tw:w-full tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:px-2.5 tw:py-2 tw:text-base tw:transition-colors tw:outline-none tw:placeholder:text-muted-foreground tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:bg-input/50 tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:md:text-sm tw:dark:bg-input/30 tw:dark:disabled:bg-input/80 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40",t),...e})}function Di({className:t,...e}){return r.jsx("div",{"data-slot":"input-group",role:"group",className:h("pr-twp tw:group/input-group tw:relative tw:flex tw:h-8 tw:w-full tw:min-w-0 tw:items-center tw:rounded-lg tw:border tw:border-input tw:transition-colors tw:outline-none tw:in-data-[slot=combobox-content]:focus-within:border-inherit tw:in-data-[slot=combobox-content]:focus-within:ring-0 tw:has-disabled:bg-input/50 tw:has-disabled:opacity-50 tw:has-[[data-slot=input-group-control]:focus-visible]:border-ring tw:has-[[data-slot=input-group-control]:focus-visible]:ring-3 tw:has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 tw:has-[[data-slot][aria-invalid=true]]:border-destructive tw:has-[[data-slot][aria-invalid=true]]:ring-3 tw:has-[[data-slot][aria-invalid=true]]:ring-destructive/20 tw:has-[>[data-align=block-end]]:h-auto tw:has-[>[data-align=block-end]]:flex-col tw:has-[>[data-align=block-start]]:h-auto tw:has-[>[data-align=block-start]]:flex-col tw:has-[>textarea]:h-auto tw:dark:bg-input/30 tw:dark:has-disabled:bg-input/80 tw:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 tw:has-[>[data-align=block-end]]:[&>input]:pt-3 tw:has-[>[data-align=block-start]]:[&>input]:pb-3 tw:has-[>[data-align=inline-end]]:[&>input]:pe-1.5 tw:has-[>[data-align=inline-start]]:[&>input]:ps-1.5",t),...e})}const Ii=ge.cva("tw:flex tw:h-auto tw:cursor-text tw:items-center tw:justify-center tw:gap-2 tw:py-1.5 tw:text-sm tw:font-medium tw:text-muted-foreground tw:select-none tw:group-data-[disabled=true]/input-group:opacity-50 tw:[&>kbd]:rounded-[calc(var(--radius)-5px)] tw:[&>svg:not([class*=size-])]:size-4",{variants:{align:{"inline-start":"tw:order-first tw:ps-2 tw:has-[>button]:ms-[-0.3rem] tw:has-[>kbd]:ms-[-0.15rem]","inline-end":"tw:order-last tw:pe-2 tw:has-[>button]:me-[-0.3rem] tw:has-[>kbd]:me-[-0.15rem]","block-start":"tw:order-first tw:w-full tw:justify-start tw:px-2.5 tw:pt-2 tw:group-has-[>input]/input-group:pt-2 tw:[.border-b]:pb-2","block-end":"tw:order-last tw:w-full tw:justify-start tw:px-2.5 tw:pb-2 tw:group-has-[>input]/input-group:pb-2 tw:[.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}});function Mi({className:t,align:e="inline-start",...a}){return r.jsx("div",{role:"group","data-slot":"input-group-addon","data-align":e,className:h(Ii({align:e}),t),onClick:o=>{var n,i;o.target instanceof HTMLElement&&o.target.closest("button")||(i=(n=o.currentTarget.parentElement)==null?void 0:n.querySelector("input"))==null||i.focus()},...a})}ge.cva("tw:flex tw:items-center tw:gap-2 tw:text-sm tw:shadow-none",{variants:{size:{xs:"tw:h-6 tw:gap-1 tw:rounded-[calc(var(--radius)-3px)] tw:px-1.5 tw:[&>svg:not([class*=size-])]:size-3.5",sm:"tw:","icon-xs":"tw:size-6 tw:rounded-[calc(var(--radius)-3px)] tw:p-0 tw:has-[>svg]:p-0","icon-sm":"tw:size-8 tw:p-0 tw:has-[>svg]:p-0"}},defaultVariants:{size:"xs"}});function he({className:t,...e}){return r.jsx(Ve.Command,{"data-slot":"command",className:h("pr-twp tw:flex tw:size-full tw:flex-col tw:overflow-hidden tw:rounded-xl! tw:bg-popover tw:p-1 tw:text-popover-foreground",t),...e})}function Fe({className:t,onKeyDown:e,...a}){const o=ft(),n=l.useCallback(i=>{if(e==null||e(i),i.defaultPrevented||i.key!==" "||i.currentTarget.value!=="")return;const s=i.currentTarget.closest("[cmdk-root]"),c=s==null?void 0:s.querySelector('[cmdk-item][data-selected="true"]:not([data-disabled="true"])');c&&(i.preventDefault(),i.stopPropagation(),c.click())},[e]);return r.jsx("div",{"data-slot":"command-input-wrapper",className:"tw:p-1 tw:pb-0",dir:o,children:r.jsxs(Di,{className:"tw:h-8! tw:rounded-lg! tw:border-input/30 tw:bg-input/30 tw:shadow-none! tw:*:data-[slot=input-group-addon]:ps-2!",children:[r.jsx(Ve.Command.Input,{"data-slot":"command-input",className:h("tw:w-full tw:text-sm tw:outline-hidden tw:disabled:cursor-not-allowed tw:disabled:opacity-50",t),onKeyDown:n,...a}),r.jsx(Mi,{children:r.jsx(ht.IconSearch,{className:"tw:size-4 tw:shrink-0 tw:opacity-50"})})]})})}function fe({className:t,...e}){return r.jsx(Ve.Command.List,{"data-slot":"command-list",className:h("pr-twp tw:no-scrollbar tw:max-h-72 tw:scroll-py-1 tw:overflow-x-hidden tw:overflow-y-auto tw:outline-none",t),...e})}function Ze({className:t,...e}){return r.jsx(Ve.Command.Empty,{"data-slot":"command-empty",className:h("pr-twp tw:py-6 tw:text-center tw:text-sm",t),...e})}function re({className:t,...e}){return r.jsx(Ve.Command.Group,{"data-slot":"command-group",className:h("pr-twp tw:overflow-hidden tw:p-1 tw:text-foreground tw:**:[[cmdk-group-heading]]:px-2 tw:**:[[cmdk-group-heading]]:py-1.5 tw:**:[[cmdk-group-heading]]:text-xs tw:**:[[cmdk-group-heading]]:font-medium tw:**:[[cmdk-group-heading]]:text-muted-foreground",t),...e})}function ka({className:t,...e}){return r.jsx(Ve.Command.Separator,{"data-slot":"command-separator",className:h("pr-twp tw:-mx-1 tw:h-px tw:bg-border",t),...e})}function ie({className:t,children:e,...a}){return r.jsxs(Ve.Command.Item,{"data-slot":"command-item",className:h("pr-twp tw:group/command-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-2 tw:rounded-sm tw:px-2 tw:py-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:in-data-[slot=dialog-content]:rounded-lg! tw:data-[disabled=true]:pointer-events-none tw:data-[disabled=true]:opacity-50 tw:data-selected:bg-muted tw:data-selected:text-foreground tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:data-selected:*:[svg]:text-foreground",t),...a,children:[e,r.jsx(ht.IconCheck,{className:"tw:ms-auto tw:opacity-0 tw:group-has-data-[slot=command-shortcut]/command-item:hidden tw:group-data-[checked=true]/command-item:opacity-100"})]})}function Oi({className:t,...e}){return r.jsx("span",{"data-slot":"command-shortcut",className:h("pr-twp tw:ms-auto tw:text-xs tw:tracking-widest tw:text-muted-foreground tw:group-data-selected/command-item:text-foreground",t),...e})}const Do=(t,e,a,o,n)=>{switch(t){case D.Section.OT:return e??"Old Testament";case D.Section.NT:return a??"New Testament";case D.Section.DC:return o??"Deuterocanon";case D.Section.Extra:return n??"Extra Materials";default:throw new Error(`Unknown section: ${t}`)}},$i=(t,e,a,o,n)=>{switch(t){case D.Section.OT:return e??"OT";case D.Section.NT:return a??"NT";case D.Section.DC:return o??"DC";case D.Section.Extra:return n??"Extra";default:throw new Error(`Unknown section: ${t}`)}};function Ce(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedName)??st.Canon.bookIdToEnglishName(t)}function ja(t,e){var o;return((o=e==null?void 0:e.get(t))==null?void 0:o.localizedId)??t.toUpperCase()}const Io=st.Canon.allBookIds.filter(t=>!st.Canon.isObsolete(st.Canon.bookIdToNumber(t))),te=Object.fromEntries(Io.map(t=>[t,st.Canon.bookIdToEnglishName(t)]));function _a(t,e,a){const o=e.trim().toLowerCase();if(!o)return!1;const n=st.Canon.bookIdToEnglishName(t),i=a==null?void 0:a.get(t);return!!(D.includes(n.toLowerCase(),o)||D.includes(t.toLowerCase(),o)||(i?D.includes(i.localizedName.toLowerCase(),o)||D.includes(i.localizedId.toLowerCase(),o):!1))}function Mo({ref:t,bookId:e,isSelected:a,onSelect:o,onMouseDown:n,section:i,className:s,showCheck:c=!1,localizedBookNames:w,commandValue:d,disabled:u=!1}){const g=l.useRef(!1),m=()=>{u||(g.current||o==null||o(e),setTimeout(()=>{g.current=!1},100))},p=b=>{if(u){b.preventDefault();return}g.current=!0,n?n(b):o==null||o(e)},f=l.useMemo(()=>Ce(e,w),[e,w]),y=l.useMemo(()=>ja(e,w),[e,w]);return r.jsx("div",{className:h("tw:mx-1 tw:my-1 tw:border-b-0 tw:border-e-0 tw:border-s-2 tw:border-t-0 tw:border-solid",{"tw:border-s-red-200":i===D.Section.OT,"tw:border-s-purple-200":i===D.Section.NT,"tw:border-s-indigo-200":i===D.Section.DC,"tw:border-s-amber-200":i===D.Section.Extra}),children:r.jsxs(ie,{ref:t,value:d||`${e} ${st.Canon.bookIdToEnglishName(e)}`,onSelect:m,onMouseDown:p,role:"option","aria-selected":a,"aria-disabled":u||void 0,"aria-label":`${st.Canon.bookIdToEnglishName(e)} (${e.toLocaleUpperCase()})`,disabled:u,className:h(s,u&&"tw:cursor-not-allowed tw:opacity-50"),children:[c&&r.jsx($.Check,{className:h("tw:me-2 tw:h-4 tw:w-4 tw:shrink-0",a?"tw:opacity-100":"tw:opacity-0")}),r.jsx("span",{className:"tw:min-w-0 tw:flex-1",children:f}),r.jsx("span",{className:"tw:ms-2 tw:shrink-0 tw:text-xs tw:text-muted-foreground",children:y})]})})}function se({...t}){return r.jsx(k.Popover.Root,{"data-slot":"popover",...t})}function me({...t}){return r.jsx(k.Popover.Trigger,{"data-slot":"popover-trigger",...t})}const Oo=l.createContext(null);function jr({container:t,children:e}){return r.jsx(Oo.Provider,{value:t,children:e})}function ce({className:t,align:e="center",sideOffset:a=4,style:o,...n}){const i=ft(),s=l.useContext(Oo);return r.jsx(k.Popover.Portal,{container:s??void 0,children:r.jsx(k.Popover.Content,{"data-slot":"popover-content",align:e,sideOffset:a,className:h("pr-twp tw:flex tw:w-72 tw:origin-(--radix-popover-content-transform-origin) tw:flex-col tw:gap-2.5 tw:rounded-lg tw:bg-popover tw:p-2.5 tw:text-sm tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:outline-hidden tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),style:{zIndex:We,...o},dir:i,...n})})}function $o({...t}){return r.jsx(k.Popover.Anchor,{"data-slot":"popover-anchor",...t})}function Ai({className:t,...e}){return r.jsx("div",{"data-slot":"popover-header",className:h("pr-twp tw:flex tw:flex-col tw:gap-0.5 tw:text-sm",t),...e})}function Pi({className:t,...e}){return r.jsx("div",{"data-slot":"popover-title",className:h("pr-twp tw:font-medium",t),...e})}function Li({className:t,...e}){return r.jsx("p",{"data-slot":"popover-description",className:h("pr-twp tw:text-muted-foreground",t),...e})}function Ao(t,e,a){return`${t} ${te[t]}${e?` ${ja(t,e)} ${Ce(t,e)}`:""}`}function Po({recentSearches:t,onSearchItemSelect:e,renderItem:a=u=>String(u),getItemKey:o=u=>String(u),ariaLabel:n="Show recent searches",groupHeading:i="Recent",id:s,classNameForItems:c,buttonClassName:w="tw:absolute tw:right-0 tw:top-0 tw:h-full tw:px-3 tw:py-2",buttonVariant:d="ghost"}){const[u,g]=l.useState(!1);if(t.length===0)return;const m=p=>{e(p),g(!1)};return r.jsxs(se,{open:u,onOpenChange:g,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{variant:d,size:"icon",className:w,"aria-label":n,children:r.jsx($.Clock,{className:"tw:h-4 tw:w-4"})})}),r.jsx(ce,{id:s,className:"tw:w-[300px] tw:p-0",align:"start",children:r.jsx(he,{children:r.jsx(fe,{children:r.jsx(re,{heading:i,children:t.map(p=>r.jsxs(ie,{onSelect:()=>m(p),className:h("tw:flex tw:items-center",c),children:[r.jsx($.Clock,{className:"tw:mr-2 tw:h-4 tw:w-4 tw:opacity-50"}),r.jsx("span",{children:a(p)})]},o(p)))})})})})]})}function Bi(t,e,a=(n,i)=>n===i,o=15){return n=>{const i=t.filter(c=>!a(c,n)),s=[n,...i.slice(0,o-1)];e(s)}}const _r={BOOK_ONLY:/^([^:\s]+(?:\s+[^:\s]+)*)$/i,BOOK_CHAPTER:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+)$/i,BOOK_CHAPTER_VERSE:/^([^:\s]+(?:\s+[^:\s]+)*)\s+(\d+):(\d*)$/i},Vi=[_r.BOOK_ONLY,_r.BOOK_CHAPTER,_r.BOOK_CHAPTER_VERSE];function Fi(t){return _r.BOOK_CHAPTER_VERSE.test(t.trim())}function Za(t,e){return st.Canon.bookIdToNumber(t)0?!1:e0?!1:eo.chapterNum?!1:a{if(n)return n;const s=i.exec(t.trim());if(s){const[c,w=void 0,d=void 0]=s.slice(1);let u;const g=e.filter(m=>_a(m,c,a));if(g.length===1&&([u]=g),!u&&w){if(st.Canon.isBookIdValid(c)){const m=c.toUpperCase();e.includes(m)&&(u=m)}if(!u&&a){const m=Array.from(a.entries()).find(([,p])=>p.localizedId.toLowerCase()===c.toLowerCase());m&&e.includes(m[0])&&([u]=m)}}if(!u&&w){const p=(f=>Object.keys(te).find(y=>te[y].toLowerCase()===f.toLowerCase()))(c);if(p&&e.includes(p)&&(u=p),!u&&a){const f=Array.from(a.entries()).find(([,y])=>y.localizedName.toLowerCase()===c.toLowerCase());f&&e.includes(f[0])&&([u]=f)}}if(u){let m=w?parseInt(w,10):void 0;m&&m>we(u)&&(m=Math.max(we(u),1));const p=d?parseInt(d,10):void 0;return{book:u,chapterNum:m,verseNum:p}}}},void 0);if(o)return o}function Ki(t,e,a,o){const n=l.useCallback(()=>{if(t.chapterNum>1)o({book:t.book,chapterNum:t.chapterNum-1,verseNum:1});else{const w=e.indexOf(t.book);if(w>0){const d=e[w-1],u=Math.max(we(d),1);o({book:d,chapterNum:u,verseNum:1})}}},[t,e,o]),i=l.useCallback(()=>{const w=we(t.book);if(t.chapterNum{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum>1?t.verseNum-1:0})},[t,o]),c=l.useCallback(()=>{o({book:t.book,chapterNum:t.chapterNum,verseNum:t.verseNum+1})},[t,o]);return l.useMemo(()=>[{onClick:n,disabled:e.length===0||t.chapterNum===1&&e.indexOf(t.book)===0,title:"Previous chapter",icon:a==="ltr"?$.ChevronsLeft:$.ChevronsRight},{onClick:s,disabled:e.length===0||t.verseNum===0,title:"Previous verse",icon:a==="ltr"?$.ChevronLeft:$.ChevronRight},{onClick:c,disabled:e.length===0,title:"Next verse",icon:a==="ltr"?$.ChevronRight:$.ChevronLeft},{onClick:i,disabled:e.length===0||(t.chapterNum===we(t.book)||we(t.book)<=0)&&e.indexOf(t.book)===e.length-1,title:"Next chapter",icon:a==="ltr"?$.ChevronsRight:$.ChevronsLeft}],[t,e,a,n,s,c,i])}function Lo({count:t,valueBuilder:e,onSelect:a,itemRef:o,isDisabled:n,isDimmed:i,isSelected:s,className:c}){if(!(t<=0))return r.jsx(re,{children:r.jsx("div",{className:h("tw:grid tw:grid-cols-6 tw:gap-1",c),children:Array.from({length:t},(w,d)=>d+1).map(w=>{const d=(n==null?void 0:n(w))??!1;return r.jsx(ie,{value:e(w),onSelect:()=>{d||a(w)},ref:o(w),disabled:d,"aria-disabled":d||void 0,className:h("tw:h-8 tw:min-w-0 tw:cursor-pointer tw:justify-center tw:rounded-md tw:px-0 tw:text-center tw:text-sm",{"tw:bg-primary tw:text-primary-foreground":(s==null?void 0:s(w))??!1},{"tw:bg-muted/50 tw:text-muted-foreground/50":((i==null?void 0:i(w))??!1)&&!d},d&&"tw:cursor-not-allowed tw:opacity-40"),children:w},w)})})})}function Qa({bookId:t,scrRef:e,onChapterSelect:a,setChapterRef:o,isChapterDimmed:n,isChapterDisabled:i,className:s}){if(t)return r.jsx(Lo,{count:we(t),valueBuilder:c=>`${t} ${te[t]||""} ${c}`,onSelect:a,itemRef:o,isDisabled:i,isDimmed:n,isSelected:c=>t===e.book&&c===e.chapterNum,className:s})}function to({bookId:t,chapterNum:e,endVerse:a,scrRef:o,onVerseSelect:n,setVerseRef:i,isVerseDimmed:s,isVerseDisabled:c,className:w}){if(!(!t||a<=0))return r.jsx(Lo,{count:a,valueBuilder:d=>`${t} ${te[t]||""} ${e}:${d}`,onSelect:n,itemRef:i,isDisabled:c,isDimmed:s,isSelected:d=>t===o.book&&e===o.chapterNum&&d===o.verseNum,className:w})}function Nr({scrRef:t,handleSubmit:e,className:a,getActiveBookIds:o,localizedBookNames:n,localizedStrings:i,recentSearches:s,onAddRecentSearch:c,id:w,getEndVerse:d,disableReferencesUpTo:u,submitKeys:g,triggerContent:m,triggerVariant:p="outline",onOpenChange:f,onCloseAutoFocus:y,modal:b=!1,align:I="center"}){const C=ft(),[T,S]=l.useState(!1),[N,E]=l.useState(""),[z,j]=l.useState(""),[x,R]=l.useState("books"),[P,G]=l.useState(void 0),[K,V]=l.useState(void 0),[q,M]=l.useState(void 0),[Y,lt]=l.useState(!1),kt=l.useRef(null),St=l.useRef(!1),J=l.useRef(void 0),Et=l.useRef(void 0),U=l.useRef(void 0),tt=l.useRef(void 0),rt=l.useRef({}),at=l.useRef({}),ot=l.useCallback(_=>{e(_),c&&c(_)},[e,c]),Lt=l.useMemo(()=>o?o():Io,[o]),gt=l.useMemo(()=>({[D.Section.OT]:Lt.filter(H=>st.Canon.isBookOT(H)),[D.Section.NT]:Lt.filter(H=>st.Canon.isBookNT(H)),[D.Section.DC]:Lt.filter(H=>st.Canon.isBookDC(H)),[D.Section.Extra]:Lt.filter(H=>st.Canon.extraBooks().includes(H))}),[Lt]),Ft=l.useMemo(()=>Object.values(gt).flat(),[gt]),Gt=l.useMemo(()=>{if(!z.trim())return gt;const _={[D.Section.OT]:[],[D.Section.NT]:[],[D.Section.DC]:[],[D.Section.Extra]:[]};return[D.Section.OT,D.Section.NT,D.Section.DC,D.Section.Extra].forEach(Z=>{_[Z]=gt[Z].filter(_t=>_a(_t,z,n))}),_},[gt,z,n]),A=l.useMemo(()=>Ui(z,Ft,n),[z,Ft,n]),Tt=l.useRef(!1);l.useEffect(()=>{if(!Tt.current){Tt.current=!0;return}f==null||f(T)},[T,f]);const Bt=l.useCallback(()=>{if(A){const _=A.chapterNum??1,H=A.verseNum??1;if(u&&Yr(A.book,_,H,u))return;ot({book:A.book,chapterNum:_,verseNum:H}),S(!1),j(""),E("")}},[ot,A,u]),Qt=l.useCallback(_=>{const H=K??(A==null?void 0:A.book),Z=q??(A==null?void 0:A.chapterNum);!H||!Z||(ot({book:H,chapterNum:Z,verseNum:_}),S(!1))},[ot,K,q,A]),Ut=l.useCallback(_=>{if(u&&Za(_,u))return;if(we(_)<=1){ot({book:_,chapterNum:1,verseNum:1}),S(!1),j("");return}G(_),R("chapters")},[ot,u]),Rt=l.useCallback(_=>{const H=x==="chapters"?P:A==null?void 0:A.book;if(H){if(d&&d(H,_)>1){V(H),M(_),R("verses"),E("");return}ot({book:H,chapterNum:_,verseNum:1}),S(!1)}},[ot,x,P,A,d]),je=l.useCallback(_=>{ot(_),S(!1),j("")},[ot]),Kt=Ki(t,Ft,C,e),qt=l.useCallback(()=>{R("books"),G(void 0),V(void 0),M(void 0),setTimeout(()=>{Et.current&&Et.current.focus()},0)},[]),B=l.useCallback(()=>{const _=K;V(void 0),M(void 0),_?(G(_),R("chapters"),E("")):qt()},[K,qt]),W=l.useCallback(_=>{S(_),_&&(R("books"),G(void 0),V(void 0),M(void 0),j(""))},[]),{otLong:nt,ntLong:et,dcLong:wt,extraLong:mt}={otLong:i==null?void 0:i["%scripture_section_ot_long%"],ntLong:i==null?void 0:i["%scripture_section_nt_long%"],dcLong:i==null?void 0:i["%scripture_section_dc_long%"],extraLong:i==null?void 0:i["%scripture_section_extra_long%"]},vt=l.useCallback(_=>Do(_,nt,et,wt,mt),[nt,et,wt,mt]),ut=l.useCallback(_=>A?!!A.chapterNum&&!_.toString().includes(A.chapterNum.toString()):!1,[A]),jt=l.useMemo(()=>D.formatScrRef(t,n?Ce(t.book,n):"English"),[t,n]),O=l.useCallback(_=>H=>{rt.current[_]=H},[]),ct=l.useCallback(_=>H=>{at.current[_]=H},[]),dt=l.useMemo(()=>Fi(z),[z]),bt=l.useMemo(()=>!d||!A||!A.chapterNum||!dt?!1:d(A.book,A.chapterNum)>0,[d,A,dt]),Re=l.useCallback(_=>u?Za(_,u):!1,[u]),_e=l.useCallback(_=>H=>u?Gi(_,H,u):!1,[u]),Qe=l.useCallback((_,H)=>Z=>u?Yr(_,H,Z,u):!1,[u]),ze=(i==null?void 0:i["%webView_bookChapterControl_selectChapter%"])??"Select Chapter",tr=(i==null?void 0:i["%webView_bookChapterControl_selectVerse%"])??"Select Verse",er=l.useCallback(_=>{(_.key==="Home"||_.key==="End")&&_.stopPropagation(),g&&g.includes(_.key)&&A&&A.chapterNum!==void 0&&A.verseNum!==void 0&&(_.preventDefault(),_.stopPropagation(),Bt())},[g,A,Bt]),gr=l.useCallback(_=>{var Wt,Ue,rr;if(_.ctrlKey)return;const{isLetter:H,isDigit:Z}=Ja(_.key);if((x==="chapters"||x==="verses")&&(_.key===" "||_.key==="Enter")){const zt=_.target instanceof HTMLElement?_.target:void 0;if(!!(zt!=null&&zt.closest('button, a, input, select, textarea, [role="button"]'))){_.stopPropagation();return}const Nt=(Wt=J.current)==null?void 0:Wt.querySelector('[cmdk-item][data-selected="true"]:not([data-disabled="true"])');if(Nt){_.preventDefault(),_.stopPropagation(),Nt.click();return}}if((x==="chapters"||x==="verses")&&(H||Z)){_.preventDefault(),_.stopPropagation();return}if(x==="chapters"&&_.key==="Backspace"){_.preventDefault(),_.stopPropagation(),qt();return}if(x==="verses"&&_.key==="Backspace"){_.preventDefault(),_.stopPropagation(),B();return}const _t=["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(_.key);if(x==="verses"&&_t){const zt=K,xt=q;if(!zt||!xt||!d)return;const Nt=d(zt,xt);if(!Nt)return;(Ue=J.current)==null||Ue.focus();const pt=(()=>{if(!N)return 1;const De=N.match(/:(\d+)$/);return De?parseInt(De[1],10):0})();let Ht=pt;const Yt=6;switch(_.key){case"ArrowLeft":pt!==0&&(Ht=pt>1?pt-1:Nt);break;case"ArrowRight":pt!==0&&(Ht=pt{const De=at.current[Ht];De&&De.scrollIntoView({block:"nearest",behavior:"smooth"})},0));return}if((x==="chapters"||x==="books"&&A)&&_t){const zt=x==="chapters"?P:A==null?void 0:A.book;if(!zt)return;x==="chapters"&&((rr=J.current)==null||rr.focus());const xt=(()=>{if(!N)return 1;const Yt=N.match(/(\d+)$/);return Yt?parseInt(Yt[1],10):0})(),Nt=we(zt);if(!Nt)return;let pt=xt;const Ht=6;switch(_.key){case"ArrowLeft":xt!==0&&(pt=xt>1?xt-1:Nt);break;case"ArrowRight":xt!==0&&(pt=xt{const Yt=rt.current[pt];Yt&&Yt.scrollIntoView({block:"nearest",behavior:"smooth"})},0))}},[x,A,qt,B,P,K,q,d,N]),hr=l.useCallback(_=>{var _t;if(_.shiftKey||_.key==="Tab"||_.key===" ")return;if(_.key==="Enter"){_.stopPropagation();return}if(_.key==="ArrowUp"||_.key==="ArrowDown"){(_t=Et.current)==null||_t.focus();return}const{isLetter:H,isDigit:Z}=Ja(_.key);(H||Z)&&(_.preventDefault(),j(Wt=>Wt+_.key),Et.current.focus(),lt(!1))},[]);return l.useLayoutEffect(()=>{const _=setTimeout(()=>{if(T&&x==="books"&&U.current&&tt.current){const H=U.current,Z=tt.current,_t=Z.offsetTop,Wt=H.clientHeight,Ue=Z.clientHeight,rr=_t-Wt/2+Ue/2;H.scrollTo({top:Math.max(0,rr),behavior:"smooth"}),E(Ao(t.book))}},0);return()=>{clearTimeout(_)}},[T,x,z,A,t.book]),l.useLayoutEffect(()=>{if(x==="chapters"&&P){const _=P===t.book,H=_?t.chapterNum:1;E(`${P} ${te[P]||""} ${H}`),setTimeout(()=>{if(U.current)if(_){const Z=rt.current[t.chapterNum];Z&&Z.scrollIntoView({block:"center",behavior:"smooth"})}else U.current.scrollTo({top:0});J.current&&J.current.focus()},0)}},[x,P,A,t.book,t.chapterNum]),l.useLayoutEffect(()=>{if(x==="verses"&&K&&q!==void 0){const _=K===t.book&&q===t.chapterNum,H=_?t.verseNum:1;E(`${K} ${te[K]||""} ${q}:${H}`),setTimeout(()=>{if(U.current)if(_){const Z=at.current[t.verseNum];Z&&Z.scrollIntoView({block:"center",behavior:"smooth"})}else U.current.scrollTo({top:0});J.current&&J.current.focus()},0)}},[x,K,q,t.book,t.chapterNum,t.verseNum]),r.jsxs(se,{open:T,onOpenChange:W,modal:b,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{ref:kt,"aria-label":"book-chapter-trigger",variant:p,role:"combobox","aria-expanded":T,className:h("tw:h-8 tw:w-full tw:min-w-16 tw:max-w-48 tw:overflow-hidden tw:px-1",a),onClick:_=>{St.current&&(St.current=!1,_.preventDefault())},children:m??r.jsx("span",{className:"tw:truncate",children:jt})})}),r.jsx(ce,{id:w,forceMount:!0,className:"tw:w-[var(--radix-popper-anchor-width,280px)] tw:min-w-[200px] tw:max-w-[280px] tw:p-0",align:I,onKeyDownCapture:gr,onKeyDown:_=>_.stopPropagation(),onPointerDownOutside:_=>{const{target:H}=_;T&&kt.current&&H instanceof Node&&kt.current.contains(H)&&(St.current=!0,W(!1))},onCloseAutoFocus:y,children:r.jsxs(he,{ref:J,loop:!0,value:N,onValueChange:E,shouldFilter:!1,children:[x==="books"?r.jsxs("div",{className:"tw:flex tw:items-end",children:[r.jsxs("div",{className:"tw:relative tw:flex-1",children:[r.jsx(Fe,{ref:Et,value:z,onValueChange:j,onKeyDown:er,onFocus:()=>lt(!1),className:s&&s.length>0?"tw:!pr-10":""}),s&&s.length>0&&r.jsx(Po,{recentSearches:s,onSearchItemSelect:je,renderItem:_=>D.formatScrRef(_,"English"),getItemKey:_=>`${_.book}-${_.chapterNum}-${_.verseNum}`,ariaLabel:i==null?void 0:i["%history_recentSearches_ariaLabel%"],groupHeading:i==null?void 0:i["%history_recent%"]})]}),r.jsx("div",{className:"tw:flex tw:items-center tw:gap-1 tw:border-b tw:pe-2",children:Kt.map(({onClick:_,disabled:H,title:Z,icon:_t})=>r.jsx(F,{variant:"ghost",size:"sm",onClick:()=>{lt(!0),_()},disabled:H,className:"tw:h-10 tw:w-4 tw:p-0",title:Z,onKeyDown:hr,children:r.jsx(_t,{})},Z))})]}):r.jsxs("div",{className:"tw:flex tw:items-center tw:border-b tw:px-3 tw:py-2",children:[r.jsx(F,{variant:"ghost",size:"sm",onClick:x==="verses"?B:qt,className:"tw:mr-2 tw:h-6 tw:w-6 tw:p-0",tabIndex:-1,children:C==="ltr"?r.jsx($.ArrowLeft,{className:"tw:h-4 tw:w-4"}):r.jsx($.ArrowRight,{className:"tw:h-4 tw:w-4"})}),x==="chapters"&&P&&r.jsx("span",{tabIndex:-1,className:"tw:text-sm tw:font-medium",children:Ce(P,n)}),x==="verses"&&K&&q!==void 0&&r.jsx("span",{tabIndex:-1,className:"tw:text-sm tw:font-medium",children:`${Ce(K,n)} ${q}`}),r.jsx("span",{tabIndex:-1,className:"tw:ms-auto tw:text-sm tw:font-medium tw:text-muted-foreground",children:x==="verses"?tr:ze})]}),!Y&&r.jsxs(fe,{ref:U,children:[x==="books"&&r.jsxs(r.Fragment,{children:[!A&&Object.entries(Gt).map(([_,H])=>{if(H.length!==0)return r.jsx(re,{heading:vt(_),children:H.map(Z=>r.jsx(Mo,{bookId:Z,onSelect:_t=>Ut(_t),section:D.getSectionForBook(Z),commandValue:`${Z} ${te[Z]}`,ref:Z===t.book?tt:void 0,localizedBookNames:n,disabled:Re(Z)},Z))},_)}),A&&r.jsx(re,{children:r.jsx(ie,{value:`${A.book} ${te[A.book]} ${A.chapterNum||""}:${A.verseNum||""})}`,onSelect:Bt,disabled:!!u&&Yr(A.book,A.chapterNum??1,A.verseNum??1,u),className:"tw:font-semibold tw:text-primary",children:D.formatScrRef({book:A.book,chapterNum:A.chapterNum??1,verseNum:A.verseNum??1},n?ja(A.book,n):void 0)},"top-match")}),A&&bt&&A.chapterNum&&d&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"tw:mb-2 tw:flex tw:items-center tw:justify-between tw:px-3 tw:text-sm tw:font-medium tw:text-muted-foreground",children:[r.jsx("span",{children:`${Ce(A.book,n)} ${A.chapterNum}`}),r.jsx("span",{children:tr})]}),r.jsx(to,{bookId:A.book,chapterNum:A.chapterNum,endVerse:d(A.book,A.chapterNum),scrRef:t,onVerseSelect:Qt,setVerseRef:ct,isVerseDisabled:Qe(A.book,A.chapterNum),className:"tw:px-4 tw:pb-4"})]}),A&&!bt&&we(A.book)>1&&r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:"tw:mb-2 tw:flex tw:items-center tw:justify-between tw:px-3 tw:text-sm tw:font-medium tw:text-muted-foreground",children:[r.jsx("span",{children:Ce(A.book,n)}),r.jsx("span",{children:ze})]}),r.jsx(Qa,{bookId:A.book,scrRef:t,onChapterSelect:Rt,setChapterRef:O,isChapterDimmed:ut,isChapterDisabled:_e(A.book),className:"tw:px-4 tw:pb-4"})]})]}),x==="chapters"&&P&&r.jsx(Qa,{bookId:P,scrRef:t,onChapterSelect:Rt,setChapterRef:O,isChapterDisabled:_e(P),className:"tw:p-4"}),x==="verses"&&K&&q!==void 0&&d&&r.jsx(to,{bookId:K,chapterNum:q,endVerse:d(K,q),scrRef:t,onVerseSelect:Qt,setVerseRef:ct,isVerseDisabled:Qe(K,q),className:"tw:p-4"})]})]})})]})}const qi=Object.freeze(["%scripture_section_ot_long%","%scripture_section_nt_long%","%scripture_section_dc_long%","%scripture_section_extra_long%","%history_recent%","%history_recentSearches_ariaLabel%","%webView_bookChapterControl_selectChapter%","%webView_bookChapterControl_selectVerse%"]);function yt({className:t,...e}){return r.jsx(k.Label.Root,{"data-slot":"label",className:h("pr-twp tw:flex tw:items-center tw:gap-2 tw:text-sm tw:leading-none tw:font-medium tw:select-none tw:group-data-[disabled=true]:pointer-events-none tw:group-data-[disabled=true]:opacity-50 tw:peer-disabled:cursor-not-allowed tw:peer-disabled:opacity-50",t),...e})}function Na({className:t,...e}){const a=ft();return r.jsx(k.RadioGroup.Root,{"data-slot":"radio-group",className:h("pr-twp tw:grid tw:w-full tw:gap-2",t),dir:a,...e})}function Dr({className:t,...e}){return r.jsx(k.RadioGroup.Item,{"data-slot":"radio-group-item",className:h("pr-twp tw:group/radio-group-item tw:peer tw:relative tw:flex tw:aspect-square tw:size-4 tw:shrink-0 tw:rounded-full tw:border tw:border-input tw:outline-none tw:after:absolute tw:after:-inset-x-3 tw:after:-inset-y-2 tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:aria-checked:border-primary tw:dark:bg-input/30 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:data-checked:border-primary tw:data-checked:bg-primary tw:data-checked:text-primary-foreground tw:dark:data-checked:bg-primary",t),...e,children:r.jsx(k.RadioGroup.Indicator,{"data-slot":"radio-group-indicator",className:"tw:flex tw:size-4 tw:items-center tw:justify-center",children:r.jsx("span",{className:"tw:absolute tw:top-1/2 tw:start-1/2 tw:size-2 tw:-translate-x-1/2 tw:rtl:translate-x-1/2 tw:-translate-y-1/2 tw:rounded-full tw:bg-primary-foreground"})})})}function Hi(t){return typeof t=="string"?t:typeof t=="number"?t.toString():t.label}function wa({id:t,options:e=[],className:a,buttonClassName:o,popoverContentClassName:n,popoverContentStyle:i,value:s,onChange:c=()=>{},getOptionLabel:w=Hi,getButtonLabel:d,icon:u=void 0,buttonPlaceholder:g="",textPlaceholder:m="",commandEmptyMessage:p="No option found",buttonVariant:f="outline",alignDropDown:y="start",isDisabled:b=!1,ariaLabel:I,...C}){const[T,S]=l.useState(!1),N=d??w,E=j=>j.length>0&&typeof j[0]=="object"&&"options"in j[0],z=(j,x)=>{const R=w(j),P=typeof j=="object"&&"secondaryLabel"in j?j.secondaryLabel:void 0,G=`${x??""}${R}${P??""}`;return r.jsxs(ie,{value:R,onSelect:()=>{c(j),S(!1)},className:"tw:flex tw:items-center",children:[r.jsx($.Check,{className:h("tw:me-2 tw:h-4 tw:w-4 tw:shrink-0",{"tw:opacity-0":!s||w(s)!==R})}),r.jsxs("span",{className:"tw:flex-1 tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap",children:[R,P&&r.jsxs("span",{className:"tw:text-muted-foreground",children:[" · ",P]})]})]},G)};return r.jsxs(se,{open:T,onOpenChange:S,...C,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:f,role:"combobox","aria-expanded":T,"aria-label":I,id:t,className:h("tw:flex tw:w-[200px] tw:items-center tw:justify-between tw:overflow-hidden",o??a),disabled:b,children:[r.jsxs("div",{className:"tw:flex tw:min-w-0 tw:flex-1 tw:items-center tw:overflow-hidden",children:[u&&r.jsx("div",{className:"tw:shrink-0 tw:pe-2",children:u}),r.jsx("span",{className:h("tw:min-w-0 tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap tw:text-start"),children:s?N(s):g})]}),r.jsx($.ChevronDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})}),r.jsx(ce,{align:y,className:h("tw:w-[200px] tw:p-0",n),style:i,children:r.jsxs(he,{children:[r.jsx(Fe,{placeholder:m,className:"tw:text-inherit"}),r.jsx(Ze,{children:p}),r.jsx(fe,{children:E(e)?e.map(j=>r.jsx(re,{heading:j.groupHeading,children:j.options.map(x=>z(x,j.groupHeading))},j.groupHeading)):e.map(j=>z(j))})]})})]})}function Bo({startChapter:t,endChapter:e,handleSelectStartChapter:a,handleSelectEndChapter:o,isDisabled:n=!1,chapterCount:i}){const s=l.useMemo(()=>Array.from({length:i},(d,u)=>u+1),[i]),c=d=>{a(d),d>e&&o(d)},w=d=>{o(d),dd.toString(),value:t},"start chapter"),r.jsx(yt,{htmlFor:"end-chapters-combobox",children:"to"}),r.jsx(wa,{isDisabled:n,onChange:w,buttonClassName:"tw:ms-2 tw:w-20",options:s,getOptionLabel:d=>d.toString(),value:e},"end chapter")]})}exports.BookSelectionMode=(t=>(t.CurrentBook="current book",t.ChooseBooks="choose books",t))(exports.BookSelectionMode||{});(t=>{t.CURRENT_BOOK="current book",t.CHOOSE_BOOKS="choose books"})(exports.BookSelectionMode||(exports.BookSelectionMode={}));const Yi=Object.freeze(["%webView_bookSelector_currentBook%","%webView_bookSelector_choose%","%webView_bookSelector_chooseBooks%"]),Wr=(t,e)=>t[e]??e;function Wi({handleBookSelectionModeChange:t,currentBookName:e,onSelectBooks:a,selectedBookIds:o,chapterCount:n,endChapter:i,handleSelectEndChapter:s,startChapter:c,handleSelectStartChapter:w,localizedStrings:d}){const u=Wr(d,"%webView_bookSelector_currentBook%"),g=Wr(d,"%webView_bookSelector_choose%"),m=Wr(d,"%webView_bookSelector_chooseBooks%"),[p,f]=l.useState("current book"),y=b=>{f(b),t(b)};return r.jsx(Na,{className:"pr-twp tw:flex",value:p,onValueChange:b=>y(b),children:r.jsxs("div",{className:"tw:flex tw:w-full tw:flex-col tw:gap-4",children:[r.jsxs("div",{className:"tw:grid tw:grid-cols-[25%_25%_50%]",children:[r.jsxs("div",{className:"tw:flex tw:items-center",children:[r.jsx(Dr,{value:"current book"}),r.jsx(yt,{className:"tw:ms-1",children:u})]}),r.jsx(yt,{className:"tw:flex tw:items-center",children:e}),r.jsx("div",{className:"tw:flex tw:items-center tw:justify-end",children:r.jsx(Bo,{isDisabled:p==="choose books",handleSelectStartChapter:w,handleSelectEndChapter:s,chapterCount:n,startChapter:c,endChapter:i})})]}),r.jsxs("div",{className:"tw:grid tw:grid-cols-[25%_50%_25%]",children:[r.jsxs("div",{className:"tw:flex tw:items-center",children:[r.jsx(Dr,{value:"choose books"}),r.jsx(yt,{className:"tw:ms-1",children:m})]}),r.jsx(yt,{className:"tw:flex tw:items-center",children:o.map(b=>st.Canon.bookIdToEnglishName(b)).join(", ")}),r.jsx(F,{disabled:p==="current book",onClick:()=>a(),children:g})]})]})})}const Vo=l.createContext(null);function Xi(t,e){return{getTheme:function(){return e??null}}}function ve(){const t=l.useContext(Vo);return t==null&&function(e,...a){const o=new URL("https://lexical.dev/docs/error"),n=new URLSearchParams;n.append("code",e);for(const i of a)n.append("v",i);throw o.search=n.toString(),Error(`Minified Lexical error #${e}; visit ${o.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(8),t}const Fo=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,Zi=Fo?l.useLayoutEffect:l.useEffect,br={tag:v.HISTORY_MERGE_TAG};function Ji({initialConfig:t,children:e}){const a=l.useMemo(()=>{const{theme:o,namespace:n,nodes:i,onError:s,editorState:c,html:w}=t,d=Xi(null,o),u=v.createEditor({editable:t.editable,html:w,namespace:n,nodes:i,onError:g=>s(g,u),theme:o});return function(g,m){if(m!==null){if(m===void 0)g.update(()=>{const p=v.$getRoot();if(p.isEmpty()){const f=v.$createParagraphNode();p.append(f);const y=Fo?document.activeElement:null;(v.$getSelection()!==null||y!==null&&y===g.getRootElement())&&f.select()}},br);else if(m!==null)switch(typeof m){case"string":{const p=g.parseEditorState(m);g.setEditorState(p,br);break}case"object":g.setEditorState(m,br);break;case"function":g.update(()=>{v.$getRoot().isEmpty()&&m(g)},br)}}}(u,c),[u,d]},[]);return Zi(()=>{const o=t.editable,[n]=a;n.setEditable(o===void 0||o)},[]),r.jsx(Vo.Provider,{value:a,children:e})}const Qi=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function ts({ignoreHistoryMergeTagChange:t=!0,ignoreSelectionChange:e=!1,onChange:a}){const[o]=ve();return Qi(()=>{if(a)return o.registerUpdateListener(({editorState:n,dirtyElements:i,dirtyLeaves:s,prevEditorState:c,tags:w})=>{e&&i.size===0&&s.size===0||t&&w.has(v.HISTORY_MERGE_TAG)||c.isEmpty()||a(n,o,w)})},[o,t,e,a]),null}const Ca={ltr:"tw:text-left",rtl:"tw:text-right",heading:{h1:"tw:scroll-m-20 tw:text-4xl tw:font-extrabold tw:tracking-tight tw:lg:text-5xl",h2:"tw:scroll-m-20 tw:border-b tw:pb-2 tw:text-3xl tw:font-semibold tw:tracking-tight tw:first:mt-0",h3:"tw:scroll-m-20 tw:text-2xl tw:font-semibold tw:tracking-tight",h4:"tw:scroll-m-20 tw:text-xl tw:font-semibold tw:tracking-tight",h5:"tw:scroll-m-20 tw:text-lg tw:font-semibold tw:tracking-tight",h6:"tw:scroll-m-20 tw:text-base tw:font-semibold tw:tracking-tight"},paragraph:"tw:outline-hidden",quote:"tw:mt-6 tw:border-l-2 tw:pl-6 tw:italic",link:"tw:text-blue-600 tw:hover:underline tw:hover:cursor-pointer",list:{checklist:"tw:relative",listitem:"tw:mx-8",listitemChecked:'tw:relative tw:mx-2 tw:px-6 tw:list-none tw:outline-hidden tw:line-through tw:before:content-[""] tw:before:w-4 tw:before:h-4 tw:before:top-0.5 tw:before:left-0 tw:before:cursor-pointer tw:before:block tw:before:bg-cover tw:before:absolute tw:before:border tw:before:border-primary tw:before:rounded tw:before:bg-primary tw:before:bg-no-repeat tw:after:content-[""] tw:after:cursor-pointer tw:after:border-white tw:after:border-solid tw:after:absolute tw:after:block tw:after:top-[6px] tw:after:w-[3px] tw:after:left-[7px] tw:after:right-[7px] tw:after:h-[6px] tw:after:rotate-45 tw:after:border-r-2 tw:after:border-b-2 tw:after:border-l-0 tw:after:border-t-0',listitemUnchecked:'tw:relative tw:mx-2 tw:px-6 tw:list-none tw:outline-hidden tw:before:content-[""] tw:before:w-4 tw:before:h-4 tw:before:top-0.5 tw:before:left-0 tw:before:cursor-pointer tw:before:block tw:before:bg-cover tw:before:absolute tw:before:border tw:before:border-primary tw:before:rounded',nested:{listitem:"tw:list-none tw:before:hidden tw:after:hidden"},ol:"tw:m-0 tw:p-0 tw:list-decimal tw:[&>li]:mt-2",olDepth:["tw:list-outside tw:!list-decimal","tw:list-outside tw:!list-[upper-roman]","tw:list-outside tw:!list-[lower-roman]","tw:list-outside tw:!list-[upper-alpha]","tw:list-outside tw:!list-[lower-alpha]"],ul:"tw:m-0 tw:p-0 tw:list-outside tw:[&>li]:mt-2",ulDepth:["tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc","tw:list-outside tw:!list-disc"]},hashtag:"tw:text-blue-600 tw:bg-blue-100 tw:rounded-md tw:px-1",text:{bold:"tw:font-bold",code:"tw:bg-gray-100 tw:p-1 tw:rounded-md",italic:"tw:italic",strikethrough:"tw:line-through",subscript:"tw:sub",superscript:"tw:sup",underline:"tw:underline",underlineStrikethrough:"tw:underline tw:line-through"},image:"tw:relative tw:inline-block tw:user-select-none tw:cursor-default editor-image",inlineImage:"tw:relative tw:inline-block tw:user-select-none tw:cursor-default inline-editor-image",keyword:"tw:text-purple-900 tw:font-bold",code:"EditorTheme__code",codeHighlight:{atrule:"EditorTheme__tokenAttr",attr:"EditorTheme__tokenAttr",boolean:"EditorTheme__tokenProperty",builtin:"EditorTheme__tokenSelector",cdata:"EditorTheme__tokenComment",char:"EditorTheme__tokenSelector",class:"EditorTheme__tokenFunction","class-name":"EditorTheme__tokenFunction",comment:"EditorTheme__tokenComment",constant:"EditorTheme__tokenProperty",deleted:"EditorTheme__tokenProperty",doctype:"EditorTheme__tokenComment",entity:"EditorTheme__tokenOperator",function:"EditorTheme__tokenFunction",important:"EditorTheme__tokenVariable",inserted:"EditorTheme__tokenSelector",keyword:"EditorTheme__tokenAttr",namespace:"EditorTheme__tokenVariable",number:"EditorTheme__tokenProperty",operator:"EditorTheme__tokenOperator",prolog:"EditorTheme__tokenComment",property:"EditorTheme__tokenProperty",punctuation:"EditorTheme__tokenPunctuation",regex:"EditorTheme__tokenVariable",selector:"EditorTheme__tokenSelector",string:"EditorTheme__tokenSelector",symbol:"EditorTheme__tokenProperty",tag:"EditorTheme__tokenProperty",url:"EditorTheme__tokenOperator",variable:"EditorTheme__tokenVariable"},characterLimit:"tw:!bg-destructive/50",table:"EditorTheme__table tw:w-fit tw:overflow-scroll tw:border-collapse",tableCell:"EditorTheme__tableCell tw:w-24 tw:relative tw:border tw:px-4 tw:py-2 tw:text-left tw:[&[align=center]]:text-center tw:[&[align=right]]:text-right",tableCellActionButton:"EditorTheme__tableCellActionButton tw:bg-background tw:block tw:border-0 tw:rounded-2xl tw:w-5 tw:h-5 tw:text-foreground tw:cursor-pointer",tableCellActionButtonContainer:"EditorTheme__tableCellActionButtonContainer tw:block tw:right-1 tw:top-1.5 tw:absolute tw:z-10 tw:w-5 tw:h-5",tableCellEditing:"EditorTheme__tableCellEditing tw:rounded-sm tw:shadow-sm",tableCellHeader:"EditorTheme__tableCellHeader tw:bg-muted tw:border tw:px-4 tw:py-2 tw:text-left tw:font-bold tw:[&[align=center]]:text-center tw:[&[align=right]]:text-right",tableCellPrimarySelected:"EditorTheme__tableCellPrimarySelected tw:border tw:border-primary tw:border-solid tw:block tw:h-[calc(100%-2px)] tw:w-[calc(100%-2px)] tw:absolute tw:-left-[1px] tw:-top-[1px] tw:z-10 ",tableCellResizer:"EditorTheme__tableCellResizer tw:absolute tw:-right-1 tw:h-full tw:w-2 tw:cursor-ew-resize tw:z-10 tw:top-0",tableCellSelected:"EditorTheme__tableCellSelected tw:bg-muted",tableCellSortedIndicator:"EditorTheme__tableCellSortedIndicator tw:block tw:opacity-50 tw:absolute tw:bottom-0 tw:left-0 tw:w-full tw:h-1 tw:bg-muted",tableResizeRuler:"EditorTheme__tableCellResizeRuler tw:block tw:absolute tw:w-[1px] tw:h-full tw:bg-primary tw:top-0",tableRowStriping:"EditorTheme__tableRowStriping tw:m-0 tw:border-t tw:p-0 tw:even:bg-muted",tableSelected:"EditorTheme__tableSelected tw:ring-2 tw:ring-primary tw:ring-offset-2",tableSelection:"EditorTheme__tableSelection tw:bg-transparent",layoutItem:"tw:border tw:border-dashed tw:px-4 tw:py-2",layoutContainer:"tw:grid tw:gap-2.5 tw:my-2.5 tw:mx-0",autocomplete:"tw:text-muted-foreground",blockCursor:"",embedBlock:{base:"tw:user-select-none",focus:"tw:ring-2 tw:ring-primary tw:ring-offset-2"},hr:'tw:p-0.5 tw:border-none tw:my-1 tw:mx-0 tw:cursor-pointer tw:after:content-[""] tw:after:block tw:after:h-0.5 tw:after:bg-muted tw:selected:ring-2 tw:selected:ring-primary tw:selected:ring-offset-2 tw:selected:user-select-none',indent:"[--lexical-indent-base-value:40px]",mark:"",markOverlap:""};function Ot({delayDuration:t=0,...e}){return r.jsx(k.Tooltip.Provider,{"data-slot":"tooltip-provider",delayDuration:t,...e})}function $t({...t}){return r.jsx(k.Tooltip.Root,{"data-slot":"tooltip",...t})}function At({className:t,variant:e,...a}){return r.jsx(k.Tooltip.Trigger,{"data-slot":"tooltip-trigger",className:e?h(ya({variant:e}),t):t,...a})}function Pt({className:t,sideOffset:e=0,style:a,children:o,...n}){return r.jsx(k.Tooltip.Portal,{children:r.jsxs(k.Tooltip.Content,{"data-slot":"tooltip-content",sideOffset:e,style:{zIndex:To,...a},className:h("pr-twp tw:inline-flex tw:w-fit tw:max-w-xs tw:origin-(--radix-tooltip-content-transform-origin) tw:items-center tw:gap-1.5 tw:rounded-md tw:bg-foreground tw:px-3 tw:py-1.5 tw:text-xs tw:text-background tw:has-data-[slot=kbd]:pe-1.5 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:**:data-[slot=kbd]:relative tw:**:data-[slot=kbd]:isolate tw:**:data-[slot=kbd]:z-50 tw:**:data-[slot=kbd]:rounded-sm tw:data-[state=delayed-open]:animate-in tw:data-[state=delayed-open]:fade-in-0 tw:data-[state=delayed-open]:zoom-in-95 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95",t),...n,children:[o,r.jsx(k.Tooltip.Arrow,{className:"tw:z-50 tw:size-2.5 tw:translate-y-[calc(-50%_-_2px)] tw:rotate-45 tw:rounded-[2px] tw:bg-foreground tw:fill-foreground"})]})})}const Sa=[ca.HeadingNode,v.ParagraphNode,v.TextNode,ca.QuoteNode],es=l.createContext(null),Xr={didCatch:!1,error:null};class rs extends l.Component{constructor(e){super(e),this.resetErrorBoundary=this.resetErrorBoundary.bind(this),this.state=Xr}static getDerivedStateFromError(e){return{didCatch:!0,error:e}}resetErrorBoundary(){const{error:e}=this.state;if(e!==null){for(var a,o,n=arguments.length,i=new Array(n),s=0;s0&&arguments[0]!==void 0?arguments[0]:[],e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[];return t.length!==e.length||t.some((a,o)=>!Object.is(a,e[o]))}function os({children:t,onError:e}){return r.jsx(rs,{fallback:r.jsx("div",{style:{border:"1px solid #f00",color:"#f00",padding:"8px"},children:"An error was thrown."}),onError:e,children:t})}const ns=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function is(t){return{initialValueFn:()=>t.isEditable(),subscribe:e=>t.registerEditableListener(e)}}function ss(){return function(t){const[e]=ve(),a=l.useMemo(()=>t(e),[e,t]),[o,n]=l.useState(()=>a.initialValueFn()),i=l.useRef(o);return ns(()=>{const{initialValueFn:s,subscribe:c}=a,w=s();return i.current!==w&&(i.current=w,n(w)),c(d=>{i.current=d,n(d)})},[a,t]),o}(is)}function cs(t,e){const a=t.getRootElement();if(a===null)return[];const o=a.getBoundingClientRect(),n=getComputedStyle(a),i=parseFloat(n.paddingLeft)+parseFloat(n.paddingRight),s=Array.from(e.getClientRects());let c,w=s.length;s.sort((d,u)=>{const g=d.top-u.top;return Math.abs(g)<=3?d.left-u.left:g});for(let d=0;du.top&&c.left+c.width>u.left,m=u.width+i===o.width;g||m?(s.splice(d--,1),w--):c=u}return s}function ls(t,e,a="self"){const o=t.getStartEndPoints();if(e.isSelected(t)&&!v.$isTokenOrSegmented(e)&&o!==null){const[n,i]=o,s=t.isBackward(),c=n.getNode(),w=i.getNode(),d=e.is(c),u=e.is(w);if(d||u){const[g,m]=v.$getCharacterOffsets(t),p=c.is(w),f=e.is(s?w:c),y=e.is(s?c:w);let b,I=0;p?(I=g>m?m:g,b=g>m?g:m):f?(I=s?m:g,b=void 0):y&&(I=0,b=s?g:m);const C=e.__text.slice(I,b);C!==e.__text&&(a==="clone"&&(e=v.$cloneWithPropertiesEphemeral(e)),e.__text=C)}}return e}function Ir(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const Go=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0,ds=Go&&"documentMode"in document?document.documentMode:null;!(!Go||!("InputEvent"in window)||ds)&&"getTargetRanges"in new window.InputEvent("input");function de(t){return`${t}px`}const ws={attributes:!0,characterData:!0,childList:!0,subtree:!0};function us(t,e,a){let o=null,n=null,i=null,s=[];const c=document.createElement("div");function w(){o===null&&Ir(182),n===null&&Ir(183);const{left:g,top:m}=n.getBoundingClientRect(),p=cs(t,e);var f,y;c.isConnected||(y=c,(f=n).insertBefore(y,f.firstChild));let b=!1;for(let I=0;Ip.length;)s.pop();b&&a(s)}function d(){n=null,o=null,i!==null&&i.disconnect(),i=null,c.remove();for(const g of s)g.remove();s=[]}c.style.position="relative";const u=t.registerRootListener(function g(){const m=t.getRootElement();if(m===null)return d();const p=m.parentElement;if(!v.isHTMLElement(p))return d();d(),o=m,n=p,i=new MutationObserver(f=>{const y=t.getRootElement(),b=y&&y.parentElement;if(y!==o||b!==n)return g();for(const I of f)if(!c.contains(I.target))return w()}),i.observe(p,ws),w()});return()=>{u(),d()}}function eo(t,e,a){if(t.type!=="text"&&v.$isElementNode(e)){const o=e.getDOMSlot(a);return[o.element,o.getFirstChildOffset()+t.offset]}return[v.getDOMTextNode(a)||a,t.offset]}function ps(t){for(const e of t){const a=e.style;a.background!=="Highlight"&&(a.background="Highlight"),a.color!=="HighlightText"&&(a.color="HighlightText"),a.marginTop!==de(-1.5)&&(a.marginTop=de(-1.5)),a.paddingTop!==de(4)&&(a.paddingTop=de(4)),a.paddingBottom!==de(0)&&(a.paddingBottom=de(0))}}function gs(t,e=ps){let a=null,o=null,n=null,i=null,s=null,c=null,w=()=>{};function d(u){u.read(()=>{const g=v.$getSelection();if(!v.$isRangeSelection(g))return a=null,n=null,i=null,c=null,w(),void(w=()=>{});const[m,p]=function(j){const x=j.getStartEndPoints();return j.isBackward()?[x[1],x[0]]:x}(g),f=m.getNode(),y=f.getKey(),b=m.offset,I=p.getNode(),C=I.getKey(),T=p.offset,S=t.getElementByKey(y),N=t.getElementByKey(C),E=a===null||S!==o||b!==n||y!==a.getKey(),z=i===null||N!==s||T!==c||C!==i.getKey();if((E||z)&&S!==null&&N!==null){const j=function(x,R,P,G,K,V,q){const M=(x._window?x._window.document:document).createRange();return M.setStart(...eo(R,P,G)),M.setEnd(...eo(K,V,q)),M}(t,m,f,S,p,I,N);w(),w=us(t,j,e)}a=f,o=S,n=b,i=I,s=N,c=T})}return d(t.getEditorState()),v.mergeRegister(t.registerUpdateListener(({editorState:u})=>d(u)),()=>{w()})}function hs(t,e){let a=null;const o=()=>{const n=getSelection(),i=n&&n.anchorNode,s=t.getRootElement();i!==null&&s!==null&&s.contains(i)?a!==null&&(a(),a=null):a===null&&(a=gs(t,e))};return t.registerRootListener(n=>{if(n){const i=n.ownerDocument;return i.addEventListener("selectionchange",o),o(),()=>{a!==null&&a(),i.removeEventListener("selectionchange",o)}}})}function fs(t){const e=v.$findMatchingParent(t,a=>v.$isElementNode(a)&&!a.isInline());return v.$isElementNode(e)||Ir(4,t.__key),e}function ms(t){const e=v.$getSelection()||v.$getPreviousSelection();let a;if(v.$isRangeSelection(e))a=v.$caretFromPoint(e.focus,"next");else{if(e!=null){const s=e.getNodes(),c=s[s.length-1];c&&(a=v.$getSiblingCaret(c,"next"))}a=a||v.$getChildCaret(v.$getRoot(),"previous").getFlipped().insert(v.$createParagraphNode())}const o=vs(t,a),n=v.$getAdjacentChildCaret(o),i=v.$isChildCaret(n)?v.$normalizeCaret(n):o;return v.$setSelectionFromCaretRange(v.$getCollapsedCaretRange(i)),t.getLatest()}function vs(t,e,a){let o=v.$getCaretInDirection(e,"next");for(let n=o;n;n=v.$splitAtPointCaretNext(n,a))o=n;return v.$isTextPointCaret(o)&&Ir(283),o.insert(t.isInline()?v.$createParagraphNode().append(t):t),v.$getCaretInDirection(v.$getSiblingCaret(t.getLatest(),"next"),e.direction)}function bs(t){const e=v.$getSelection();if(!v.$isRangeSelection(e))return!1;const a=new Set,o=e.getNodes();for(let n=0;nv.$isElementNode(d)&&!d.isInline());if(c===null)continue;const w=c.getKey();c.canIndent()&&!a.has(w)&&(a.add(w),t(c))}return a.size>0}const xs=Symbol.for("preact-signals");function Fr(){if(xe>1)return void xe--;let t,e=!1;for(!function(){let a=Mr;for(Mr=void 0;a!==void 0;)a.S.v===a.v&&(a.S.i=a.i),a=a.o}();ir!==void 0;){let a=ir;for(ir=void 0,Or++;a!==void 0;){const o=a.u;if(a.u=void 0,a.f&=-3,!(8&a.f)&&Uo(a))try{a.c()}catch(n){e||(t=n,e=!0)}a=o}}if(Or=0,xe--,e)throw t}function ys(t){if(xe>0)return t();ua=++ks,xe++;try{return t()}finally{Fr()}}let Q,ir;function ro(t){const e=Q;Q=void 0;try{return t()}finally{Q=e}}let Mr,xe=0,Or=0,ks=0,ua=0,Cr=0;function ao(t){if(Q===void 0)return;let e=t.n;return e===void 0||e.t!==Q?(e={i:0,S:t,p:Q.s,n:void 0,t:Q,e:void 0,x:void 0,r:e},Q.s!==void 0&&(Q.s.n=e),Q.s=e,t.n=e,32&Q.f&&t.S(e),e):e.i===-1?(e.i=0,e.n!==void 0&&(e.n.p=e.p,e.p!==void 0&&(e.p.n=e.n),e.p=Q.s,e.n=void 0,Q.s.n=e,Q.s=e),e):void 0}function It(t,e){this.v=t,this.i=0,this.n=void 0,this.t=void 0,this.l=0,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function cr(t,e){return new It(t,e)}function Uo(t){for(let e=t.s;e!==void 0;e=e.n)if(e.S.i!==e.i||!e.S.h()||e.S.i!==e.i)return!0;return!1}function oo(t){for(let e=t.s;e!==void 0;e=e.n){const a=e.S.n;if(a!==void 0&&(e.r=a),e.S.n=e,e.i=-1,e.n===void 0){t.s=e;break}}}function Ko(t){let e,a=t.s;for(;a!==void 0;){const o=a.p;a.i===-1?(a.S.U(a),o!==void 0&&(o.n=a.n),a.n!==void 0&&(a.n.p=o)):e=a,a.S.n=a.r,a.r!==void 0&&(a.r=void 0),a=o}t.s=e}function Ie(t,e){It.call(this,void 0),this.x=t,this.s=void 0,this.g=Cr-1,this.f=4,this.W=e==null?void 0:e.watched,this.Z=e==null?void 0:e.unwatched,this.name=e==null?void 0:e.name}function js(t,e){return new Ie(t,e)}function qo(t){const e=t.m;if(t.m=void 0,typeof e=="function"){xe++;const a=Q;Q=void 0;try{e()}catch(o){throw t.f&=-2,t.f|=8,Ea(t),o}finally{Q=a,Fr()}}}function Ea(t){for(let e=t.s;e!==void 0;e=e.n)e.S.U(e);t.x=void 0,t.s=void 0,qo(t)}function _s(t){if(Q!==this)throw new Error("Out-of-order effect");Ko(this),Q=t,this.f&=-2,8&this.f&&Ea(this),Fr()}function qe(t,e){this.x=t,this.m=void 0,this.s=void 0,this.u=void 0,this.f=32,this.name=e==null?void 0:e.name}function ue(t,e){const a=new qe(t,e);try{a.c()}catch(n){throw a.d(),n}const o=a.d.bind(a);return o[Symbol.dispose]=o,o}function Je(t,e={}){const a={};for(const o in t){const n=e[o],i=cr(n===void 0?t[o]:n);a[o]=i}return a}It.prototype.brand=xs,It.prototype.h=function(){return!0},It.prototype.S=function(t){const e=this.t;e!==t&&t.e===void 0&&(t.x=e,this.t=t,e!==void 0?e.e=t:ro(()=>{var a;(a=this.W)==null||a.call(this)}))},It.prototype.U=function(t){if(this.t!==void 0){const e=t.e,a=t.x;e!==void 0&&(e.x=a,t.e=void 0),a!==void 0&&(a.e=e,t.x=void 0),t===this.t&&(this.t=a,a===void 0&&ro(()=>{var o;(o=this.Z)==null||o.call(this)}))}},It.prototype.subscribe=function(t){return ue(()=>{const e=this.value,a=Q;Q=void 0;try{t(e)}finally{Q=a}},{name:"sub"})},It.prototype.valueOf=function(){return this.value},It.prototype.toString=function(){return this.value+""},It.prototype.toJSON=function(){return this.value},It.prototype.peek=function(){const t=Q;Q=void 0;try{return this.value}finally{Q=t}},Object.defineProperty(It.prototype,"value",{get(){const t=ao(this);return t!==void 0&&(t.i=this.i),this.v},set(t){if(t!==this.v){if(Or>100)throw new Error("Cycle detected");(function(e){xe!==0&&Or===0&&e.l!==ua&&(e.l=ua,Mr={S:e,v:e.v,i:e.i,o:Mr})})(this),this.v=t,this.i++,Cr++,xe++;try{for(let e=this.t;e!==void 0;e=e.x)e.t.N()}finally{Fr()}}}}),Ie.prototype=new It,Ie.prototype.h=function(){if(this.f&=-3,1&this.f)return!1;if((36&this.f)==32||(this.f&=-5,this.g===Cr))return!0;if(this.g=Cr,this.f|=1,this.i>0&&!Uo(this))return this.f&=-2,!0;const t=Q;try{oo(this),Q=this;const e=this.x();(16&this.f||this.v!==e||this.i===0)&&(this.v=e,this.f&=-17,this.i++)}catch(e){this.v=e,this.f|=16,this.i++}return Q=t,Ko(this),this.f&=-2,!0},Ie.prototype.S=function(t){if(this.t===void 0){this.f|=36;for(let e=this.s;e!==void 0;e=e.n)e.S.S(e)}It.prototype.S.call(this,t)},Ie.prototype.U=function(t){if(this.t!==void 0&&(It.prototype.U.call(this,t),this.t===void 0)){this.f&=-33;for(let e=this.s;e!==void 0;e=e.n)e.S.U(e)}},Ie.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let t=this.t;t!==void 0;t=t.x)t.t.N()}},Object.defineProperty(Ie.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const t=ao(this);if(this.h(),t!==void 0&&(t.i=this.i),16&this.f)throw this.v;return this.v}}),qe.prototype.c=function(){const t=this.S();try{if(8&this.f||this.x===void 0)return;const e=this.x();typeof e=="function"&&(this.m=e)}finally{t()}},qe.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1,this.f&=-9,qo(this),oo(this),xe++;const t=Q;return Q=this,_s.bind(this,t)},qe.prototype.N=function(){2&this.f||(this.f|=2,this.u=ir,ir=this)},qe.prototype.d=function(){this.f|=8,1&this.f||Ea(this)},qe.prototype.dispose=function(){this.d()};v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({defaultSelection:"rootEnd",disabled:!1}),name:"@lexical/extension/AutoFocus",register(t,e,a){const o=a.getOutput();return ue(()=>o.disabled.value?void 0:t.registerRootListener(n=>{t.focus(()=>{const i=document.activeElement;n===null||i!==null&&n.contains(i)||n.focus({preventScroll:!0})},{defaultSelection:o.defaultSelection.peek()})}))}});function Ho(){const t=v.$getRoot(),e=v.$getSelection(),a=v.$createParagraphNode();t.clear(),t.append(a),e!==null&&a.select(),v.$isRangeSelection(e)&&(e.format=0)}function Yo(t,e=Ho){return t.registerCommand(v.CLEAR_EDITOR_COMMAND,a=>(t.update(e),!0),v.COMMAND_PRIORITY_EDITOR)}v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({$onClear:Ho}),name:"@lexical/extension/ClearEditor",register(t,e,a){const{$onClear:o}=a.getOutput();return ue(()=>Yo(t,o.value))}});function Ns(t){return(typeof t.nodes=="function"?t.nodes():t.nodes)||[]}const Zr=v.createState("format",{parse:t=>typeof t=="number"?t:0});class Wo extends v.DecoratorNode{$config(){return this.config("decorator-text",{extends:v.DecoratorNode,stateConfigs:[{flat:!0,stateConfig:Zr}]})}getFormat(){return v.$getState(this,Zr)}getFormatFlags(e,a){return v.toggleTextFormatType(this.getFormat(),e,a)}hasFormat(e){const a=v.TEXT_TYPE_TO_FORMAT[e];return(this.getFormat()&a)!==0}setFormat(e){return v.$setState(this,Zr,e)}toggleFormat(e){const a=this.getFormat(),o=v.toggleTextFormatType(a,e,null);return this.setFormat(o)}isInline(){return!0}createDOM(){return document.createElement("span")}updateDOM(){return!1}}function Cs(t){return t instanceof Wo}v.defineExtension({name:"@lexical/extension/DecoratorText",nodes:()=>[Wo],register:(t,e,a)=>t.registerCommand(v.FORMAT_TEXT_COMMAND,o=>{const n=v.$getSelection();if(v.$isNodeSelection(n)||v.$isRangeSelection(n))for(const i of n.getNodes())Cs(i)&&i.toggleFormat(o);return!1},v.COMMAND_PRIORITY_LOW)});function Xo(t,e){let a;return cr(t(),{unwatched(){a&&(a(),a=void 0)},watched(){this.value=t(),a=e(this)}})}const pa=v.defineExtension({build:t=>Xo(()=>t.getEditorState(),e=>t.registerUpdateListener(a=>{e.value=a.editorState})),name:"@lexical/extension/EditorState"});function it(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}function Zo(t,e){if(t&&e&&!Array.isArray(e)&&typeof t=="object"&&typeof e=="object"){const a=t,o=e;for(const n in o)a[n]=Zo(a[n],o[n]);return t}return e}const Ta=0,ga=1,Jo=2,Jr=3,xr=4,Ke=5,Qr=6,or=7;function ta(t){return t.id===Ta}function Qo(t){return t.id===Jo}function Ss(t){return function(e){return e.id===ga}(t)||it(305,String(t.id),String(ga)),Object.assign(t,{id:Jo})}const Es=new Set;class Ts{constructor(e,a){Dt(this,"builder");Dt(this,"configs");Dt(this,"_dependency");Dt(this,"_peerNameSet");Dt(this,"extension");Dt(this,"state");Dt(this,"_signal");this.builder=e,this.extension=a,this.configs=new Set,this.state={id:Ta}}mergeConfigs(){let e=this.extension.config||{};const a=this.extension.mergeConfig?this.extension.mergeConfig.bind(this.extension):v.shallowMergeConfig;for(const o of this.configs)e=a(e,o);return e}init(e){const a=this.state;Qo(a)||it(306,String(a.id));const o={getDependency:this.getInitDependency.bind(this),getDirectDependentNames:this.getDirectDependentNames.bind(this),getPeer:this.getInitPeer.bind(this),getPeerNameSet:this.getPeerNameSet.bind(this)},n={...o,getDependency:this.getDependency.bind(this),getInitResult:this.getInitResult.bind(this),getPeer:this.getPeer.bind(this)},i=function(c,w,d){return Object.assign(c,{config:w,id:Jr,registerState:d})}(a,this.mergeConfigs(),o);let s;this.state=i,this.extension.init&&(s=this.extension.init(e,i.config,o)),this.state=function(c,w,d){return Object.assign(c,{id:xr,initResult:w,registerState:d})}(i,s,n)}build(e){const a=this.state;let o;a.id!==xr&&it(307,String(a.id),String(Ke)),this.extension.build&&(o=this.extension.build(e,a.config,a.registerState));const n={...a.registerState,getOutput:()=>o,getSignal:this.getSignal.bind(this)};this.state=function(i,s,c){return Object.assign(i,{id:Ke,output:s,registerState:c})}(a,o,n)}register(e,a){this._signal=a;const o=this.state;o.id!==Ke&&it(308,String(o.id),String(Ke));const n=this.extension.register&&this.extension.register(e,o.config,o.registerState);return this.state=function(i){return Object.assign(i,{id:Qr})}(o),()=>{const i=this.state;i.id!==or&&it(309,String(o.id),String(or)),this.state=function(s){return Object.assign(s,{id:Ke})}(i),n&&n()}}afterRegistration(e){const a=this.state;let o;return a.id!==Qr&&it(310,String(a.id),String(Qr)),this.extension.afterRegistration&&(o=this.extension.afterRegistration(e,a.config,a.registerState)),this.state=function(n){return Object.assign(n,{id:or})}(a),o}getSignal(){return this._signal===void 0&&it(311),this._signal}getInitResult(){this.extension.init===void 0&&it(312,this.extension.name);const e=this.state;return function(a){return a.id>=xr}(e)||it(313,String(e.id),String(xr)),e.initResult}getInitPeer(e){const a=this.builder.extensionNameMap.get(e);return a?a.getExtensionInitDependency():void 0}getExtensionInitDependency(){const e=this.state;return function(a){return a.id>=Jr}(e)||it(314,String(e.id),String(Jr)),{config:e.config}}getPeer(e){const a=this.builder.extensionNameMap.get(e);return a?a.getExtensionDependency():void 0}getInitDependency(e){const a=this.builder.getExtensionRep(e);return a===void 0&&it(315,this.extension.name,e.name),a.getExtensionInitDependency()}getDependency(e){const a=this.builder.getExtensionRep(e);return a===void 0&&it(315,this.extension.name,e.name),a.getExtensionDependency()}getState(){const e=this.state;return function(a){return a.id>=or}(e)||it(316,String(e.id),String(or)),e}getDirectDependentNames(){return this.builder.incomingEdges.get(this.extension.name)||Es}getPeerNameSet(){let e=this._peerNameSet;return e||(e=new Set((this.extension.peerDependencies||[]).map(([a])=>a)),this._peerNameSet=e),e}getExtensionDependency(){if(!this._dependency){const e=this.state;(function(a){return a.id>=Ke})(e)||it(317,this.extension.name),this._dependency={config:e.config,init:e.initResult,output:e.output}}return this._dependency}}const no={tag:v.HISTORY_MERGE_TAG};function Rs(){const t=v.$getRoot();t.isEmpty()&&t.append(v.$createParagraphNode())}const zs=v.defineExtension({config:v.safeCast({setOptions:no,updateOptions:no}),init:({$initialEditorState:t=Rs})=>({$initialEditorState:t,initialized:!1}),afterRegistration(t,{updateOptions:e,setOptions:a},o){const n=o.getInitResult();if(!n.initialized){n.initialized=!0;const{$initialEditorState:i}=n;if(v.$isEditorState(i))t.setEditorState(i,a);else if(typeof i=="function")t.update(()=>{i(t)},e);else if(i&&(typeof i=="string"||typeof i=="object")){const s=t.parseEditorState(i);t.setEditorState(s,a)}}return()=>{}},name:"@lexical/extension/InitialState",nodes:[v.RootNode,v.TextNode,v.LineBreakNode,v.TabNode,v.ParagraphNode]}),io=Symbol.for("@lexical/extension/LexicalBuilder");function so(){}function Ds(t){throw t}function yr(t){return Array.isArray(t)?t:[t]}const ea="0.43.0+prod.esm";class He{constructor(e){Dt(this,"roots");Dt(this,"extensionNameMap");Dt(this,"outgoingConfigEdges");Dt(this,"incomingEdges");Dt(this,"conflicts");Dt(this,"_sortedExtensionReps");Dt(this,"PACKAGE_VERSION");this.outgoingConfigEdges=new Map,this.incomingEdges=new Map,this.extensionNameMap=new Map,this.conflicts=new Map,this.PACKAGE_VERSION=ea,this.roots=e;for(const a of e)this.addExtension(a)}static fromExtensions(e){const a=[yr(zs)];for(const o of e)a.push(yr(o));return new He(a)}static maybeFromEditor(e){const a=e[io];return a&&(a.PACKAGE_VERSION!==ea&&it(292,a.PACKAGE_VERSION,ea),a instanceof He||it(293)),a}static fromEditor(e){const a=He.maybeFromEditor(e);return a===void 0&&it(294),a}constructEditor(){const{$initialEditorState:e,onError:a,...o}=this.buildCreateEditorArgs(),n=Object.assign(v.createEditor({...o,...a?{onError:i=>{a(i,n)}}:{}}),{[io]:this});for(const i of this.sortedExtensionReps())i.build(n);return n}buildEditor(){let e=so;function a(){try{e()}finally{e=so}}const o=Object.assign(this.constructEditor(),{dispose:a,[Symbol.dispose]:a});return e=v.mergeRegister(this.registerEditor(o),()=>o.setRootElement(null)),o}hasExtensionByName(e){return this.extensionNameMap.has(e)}getExtensionRep(e){const a=this.extensionNameMap.get(e.name);if(a)return a.extension!==e&&it(295,e.name),a}addEdge(e,a,o){const n=this.outgoingConfigEdges.get(e);n?n.set(a,o):this.outgoingConfigEdges.set(e,new Map([[a,o]]));const i=this.incomingEdges.get(a);i?i.add(e):this.incomingEdges.set(a,new Set([e]))}addExtension(e){this._sortedExtensionReps!==void 0&&it(296);const a=yr(e),[o]=a;typeof o.name!="string"&&it(297,typeof o.name);let n=this.extensionNameMap.get(o.name);if(n!==void 0&&n.extension!==o&&it(298,o.name),!n){n=new Ts(this,o),this.extensionNameMap.set(o.name,n);const i=this.conflicts.get(o.name);typeof i=="string"&&it(299,o.name,i);for(const s of o.conflictsWith||[])this.extensionNameMap.has(s)&&it(299,o.name,s),this.conflicts.set(s,o.name);for(const s of o.dependencies||[]){const c=yr(s);this.addEdge(o.name,c[0].name,c.slice(1)),this.addExtension(c)}for(const[s,c]of o.peerDependencies||[])this.addEdge(o.name,s,c?[c]:[])}}sortedExtensionReps(){if(this._sortedExtensionReps)return this._sortedExtensionReps;const e=[],a=(o,n)=>{let i=o.state;if(Qo(i))return;const s=o.extension.name;var c;ta(i)||it(300,s,n||"[unknown]"),ta(c=i)||it(304,String(c.id),String(Ta)),i=Object.assign(c,{id:ga}),o.state=i;const w=this.outgoingConfigEdges.get(s);if(w)for(const d of w.keys()){const u=this.extensionNameMap.get(d);u&&a(u,s)}i=Ss(i),o.state=i,e.push(o)};for(const o of this.extensionNameMap.values())ta(o.state)&&a(o);for(const o of e)for(const[n,i]of this.outgoingConfigEdges.get(o.extension.name)||[])if(i.length>0){const s=this.extensionNameMap.get(n);if(s)for(const c of i)s.configs.add(c)}for(const[o,...n]of this.roots)if(n.length>0){const i=this.extensionNameMap.get(o.name);i===void 0&&it(301,o.name);for(const s of n)i.configs.add(s)}return this._sortedExtensionReps=e,this._sortedExtensionReps}registerEditor(e){const a=this.sortedExtensionReps(),o=new AbortController,n=[()=>o.abort()],i=o.signal;for(const s of a){const c=s.register(e,i);c&&n.push(c)}for(const s of a){const c=s.afterRegistration(e);c&&n.push(c)}return v.mergeRegister(...n)}buildCreateEditorArgs(){const e={},a=new Set,o=new Map,n=new Map,i={},s={},c=this.sortedExtensionReps();for(const u of c){const{extension:g}=u;if(g.onError!==void 0&&(e.onError=g.onError),g.disableEvents!==void 0&&(e.disableEvents=g.disableEvents),g.parentEditor!==void 0&&(e.parentEditor=g.parentEditor),g.editable!==void 0&&(e.editable=g.editable),g.namespace!==void 0&&(e.namespace=g.namespace),g.$initialEditorState!==void 0&&(e.$initialEditorState=g.$initialEditorState),g.nodes)for(const m of Ns(g)){if(typeof m!="function"){const p=o.get(m.replace);p&&it(302,g.name,m.replace.name,p.extension.name),o.set(m.replace,u)}a.add(m)}if(g.html){if(g.html.export)for(const[m,p]of g.html.export.entries())n.set(m,p);g.html.import&&Object.assign(i,g.html.import)}g.theme&&Zo(s,g.theme)}Object.keys(s).length>0&&(e.theme=s),a.size&&(e.nodes=[...a]);const w=Object.keys(i).length>0,d=n.size>0;(w||d)&&(e.html={},w&&(e.html.import=i),d&&(e.html.export=n));for(const u of c)u.init(e);return e.onError||(e.onError=Ds),e}}const Is=new Set,co=v.defineExtension({build(t,e,a){const o=a.getDependency(pa).output,n=cr({watchedNodeKeys:new Map}),i=Xo(()=>{},()=>ue(()=>{const s=i.peek(),{watchedNodeKeys:c}=n.value;let w,d=!1;o.value.read(()=>{if(v.$getSelection())for(const[u,g]of c.entries()){if(g.size===0){c.delete(u);continue}const m=v.$getNodeByKey(u),p=m&&m.isSelected()||!1;d=d||p!==(!!s&&s.has(u)),p&&(w=w||new Set,w.add(u))}}),!d&&w&&s&&w.size===s.size||(i.value=w)}));return{watchNodeKey:function(s){const c=js(()=>(i.value||Is).has(s)),{watchedNodeKeys:w}=n.peek();let d=w.get(s);const u=d!==void 0;return d=d||new Set,d.add(c),u||(w.set(s,d),n.value={watchedNodeKeys:w}),c}}},dependencies:[pa],name:"@lexical/extension/NodeSelection"}),Ms=v.createCommand("INSERT_HORIZONTAL_RULE_COMMAND");class Ye extends v.DecoratorNode{static getType(){return"horizontalrule"}static clone(e){return new Ye(e.__key)}static importJSON(e){return Ra().updateFromJSON(e)}static importDOM(){return{hr:()=>({conversion:Os,priority:0})}}exportDOM(){return{element:document.createElement("hr")}}createDOM(e){const a=document.createElement("hr");return v.addClassNamesToElement(a,e.theme.hr),a}getTextContent(){return` +`}isInline(){return!1}updateDOM(){return!1}}function Os(){return{node:Ra()}}function Ra(){return v.$create(Ye)}function $s(t){return t instanceof Ye}v.defineExtension({dependencies:[pa,co],name:"@lexical/extension/HorizontalRule",nodes:()=>[Ye],register(t,e,a){const{watchNodeKey:o}=a.getDependency(co).output,n=cr({nodeSelections:new Map}),i=t._config.theme.hrSelected??"selected";return v.mergeRegister(t.registerCommand(Ms,s=>{const c=v.$getSelection();if(!v.$isRangeSelection(c))return!1;if(c.focus.getNode()!==null){const w=Ra();ms(w)}return!0},v.COMMAND_PRIORITY_EDITOR),t.registerCommand(v.CLICK_COMMAND,s=>{if(v.isDOMNode(s.target)){const c=v.$getNodeFromDOMNode(s.target);if($s(c))return function(w,d=!1){const u=v.$getSelection(),g=w.isSelected(),m=w.getKey();let p;d&&v.$isNodeSelection(u)?p=u:(p=v.$createNodeSelection(),v.$setSelection(p)),g?p.delete(m):p.add(m)}(c,s.shiftKey),!0}return!1},v.COMMAND_PRIORITY_LOW),t.registerMutationListener(Ye,(s,c)=>{ys(()=>{let w=!1;const{nodeSelections:d}=n.peek();for(const[u,g]of s.entries())if(g==="destroyed")d.delete(u),w=!0;else{const m=d.get(u),p=t.getElementByKey(u);m?m.domNode.value=p:(w=!0,d.set(u,{domNode:cr(p),selectedSignal:o(u)}))}w&&(n.value={nodeSelections:d})})}),ue(()=>{const s=[];for(const{domNode:c,selectedSignal:w}of n.value.nodeSelections.values())s.push(ue(()=>{const d=c.value;d&&(w.value?v.addClassNamesToElement(d,i):v.removeClassNamesFromElement(d,i))}));return v.mergeRegister(...s)}))}});v.defineExtension({build:(t,e)=>Je({inheritEditableFromParent:e.inheritEditableFromParent}),config:v.safeCast({$getParentEditor:function(){const t=v.$getEditor();return He.fromEditor(t),t},inheritEditableFromParent:!1}),init:(t,e,a)=>{const o=e.$getParentEditor();t.parentEditor=o,t.theme=t.theme||o._config.theme},name:"@lexical/extension/NestedEditor",register:(t,e,a)=>ue(()=>{const o=t._parentEditor;if(o&&a.getOutput().inheritEditableFromParent.value)return t.setEditable(o.isEditable()),o.registerEditableListener(t.setEditable.bind(t))})});v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({disabled:!1,onReposition:void 0}),name:"@lexical/utils/SelectionAlwaysOnDisplay",register:(t,e,a)=>{const o=a.getOutput();return ue(()=>{if(!o.disabled.value)return hs(t,o.onReposition.value)})}});function tn(t){return t.canBeEmpty()}function As(t,e,a=tn){return v.mergeRegister(t.registerCommand(v.KEY_TAB_COMMAND,o=>{const n=v.$getSelection();if(!v.$isRangeSelection(n))return!1;o.preventDefault();const i=function(s){if(s.getNodes().filter(m=>v.$isBlockElementNode(m)&&m.canIndent()).length>0)return!0;const c=s.anchor,w=s.focus,d=w.isBefore(c)?w:c,u=d.getNode(),g=fs(u);if(g.canIndent()){const m=g.getKey();let p=v.$createRangeSelection();if(p.anchor.set(m,0,"element"),p.focus.set(m,0,"element"),p=v.$normalizeSelection__EXPERIMENTAL(p),p.anchor.is(d))return!0}return!1}(n)?o.shiftKey?v.OUTDENT_CONTENT_COMMAND:v.INDENT_CONTENT_COMMAND:v.INSERT_TAB_COMMAND;return t.dispatchCommand(i,void 0)},v.COMMAND_PRIORITY_EDITOR),t.registerCommand(v.INDENT_CONTENT_COMMAND,()=>{const o=typeof e=="number"?e:e?e.peek():null,n=v.$getSelection();if(!v.$isRangeSelection(n))return!1;const i=typeof a=="function"?a:a.peek();return bs(s=>{if(i(s)){const c=s.getIndent()+1;(!o||cJe(e),config:v.safeCast({$canIndent:tn,disabled:!1,maxIndent:null}),name:"@lexical/extension/TabIndentation",register(t,e,a){const{disabled:o,maxIndent:n,$canIndent:i}=a.getOutput();return ue(()=>{if(!o.value)return As(t,n,i)})}});const Ps=v.defineExtension({name:"@lexical/react/ReactProvider"});function Ls(){return v.$getRoot().getTextContent()}function Bs(t,e=!0){if(t)return!1;let a=Ls();return e&&(a=a.trim()),a===""}function Vs(t){if(!Bs(t,!1))return!1;const e=v.$getRoot().getChildren(),a=e.length;if(a>1)return!1;for(let o=0;oVs(t)}function rn(t){const e=window.location.origin,a=o=>{if(o.origin!==e)return;const n=t.getRootElement();if(document.activeElement!==n)return;const i=o.data;if(typeof i=="string"){let s;try{s=JSON.parse(i)}catch{return}if(s&&s.protocol==="nuanria_messaging"&&s.type==="request"){const c=s.payload;if(c&&c.functionId==="makeChanges"){const w=c.args;if(w){const[d,u,g,m,p]=w;t.update(()=>{const f=v.$getSelection();if(v.$isRangeSelection(f)){const y=f.anchor;let b=y.getNode(),I=0,C=0;if(v.$isTextNode(b)&&d>=0&&u>=0&&(I=d,C=d+u,f.setTextNodeRange(b,I,b,C)),I===C&&g===""||(f.insertRawText(g),b=y.getNode()),v.$isTextNode(b)){I=m,C=m+p;const T=b.getTextContentSize();I=I>T?T:I,C=C>T?T:C,f.setTextNodeRange(b,I,b,C)}o.stopImmediatePropagation()}})}}}}};return window.addEventListener("message",a,!0),()=>{window.removeEventListener("message",a,!0)}}v.defineExtension({build:(t,e,a)=>Je(e),config:v.safeCast({disabled:typeof window>"u"}),name:"@lexical/dragon",register:(t,e,a)=>ue(()=>a.getOutput().disabled.value?void 0:rn(t))});function Fs(t,...e){const a=new URL("https://lexical.dev/docs/error"),o=new URLSearchParams;o.append("code",t);for(const n of e)o.append("v",n);throw a.search=o.toString(),Error(`Minified Lexical error #${t}; visit ${a.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}const za=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Gs({editor:t,ErrorBoundary:e}){return function(a,o){const[n,i]=l.useState(()=>a.getDecorators());return za(()=>a.registerDecoratorListener(s=>{Xa.flushSync(()=>{i(s)})}),[a]),l.useEffect(()=>{i(a.getDecorators())},[a]),l.useMemo(()=>{const s=[],c=Object.keys(n);for(let w=0;wa._onError(m),children:r.jsx(l.Suspense,{fallback:null,children:n[d]})}),g=a.getElementByKey(d);g!==null&&s.push(Xa.createPortal(u,g,d))}return s},[o,n,a])}(t,e)}function Us({editor:t,ErrorBoundary:e}){return function(a){const o=He.maybeFromEditor(a);if(o&&o.hasExtensionByName(Ps.name)){for(const n of["@lexical/plain-text","@lexical/rich-text"])o.hasExtensionByName(n)&&Fs(320,n);return!0}return!1}(t)?null:r.jsx(Gs,{editor:t,ErrorBoundary:e})}function lo(t){return t.getEditorState().read(en(t.isComposing()))}function Ks({contentEditable:t,placeholder:e=null,ErrorBoundary:a}){const[o]=ve();return function(n){za(()=>v.mergeRegister(ca.registerRichText(n),rn(n)),[n])}(o),r.jsxs(r.Fragment,{children:[t,r.jsx(qs,{content:e}),r.jsx(Us,{editor:o,ErrorBoundary:a})]})}function qs({content:t}){const[e]=ve(),a=function(n){const[i,s]=l.useState(()=>lo(n));return za(()=>{function c(){const w=lo(n);s(w)}return c(),v.mergeRegister(n.registerUpdateListener(()=>{c()}),n.registerEditableListener(()=>{c()}))},[n]),i}(e),o=ss();return a?typeof t=="function"?t(o):t:null}function Hs({defaultSelection:t}){const[e]=ve();return l.useEffect(()=>{e.focus(()=>{const a=document.activeElement,o=e.getRootElement();o===null||a!==null&&o.contains(a)||o.focus({preventScroll:!0})},{defaultSelection:t})},[t,e]),null}const Ys=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Ws({onClear:t}){const[e]=ve();return Ys(()=>Yo(e,t),[e,t]),null}const an=typeof window<"u"&&window.document!==void 0&&window.document.createElement!==void 0?l.useLayoutEffect:l.useEffect;function Xs({editor:t,ariaActiveDescendant:e,ariaAutoComplete:a,ariaControls:o,ariaDescribedBy:n,ariaErrorMessage:i,ariaExpanded:s,ariaInvalid:c,ariaLabel:w,ariaLabelledBy:d,ariaMultiline:u,ariaOwns:g,ariaRequired:m,autoCapitalize:p,className:f,id:y,role:b="textbox",spellCheck:I=!0,style:C,tabIndex:T,"data-testid":S,...N},E){const[z,j]=l.useState(t.isEditable()),x=l.useCallback(P=>{P&&P.ownerDocument&&P.ownerDocument.defaultView?t.setRootElement(P):t.setRootElement(null)},[t]),R=l.useMemo(()=>function(...P){return G=>{for(const K of P)typeof K=="function"?K(G):K!=null&&(K.current=G)}}(E,x),[x,E]);return an(()=>(j(t.isEditable()),t.registerEditableListener(P=>{j(P)})),[t]),r.jsx("div",{"aria-activedescendant":z?e:void 0,"aria-autocomplete":z?a:"none","aria-controls":z?o:void 0,"aria-describedby":n,...i!=null?{"aria-errormessage":i}:{},"aria-expanded":z&&b==="combobox"?!!s:void 0,...c!=null?{"aria-invalid":c}:{},"aria-label":w,"aria-labelledby":d,"aria-multiline":u,"aria-owns":z?g:void 0,"aria-readonly":!z||void 0,"aria-required":m,autoCapitalize:p,className:f,contentEditable:z,"data-testid":S,id:y,ref:R,role:b,spellCheck:I,style:C,tabIndex:T,...N})}const Zs=l.forwardRef(Xs);function wo(t){return t.getEditorState().read(en(t.isComposing()))}const Js=l.forwardRef(Qs);function Qs(t,e){const{placeholder:a,...o}=t,[n]=ve();return r.jsxs(r.Fragment,{children:[r.jsx(Zs,{editor:n,...o,ref:e}),a!=null&&r.jsx(tc,{editor:n,content:a})]})}function tc({content:t,editor:e}){const a=function(s){const[c,w]=l.useState(()=>wo(s));return an(()=>{function d(){const u=wo(s);w(u)}return d(),v.mergeRegister(s.registerUpdateListener(()=>{d()}),s.registerEditableListener(()=>{d()}))},[s]),c}(e),[o,n]=l.useState(e.isEditable());if(l.useLayoutEffect(()=>(n(e.isEditable()),e.registerEditableListener(s=>{n(s)})),[e]),!a)return null;let i=null;return typeof t=="function"?i=t(o):t!==null&&(i=t),i===null?null:r.jsx("div",{"aria-hidden":!0,children:i})}function ec({placeholder:t,className:e,placeholderClassName:a}){return r.jsx(Js,{className:e??"ContentEditable__root tw:relative tw:block tw:min-h-11 tw:overflow-auto tw:px-3 tw:py-3 tw:text-sm tw:outline-hidden","aria-placeholder":t,placeholder:r.jsx("div",{className:a??"tw:pointer-events-none tw:absolute tw:top-0 tw:select-none tw:overflow-hidden tw:text-ellipsis tw:px-3 tw:py-3 tw:text-sm tw:text-muted-foreground",children:t})})}const on=l.createContext(void 0);function rc({activeEditor:t,$updateToolbar:e,blockType:a,setBlockType:o,showModal:n,children:i}){const s=l.useMemo(()=>({activeEditor:t,$updateToolbar:e,blockType:a,setBlockType:o,showModal:n}),[t,e,a,o,n]);return r.jsx(on.Provider,{value:s,children:i})}function nn(){const t=l.useContext(on);if(!t)throw new Error("useToolbarContext must be used within a ToolbarContext provider");return t}function ac(){const[t,e]=l.useState(void 0),a=l.useCallback(()=>{e(void 0)},[]),o=l.useMemo(()=>{if(t===void 0)return;const{title:i,content:s}=t;return r.jsx(Er,{open:!0,onOpenChange:a,children:r.jsxs(Tr,{children:[r.jsx(Rr,{children:r.jsx(zr,{children:i})}),s]})})},[t,a]),n=l.useCallback((i,s,c=!1)=>{e({closeOnClickOutside:c,content:s(a),title:i})},[a]);return[o,n]}function oc({children:t}){const[e]=ve(),[a,o]=l.useState(e),[n,i]=l.useState("paragraph"),[s,c]=ac(),w=()=>{};return l.useEffect(()=>a.registerCommand(v.SELECTION_CHANGE_COMMAND,(d,u)=>(o(u),!1),v.COMMAND_PRIORITY_CRITICAL),[a]),r.jsxs(rc,{activeEditor:a,$updateToolbar:w,blockType:n,setBlockType:i,showModal:c,children:[s,t({blockType:n})]})}function nc(t){const[e]=ve(),{activeEditor:a}=nn();l.useEffect(()=>a.registerCommand(v.SELECTION_CHANGE_COMMAND,()=>{const o=v.$getSelection();return o&&t(o),!1},v.COMMAND_PRIORITY_CRITICAL),[e,t]),l.useEffect(()=>{a.getEditorState().read(()=>{const o=v.$getSelection();o&&t(o)})},[a,t])}const ic=ge.cva("pr-twp tw:group/toggle tw:inline-flex tw:items-center tw:justify-center tw:gap-1 tw:rounded-lg tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:hover:bg-muted tw:hover:text-foreground tw:focus-visible:border-ring tw:focus-visible:ring-[3px] tw:focus-visible:ring-ring/50 tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-destructive/20 tw:aria-pressed:bg-muted tw:data-[state=on]:bg-muted tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",{variants:{variant:{default:"tw:bg-transparent",outline:"tw:border tw:border-input tw:bg-transparent tw:hover:bg-muted"},size:{default:"tw:h-8 tw:min-w-8 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2",sm:"tw:h-7 tw:min-w-7 tw:rounded-[min(var(--tw-radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3.5",lg:"tw:h-9 tw:min-w-9 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2"}},defaultVariants:{variant:"default",size:"default"}}),sn=l.createContext({size:"default",variant:"default",spacing:0,orientation:"horizontal"});function Da({className:t,variant:e,size:a,spacing:o=0,orientation:n="horizontal",children:i,...s}){const c=ft();return r.jsx(k.ToggleGroup.Root,{"data-slot":"toggle-group","data-variant":e,"data-size":a,"data-spacing":o,"data-orientation":n,style:{"--gap":o},className:h("pr-twp tw:group/toggle-group tw:flex tw:w-fit tw:flex-row tw:items-center tw:gap-[--spacing(var(--gap))] tw:rounded-lg tw:data-[size=sm]:rounded-[min(var(--tw-radius-md),10px)] tw:data-vertical:flex-col tw:data-vertical:items-stretch",t),dir:c,...s,children:r.jsx(sn.Provider,{value:l.useMemo(()=>({variant:e,size:a,spacing:o,orientation:n}),[e,a,o,n]),children:i})})}function sr({className:t,children:e,variant:a="default",size:o="default",...n}){const i=l.useContext(sn);return r.jsx(k.ToggleGroup.Item,{"data-slot":"toggle-group-item","data-variant":i.variant||a,"data-size":i.size||o,"data-spacing":i.spacing,className:h("tw:shrink-0 tw:group-data-[spacing=0]/toggle-group:rounded-none tw:group-data-[spacing=0]/toggle-group:px-2 tw:focus:z-10 tw:focus-visible:z-10 tw:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pe-1.5 tw:group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:ps-1.5 tw:group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-s-lg tw:group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-lg tw:group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-e-lg tw:group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-lg tw:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-s-0 tw:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 tw:group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-s tw:group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",ic({variant:i.variant||a,size:i.size||o}),t),...n,children:e})}const uo=[{format:"bold",icon:$.BoldIcon,label:"Bold"},{format:"italic",icon:$.ItalicIcon,label:"Italic"}];function sc(){const{activeEditor:t}=nn(),[e,a]=l.useState([]),o=l.useCallback(n=>{if(v.$isRangeSelection(n)||vi.$isTableSelection(n)){const i=[];uo.forEach(({format:s})=>{n.hasFormat(s)&&i.push(s)}),a(s=>s.length!==i.length||!i.every(c=>s.includes(c))?i:s)}},[]);return nc(o),r.jsx(Da,{type:"multiple",value:e,onValueChange:a,variant:"outline",size:"sm",children:uo.map(({format:n,icon:i,label:s})=>r.jsx(sr,{value:n,"aria-label":s,onClick:()=>{t.dispatchCommand(v.FORMAT_TEXT_COMMAND,n)},children:r.jsx(i,{className:"tw:h-4 tw:w-4"})},n))})}function cc({onClear:t}){const[e]=ve();l.useEffect(()=>{t&&t(()=>{e.dispatchCommand(v.CLEAR_EDITOR_COMMAND,void 0)})},[e,t])}function lc({placeholder:t="Start typing ...",autoFocus:e=!1,onClear:a}){const[,o]=l.useState(void 0),n=i=>{i!==void 0&&o(i)};return r.jsxs("div",{className:"tw:relative",children:[r.jsx(oc,{children:()=>r.jsx("div",{className:"tw:sticky tw:top-0 tw:z-10 tw:flex tw:gap-2 tw:overflow-auto tw:border-b tw:p-1",children:r.jsx(sc,{})})}),r.jsxs("div",{className:"tw:relative",children:[r.jsx(Ks,{contentEditable:r.jsx("div",{ref:n,children:r.jsx(ec,{placeholder:t})}),ErrorBoundary:os}),e&&r.jsx(Hs,{defaultSelection:"rootEnd"}),r.jsx(cc,{onClear:a}),r.jsx(Ws,{})]})]})}const dc={namespace:"commentEditor",theme:Ca,nodes:Sa,onError:t=>{console.error(t)}};function $r({editorState:t,editorSerializedState:e,onChange:a,onSerializedChange:o,placeholder:n="Start typing…",autoFocus:i=!1,onClear:s,className:c}){return r.jsx("div",{className:h("pr-twp tw:overflow-hidden tw:rounded-lg tw:border tw:bg-background tw:shadow",c),children:r.jsx(Ji,{initialConfig:{...dc,...t?{editorState:t}:{},...e?{editorState:JSON.stringify(e)}:{}},children:r.jsxs(Ot,{children:[r.jsx(lc,{placeholder:n,autoFocus:i,onClear:s}),r.jsx(ts,{ignoreSelectionChange:!0,onChange:w=>{a==null||a(w),o==null||o(w.toJSON())}})]})})})}function wc(t,e){const a=v.isDOMDocumentNode(e)?e.body.childNodes:e.childNodes;let o=[];const n=[];for(const i of a)if(!ln.has(i.nodeName)){const s=dn(i,t,n,!1);s!==null&&(o=o.concat(s))}return function(i){for(const s of i)s.getNextSibling()instanceof v.ArtificialNode__DO_NOT_USE&&s.insertAfter(v.$createLineBreakNode());for(const s of i){const c=s.getChildren();for(const w of c)s.insertBefore(w);s.remove()}}(n),o}function uc(t,e){if(typeof document>"u"||typeof window>"u"&&global.window===void 0)throw new Error("To use $generateHtmlFromNodes in headless mode please initialize a headless browser implementation such as JSDom before calling this function.");const a=document.createElement("div"),o=v.$getRoot().getChildren();for(let n=0;n{const f=new v.ArtificialNode__DO_NOT_USE;return a.push(f),f}:v.$createParagraphNode)),c==null?m.length>0?s=s.concat(m):v.isBlockDomNode(t)&&function(f){return f.nextSibling==null||f.previousSibling==null?!1:v.isInlineDomNode(f.nextSibling)&&v.isInlineDomNode(f.previousSibling)}(t)&&(s=s.concat(v.$createLineBreakNode())):v.$isElementNode(c)&&c.append(...m),s}function pc(t,e,a){const o=t.style.textAlign,n=[];let i=[];for(let s=0;se&&"text"in e&&e.text.trim().length>0?!0:!e||!("children"in e)?!1:un(e.children)):!1}function Zt(t){var e;return(e=t==null?void 0:t.root)!=null&&e.children?un(t.root.children):!1}function gc(t){if(!t||t.trim()==="")throw new Error("Input HTML is empty");const e=No.createHeadlessEditor({namespace:"EditorUtils",theme:Ca,nodes:Sa,onError:o=>{console.error(o)}});let a;if(e.update(()=>{const n=new DOMParser().parseFromString(t,"text/html"),i=wc(e,n);v.$getRoot().clear(),v.$insertNodes(i)},{discrete:!0}),e.getEditorState().read(()=>{a=e.getEditorState().toJSON()}),!a)throw new Error("Failed to convert HTML to editor state");return a}function Ar(t){const e=No.createHeadlessEditor({namespace:"EditorUtils",theme:Ca,nodes:Sa,onError:n=>{console.error(n)}}),a=e.parseEditorState(JSON.stringify(t));e.setEditorState(a);let o="";return e.getEditorState().read(()=>{o=uc(e)}),o=o.replace(/\s+style="[^"]*"/g,"").replace(/\s+class="[^"]*"/g,"").replace(/(.*?)<\/span>/g,"$1").replace(/]*>(.*?)<\/strong><\/b>/g,"$1").replace(/]*>(.*?)<\/b><\/strong>/g,"$1").replace(/]*>(.*?)<\/em><\/i>/g,"$1").replace(/]*>(.*?)<\/i><\/em>/g,"$1").replace(/]*>(.*?)<\/span><\/u>/g,"$1").replace(/]*>(.*?)<\/span><\/s>/g,"$1").replace(//gi,"
"),o}function Ia(t){return["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(t.key)?(t.stopPropagation(),!0):!1}function $e({className:t,orientation:e="horizontal",decorative:a=!0,...o}){return r.jsx(k.Separator.Root,{"data-slot":"separator",decorative:a,orientation:e,className:h("pr-twp tw:shrink-0 tw:bg-border tw:data-horizontal:h-px tw:data-horizontal:w-full tw:data-vertical:w-px tw:data-vertical:self-stretch",t),...o})}const pn=ge.cva("tw:group/button-group tw:flex tw:w-fit tw:items-stretch tw:*:focus-visible:relative tw:*:focus-visible:z-10 tw:has-[>[data-slot=button-group]]:gap-2 tw:has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-e-lg tw:[&>[data-slot=select-trigger]:not([class*=w-])]:w-fit tw:[&>input]:flex-1",{variants:{orientation:{horizontal:"tw:[&>*:not(:first-child)]:rounded-s-none tw:[&>*:not(:first-child)]:border-s-0 tw:[&>*:not(:last-child)]:rounded-e-none tw:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-e-lg!",vertical:"tw:flex-col tw:[&>*:not(:first-child)]:rounded-t-none tw:[&>*:not(:first-child)]:border-t-0 tw:[&>*:not(:last-child)]:rounded-b-none tw:[&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-lg!"}},defaultVariants:{orientation:"horizontal"}});function Gr({className:t,orientation:e,...a}){return r.jsx("div",{role:"group","data-slot":"button-group","data-orientation":e,className:h("pr-twp",pn({orientation:e}),t),...a})}function hc({className:t,asChild:e=!1,...a}){const o=e?k.Slot.Root:"div";return r.jsx(o,{className:h("pr-twp tw:flex tw:items-center tw:gap-2 tw:rounded-lg tw:border tw:bg-muted tw:px-2.5 tw:text-sm tw:font-medium tw:[&_svg]:pointer-events-none tw:[&_svg:not([class*=size-])]:size-4",t),...a})}function Ma({className:t,orientation:e="vertical",...a}){return r.jsx($e,{"data-slot":"button-group-separator",orientation:e,className:h("pr-twp tw:relative tw:self-stretch tw:bg-input tw:data-horizontal:mx-px tw:data-horizontal:w-auto tw:data-vertical:my-px tw:data-vertical:h-auto",t),...a})}const Oa=Object.freeze(["%cancelButton_tooltip%","%acceptButton_tooltip%"]),po=(t,e)=>t[e]??e;function $a({onCancelClick:t,onAcceptClick:e,canAccept:a=!0,localizedStrings:o={},className:n="tw:h-6 tw:w-6",acceptLabel:i}){const s=po(o,"%cancelButton_tooltip%"),c=i??po(o,"%acceptButton_tooltip%");return r.jsxs(Gr,{children:[r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":s,className:n,size:"icon",onClick:t,variant:"secondary",children:r.jsx($.X,{})})}),r.jsx(Pt,{children:r.jsx("p",{children:s})})]})}),r.jsx(Ma,{}),r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":c,className:n,size:"icon",onClick:e,disabled:!a,children:r.jsx($.Check,{})})}),r.jsx(Pt,{children:r.jsx("p",{children:c})})]})})]})}function Sr(t,e){return t===""?e["%comment_assign_unassigned%"]??"Unassigned":t==="Team"?e["%comment_assign_team%"]??"Team":t}function Aa(t){const e=/Macintosh/i.test(navigator.userAgent);return t.key==="Enter"&&(e&&t.metaKey||!e&&t.ctrlKey)}const fc={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function ra(t,e){return t===""?e["%commentEditor_unassigned%"]??"Unassigned":t==="Team"?e["%commentEditor_team%"]??"Team":t}function mc({assignableUsers:t,onSave:e,onClose:a,localizedStrings:o,initialAssignedUser:n}){const[i,s]=l.useState(fc),[c,w]=l.useState(n),[d,u]=l.useState(!1),g=l.useRef(void 0),m=l.useRef(null);l.useEffect(()=>{let b=!0;const I=m.current;if(!I)return;const C=setTimeout(()=>{b&&wn(I)},300);return()=>{b=!1,clearTimeout(C)}},[]);const p=l.useCallback(()=>{if(!Zt(i))return;const b=Ar(i);e(b,c)},[i,e,c]),f=o["%commentEditor_placeholder%"]??"Type your comment here...",y=o["%commentEditor_assignTo_label%"]??"Assign to";return r.jsxs("div",{className:"pr-twp tw:grid tw:gap-3",children:[r.jsxs("div",{className:"tw:flex tw:items-center tw:justify-between",children:[r.jsx("span",{className:"tw:text-sm tw:font-medium",children:y}),r.jsx($a,{onCancelClick:a,onAcceptClick:p,canAccept:Zt(i),localizedStrings:o,acceptLabel:o["%commentEditor_saveButton_tooltip%"]})]}),r.jsx("div",{className:"tw:flex tw:items-center tw:gap-2",children:r.jsxs(se,{open:d,onOpenChange:u,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:"outline",className:"tw:flex tw:w-full tw:items-center tw:justify-start tw:gap-2",disabled:t.length===0,children:[r.jsx($.AtSign,{className:"tw:h-4 tw:w-4"}),r.jsx("span",{children:ra(c!==void 0?c:"",o)})]})}),r.jsx(ce,{className:"tw:w-auto tw:p-0",align:"start",onKeyDown:b=>{b.key==="Escape"&&(b.stopPropagation(),u(!1))},children:r.jsx(he,{children:r.jsx(fe,{children:t.map(b=>r.jsx(ie,{onSelect:()=>{w(b||void 0),u(!1)},className:"tw:flex tw:items-center",children:r.jsx("span",{children:ra(b,o)})},b||"unassigned"))})})})]})}),r.jsx("div",{ref:m,role:"textbox",tabIndex:-1,className:"tw:outline-hidden",onKeyDownCapture:b=>{b.key==="Escape"?(b.preventDefault(),b.stopPropagation(),a()):Aa(b)&&(b.preventDefault(),b.stopPropagation(),Zt(i)&&p())},onKeyDown:b=>{Ia(b),(b.key==="Enter"||b.key===" ")&&b.stopPropagation()},children:r.jsx($r,{editorSerializedState:i,onSerializedChange:b=>s(b),placeholder:f,onClear:b=>{g.current=b}})})]})}const vc=Object.freeze(["%commentEditor_placeholder%","%commentEditor_assignTo_label%","%commentEditor_saveButton_tooltip%","%commentEditor_unassigned%","%commentEditor_team%",...Oa]),bc=["%comment_assign_team%","%comment_assign_unassigned%","%comment_assigned_to%","%comment_assigning_to%","%comment_dateAtTime%","%comment_date_today%","%comment_date_yesterday%","%comment_deleteComment%","%comment_editComment%","%comment_replyOrAssign%","%comment_reopenResolved%","%comment_status_resolved%","%comment_status_todo%","%comment_thread_multiple_replies%","%comment_thread_single_reply%","%comment_aria_assign_user%","%comment_aria_submit_comment%","%comment_aria_mark_as_read%","%comment_aria_mark_as_unread%","%comment_aria_resolve_thread%"],xc=["input","select","textarea","button"],yc=["button","textbox"],gn=({options:t,onFocusChange:e,onOptionSelect:a,onCharacterPress:o})=>{const n=l.useRef(null),[i,s]=l.useState(void 0),[c,w]=l.useState(void 0),d=l.useCallback(p=>{s(p);const f=t.find(b=>b.id===p);f&&(e==null||e(f));const y=document.getElementById(p);y&&(y.scrollIntoView({block:"center"}),y.focus()),n.current&&n.current.setAttribute("aria-activedescendant",p)},[e,t]),u=l.useCallback(p=>{const f=t.find(y=>y.id===p);f&&(w(y=>y===p?void 0:p),a==null||a(f))},[a,t]),g=p=>{if(!p)return!1;const f=p.tagName.toLowerCase();if(p.isContentEditable||xc.includes(f))return!0;const y=p.getAttribute("role");if(y&&yc.includes(y))return!0;const b=p.getAttribute("tabindex");return b!==void 0&&b!=="-1"},m=l.useCallback(p=>{var z;const f=p.target,y=j=>j?document.getElementById(j):void 0,b=y(c),I=y(i);if(!!(b&&f&&b.contains(f)&&f!==b)&&g(f)){if(p.key==="Escape"||p.key==="ArrowLeft"&&!f.isContentEditable){if(c){p.preventDefault(),p.stopPropagation();const j=t.find(x=>x.id===c);j&&d(j.id)}return}if(p.key==="ArrowDown"||p.key==="ArrowUp"){if(!b)return;const j=Array.from(b.querySelectorAll('button:not([disabled]), input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled]), [href], [tabindex]:not([tabindex="-1"])'));if(j.length===0)return;const x=j.findIndex(P=>P===f);if(x===-1)return;let R;p.key==="ArrowDown"?R=Math.min(x+1,j.length-1):R=Math.max(x-1,0),R!==x&&(p.preventDefault(),p.stopPropagation(),(z=j[R])==null||z.focus());return}return}const S=t.findIndex(j=>j.id===i);let N=S;switch(p.key){case"ArrowDown":N=Math.min(S+1,t.length-1),p.preventDefault();break;case"ArrowUp":N=Math.max(S-1,0),p.preventDefault();break;case"Home":N=0,p.preventDefault();break;case"End":N=t.length-1,p.preventDefault();break;case" ":case"Enter":i&&u(i),p.preventDefault(),p.stopPropagation();return;case"ArrowRight":{const j=I;if(j){const x=j.querySelector('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])'),R=j.querySelector('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"]), [contenteditable="true"]'),P=x??R;if(P){p.preventDefault(),P.focus();return}}break}default:p.key.length===1&&!p.metaKey&&!p.ctrlKey&&!p.altKey&&(g(f)||(o==null||o(p.key),p.preventDefault()));return}const E=t[N];E&&d(E.id)},[t,d,i,c,u,o]);return{listboxRef:n,activeId:i,selectedId:c,handleKeyDown:m,focusOption:d}},hn=ge.cva("tw:group/badge tw:inline-flex tw:h-5 tw:w-fit tw:shrink-0 tw:items-center tw:justify-center tw:gap-1 tw:overflow-hidden tw:rounded-4xl tw:border tw:border-transparent tw:px-2 tw:py-0.5 tw:text-xs tw:font-medium tw:whitespace-nowrap tw:transition-all tw:focus-visible:border-ring tw:focus-visible:ring-[3px] tw:focus-visible:ring-ring/50 tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:aria-invalid:border-destructive tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:ring-destructive/40 tw:[&>svg]:pointer-events-none tw:[&>svg]:size-3!",{variants:{variant:{default:"tw:bg-primary tw:text-primary-foreground tw:[a]:hover:bg-primary/80",secondary:"tw:bg-secondary tw:text-secondary-foreground tw:[a]:hover:bg-secondary/80",destructive:"tw:bg-destructive/10 tw:text-destructive tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:focus-visible:ring-destructive/40 tw:[a]:hover:bg-destructive/20",outline:"tw:border-border tw:text-foreground tw:[a]:hover:bg-muted tw:[a]:hover:text-muted-foreground",ghost:"tw:hover:bg-muted tw:hover:text-muted-foreground tw:dark:hover:bg-muted/50",link:"tw:text-primary tw:underline-offset-4 tw:hover:underline",muted:"tw:border-transparent tw:bg-muted tw:text-muted-foreground tw:hover:bg-muted/80",blueIndicator:"tw:w-[5px] tw:h-[5px] tw:bg-blue-400 tw:px-0",mutedIndicator:"tw:w-[5px] tw:h-[5px] tw:bg-zinc-400 tw:px-0"}},defaultVariants:{variant:"default"}});function pe({className:t,variant:e="default",asChild:a=!1,...o}){const n=a?k.Slot.Root:"span";return r.jsx(n,{"data-slot":"badge","data-variant":e,className:h("pr-twp",hn({variant:e}),t),...o})}function fn({className:t,size:e="default",...a}){return r.jsx("div",{"data-slot":"card","data-size":e,className:h("pr-twp tw:group/card tw:flex tw:flex-col tw:gap-4 tw:overflow-hidden tw:rounded-xl tw:bg-card tw:py-4 tw:text-sm tw:text-card-foreground tw:ring-1 tw:ring-foreground/10 tw:has-data-[slot=card-footer]:pb-0 tw:has-[>img:first-child]:pt-0 tw:data-[size=sm]:gap-3 tw:data-[size=sm]:py-3 tw:data-[size=sm]:has-data-[slot=card-footer]:pb-0 tw:*:[img:first-child]:rounded-t-xl tw:*:[img:last-child]:rounded-b-xl",t),...a})}function kc({className:t,...e}){return r.jsx("div",{"data-slot":"card-header",className:h("pr-twp tw:group/card-header tw:@container/card-header tw:grid tw:auto-rows-min tw:items-start tw:gap-1 tw:rounded-t-xl tw:px-4 tw:group-data-[size=sm]/card:px-3 tw:has-data-[slot=card-action]:grid-cols-[1fr_auto] tw:has-data-[slot=card-description]:grid-rows-[auto_auto] tw:[.border-b]:pb-4 tw:group-data-[size=sm]/card:[.border-b]:pb-3",t),...e})}function jc({className:t,...e}){return r.jsx("div",{"data-slot":"card-title",className:h("pr-twp tw:font-heading tw:text-base tw:leading-snug tw:font-medium tw:group-data-[size=sm]/card:text-sm",t),...e})}function _c({className:t,...e}){return r.jsx("div",{"data-slot":"card-description",className:h("pr-twp tw:text-sm tw:text-muted-foreground",t),...e})}function mn({className:t,...e}){return r.jsx("div",{"data-slot":"card-content",className:h("pr-twp tw:px-4 tw:group-data-[size=sm]/card:px-3",t),...e})}function Nc({className:t,...e}){return r.jsx("div",{"data-slot":"card-footer",className:h("pr-twp tw:flex tw:items-center tw:rounded-b-xl tw:border-t tw:bg-muted/50 tw:p-4 tw:group-data-[size=sm]/card:p-3",t),...e})}function vn({className:t,size:e="default",...a}){return r.jsx(k.Avatar.Root,{"data-slot":"avatar","data-size":e,className:h("pr-twp tw:group/avatar tw:relative tw:flex tw:size-8 tw:shrink-0 tw:rounded-full tw:select-none tw:after:absolute tw:after:inset-0 tw:after:rounded-full tw:after:border tw:after:border-border tw:after:mix-blend-darken tw:data-[size=lg]:size-10 tw:data-[size=sm]:size-6 tw:dark:after:mix-blend-lighten",t),...a})}function Cc({className:t,...e}){return r.jsx(k.Avatar.Image,{"data-slot":"avatar-image",className:h("pr-twp tw:aspect-square tw:size-full tw:rounded-full tw:object-cover",t),...e})}function bn({className:t,...e}){return r.jsx(k.Avatar.Fallback,{"data-slot":"avatar-fallback",className:h("pr-twp tw:flex tw:size-full tw:items-center tw:justify-center tw:rounded-full tw:bg-muted tw:text-sm tw:text-muted-foreground tw:group-data-[size=sm]/avatar:text-xs",t),...e})}const Pa=l.createContext(void 0);function ke(){const t=l.useContext(Pa);if(!t)throw new Error("useMenuContext must be used within a MenuContext.Provider.");return t}const Ge=ge.cva("",{variants:{variant:{default:"",muted:"tw:hover:bg-muted tw:hover:text-foreground tw:focus:bg-muted tw:focus:text-foreground tw:data-[state=open]:bg-muted tw:data-[state=open]:text-foreground"}},defaultVariants:{variant:"default"}});function ae({variant:t="default",...e}){const a=ft(),o=l.useMemo(()=>({variant:t}),[t]);return r.jsx(Pa.Provider,{value:o,children:r.jsx(k.DropdownMenu.Root,{"data-slot":"dropdown-menu",dir:a,...e})})}function xn({...t}){return r.jsx(k.DropdownMenu.Portal,{"data-slot":"dropdown-menu-portal",...t})}function oe({...t}){return r.jsx(k.DropdownMenu.Trigger,{"data-slot":"dropdown-menu-trigger",...t})}function ne({className:t,align:e="start",sideOffset:a=4,children:o,...n}){const i=ft();return r.jsx(k.DropdownMenu.Portal,{children:r.jsx(k.DropdownMenu.Content,{"data-slot":"dropdown-menu-content",sideOffset:a,align:e,className:h("pr-twp tw:z-50 tw:max-h-(--radix-dropdown-menu-content-available-height) tw:min-w-32 tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-[state=closed]:overflow-hidden tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...n,children:r.jsx("div",{dir:i,children:o})})})}function La({...t}){return r.jsx(k.DropdownMenu.Group,{"data-slot":"dropdown-menu-group",...t})}function Se({className:t,inset:e,variant:a="default",...o}){const n=ft(),i=ke();return r.jsx(k.DropdownMenu.Item,{"data-slot":"dropdown-menu-item","data-inset":e,"data-variant":a,className:h("tw:group/dropdown-menu-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:dark:data-[variant=destructive]:focus:bg-destructive/20 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:data-[variant=destructive]:*:[svg]:text-destructive",t,Ge({variant:i.variant})),dir:n,...o})}function ee({className:t,children:e,checked:a,inset:o,...n}){const i=ft(),s=ke();return r.jsxs(k.DropdownMenu.CheckboxItem,{"data-slot":"dropdown-menu-checkbox-item","data-inset":o,className:h("tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:s.variant})),checked:a,dir:i,...n,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:items-center tw:justify-center","data-slot":"dropdown-menu-checkbox-item-indicator",children:r.jsx(k.DropdownMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function yn({...t}){return r.jsx(k.DropdownMenu.RadioGroup,{"data-slot":"dropdown-menu-radio-group",...t})}function kn({className:t,children:e,inset:a,...o}){const n=ft(),i=ke();return r.jsxs(k.DropdownMenu.RadioItem,{"data-slot":"dropdown-menu-radio-item","data-inset":a,className:h("tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:i.variant})),dir:n,...o,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:items-center tw:justify-center","data-slot":"dropdown-menu-radio-item-indicator",children:r.jsx(k.DropdownMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function Ee({className:t,inset:e,...a}){return r.jsx(k.DropdownMenu.Label,{"data-slot":"dropdown-menu-label","data-inset":e,className:h("tw:px-1.5 tw:py-1 tw:text-xs tw:font-medium tw:text-muted-foreground tw:data-inset:ps-7",t),...a})}function ye({className:t,...e}){return r.jsx(k.DropdownMenu.Separator,{"data-slot":"dropdown-menu-separator",className:h("tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function Sc({className:t,...e}){return r.jsx("span",{"data-slot":"dropdown-menu-shortcut",className:h("tw:ms-auto tw:text-xs tw:tracking-widest tw:text-muted-foreground tw:group-focus/dropdown-menu-item:text-accent-foreground",t),...e})}function jn({...t}){return r.jsx(k.DropdownMenu.Sub,{"data-slot":"dropdown-menu-sub",...t})}function _n({className:t,inset:e,children:a,...o}){const n=ke();return r.jsxs(k.DropdownMenu.SubTrigger,{"data-slot":"dropdown-menu-sub-trigger","data-inset":e,className:h("tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-open:bg-accent tw:data-open:text-accent-foreground tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t,Ge({variant:n.variant})),...o,children:[a,r.jsx(ht.IconChevronRight,{className:"tw:ms-auto"})]})}function Nn({className:t,children:e,...a}){const o=ft();return r.jsx(k.DropdownMenu.SubContent,{"data-slot":"dropdown-menu-sub-content",className:h("pr-twp tw:z-50 tw:min-w-[96px] tw:origin-(--radix-dropdown-menu-content-transform-origin) tw:overflow-hidden tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-lg tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...a,children:r.jsx("div",{dir:o,children:e})})}function go({comment:t,isReply:e=!1,localizedStrings:a,isThreadExpanded:o=!1,handleUpdateComment:n,handleDeleteComment:i,onEditingChange:s,canEditOrDelete:c=!1}){const[w,d]=l.useState(!1),[u,g]=l.useState(),m=l.useRef(null);l.useEffect(()=>{if(!w)return;let S=!0;const N=m.current;if(!N)return;const E=setTimeout(()=>{S&&wn(N)},300);return()=>{S=!1,clearTimeout(E)}},[w]);const p=l.useCallback(S=>{S&&S.stopPropagation(),d(!1),g(void 0),s==null||s(!1)},[s]),f=l.useCallback(async S=>{if(S&&S.stopPropagation(),!u||!n)return;await n(t.id,Ar(u))&&(d(!1),g(void 0),s==null||s(!1))},[u,n,t.id,s]),y=l.useMemo(()=>{const S=new Date(t.date),N=D.formatRelativeDate(S,a["%comment_date_today%"],a["%comment_date_yesterday%"]),E=S.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"});return D.formatReplacementString(a["%comment_dateAtTime%"],{date:N,time:E})},[t.date,a]),b=l.useMemo(()=>t.user,[t.user]),I=l.useMemo(()=>t.user.split(" ").map(S=>S[0]).join("").toUpperCase().slice(0,2),[t.user]),C=l.useMemo(()=>D.sanitizeHtml(t.contents),[t.contents]),T=l.useMemo(()=>{if(o&&c)return r.jsxs(r.Fragment,{children:[r.jsxs(Se,{onClick:S=>{S.stopPropagation(),d(!0),g(gc(t.contents)),s==null||s(!0)},children:[r.jsx($.Pencil,{className:"tw:me-2 tw:h-4 tw:w-4"}),a["%comment_editComment%"]]}),r.jsxs(Se,{onClick:async S=>{S.stopPropagation(),i&&await i(t.id)},children:[r.jsx($.Trash2,{className:"tw:me-2 tw:h-4 tw:w-4"}),a["%comment_deleteComment%"]]})]})},[c,o,a,t.contents,t.id,i,s]);return r.jsxs("div",{className:h("tw:flex tw:w-full tw:flex-row tw:items-baseline tw:gap-3 tw:space-y-3",{"tw:text-sm":e}),children:[r.jsx(vn,{className:"tw:h-8 tw:w-8",children:r.jsx(bn,{className:"tw:text-xs tw:font-medium",children:I})}),r.jsxs("div",{className:"tw:flex tw:flex-1 tw:flex-col tw:gap-2",children:[r.jsxs("div",{className:"tw:flex tw:w-full tw:flex-row tw:flex-wrap tw:items-baseline tw:gap-x-2",children:[r.jsx("p",{className:"tw:text-sm tw:font-medium",children:b}),r.jsx("p",{className:"tw:text-xs tw:font-normal tw:text-muted-foreground",children:y}),r.jsx("div",{className:"tw:flex-1"}),e&&t.assignedUser!==void 0&&r.jsxs(pe,{variant:"secondary",className:"tw:text-xs tw:font-normal",children:["→ ",Sr(t.assignedUser,a)]})]}),w&&r.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw:flex tw:flex-col tw:gap-2",ref:m,onKeyDownCapture:S=>{S.key==="Escape"?(S.preventDefault(),S.stopPropagation(),p()):Aa(S)&&(S.preventDefault(),S.stopPropagation(),Zt(u)&&f())},onKeyDown:S=>{Ia(S),(S.key==="Enter"||S.key===" ")&&S.stopPropagation()},onClick:S=>{S.stopPropagation()},children:[r.jsx($r,{className:h('tw:[&_[data-lexical-editor="true"]>blockquote]:mt-0 tw:[&_[data-lexical-editor="true"]>blockquote]:border-s-0 tw:[&_[data-lexical-editor="true"]>blockquote]:ps-0 tw:[&_[data-lexical-editor="true"]>blockquote]:font-normal tw:[&_[data-lexical-editor="true"]>blockquote]:not-italic tw:[&_[data-lexical-editor="true"]>blockquote]:text-foreground'),editorSerializedState:u,onSerializedChange:S=>g(S)}),r.jsxs("div",{className:"tw:flex tw:flex-row tw:items-start tw:justify-end tw:gap-2",children:[r.jsx(F,{size:"icon",onClick:p,variant:"outline",className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",children:r.jsx($.X,{})}),r.jsx(F,{size:"icon",onClick:f,className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!Zt(u),children:r.jsx($.ArrowUp,{})})]})]}),!w&&r.jsxs(r.Fragment,{children:[t.status==="Resolved"&&r.jsx("div",{className:"tw:text-sm tw:italic",children:a["%comment_status_resolved%"]}),t.status==="Todo"&&e&&r.jsx("div",{className:"tw:text-sm tw:italic",children:a["%comment_status_todo%"]}),r.jsx("div",{className:h("tw:prose tw:items-start tw:gap-2 tw:break-words tw:text-sm tw:font-normal tw:text-foreground","tw:max-w-none","tw:[&>blockquote]:border-s-0 tw:[&>blockquote]:p-0 tw:[&>blockquote]:ps-0 tw:[&>blockquote]:font-normal tw:[&>blockquote]:not-italic tw:[&>blockquote]:text-foreground","tw:prose-quoteless",{"tw:line-clamp-3":!o}),dangerouslySetInnerHTML:{__html:C}})]})]}),T&&r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsx(F,{variant:"ghost",size:"icon",children:r.jsx($.MoreHorizontal,{})})}),r.jsx(ne,{align:"end",children:T})]})]})}const ho={root:{children:[{children:[{detail:0,format:0,mode:"normal",style:"",text:"",type:"text",version:1}],direction:"ltr",format:"",indent:0,type:"paragraph",version:1,textFormat:0,textStyle:""}],direction:"ltr",format:"",indent:0,type:"root",version:1}};function Ec({classNameForVerseText:t,comments:e,localizedStrings:a,isSelected:o=!1,verseRef:n,assignedUser:i,currentUser:s,handleSelectThread:c,threadId:w,thread:d,threadStatus:u,handleAddCommentToThread:g,handleUpdateComment:m,handleDeleteComment:p,handleReadStatusChange:f,assignableUsers:y,canUserAddCommentToThread:b,canUserAssignThreadCallback:I,canUserResolveThreadCallback:C,canUserEditOrDeleteCommentCallback:T,isRead:S=!1,autoReadDelay:N=5,onVerseRefClick:E,initialAssignedUser:z}){const[j,x]=l.useState(ho),[R,P]=l.useState(),[G,K]=l.useState(),V=o,[q,M]=l.useState(!1),[Y,lt]=l.useState(!1),[kt,St]=l.useState(!1),[J,Et]=l.useState(!1),[U,tt]=l.useState(!1),[rt,at]=l.useState(S),[ot,Lt]=l.useState(!1),gt=l.useRef(void 0),[Ft,Gt]=l.useState(new Map);l.useEffect(()=>{let O=!0;return(async()=>{const dt=C?await C(w):!1;O&&tt(dt)})(),()=>{O=!1}},[w,C]),l.useEffect(()=>{let O=!0;if(!o){Et(!1),Gt(new Map);return}return(async()=>{const dt=I?await I(w):!1;O&&Et(dt)})(),()=>{O=!1}},[o,w,I]);const A=l.useRef("idle");l.useEffect(()=>{if(!o){A.current!=="idle"&&(P(void 0),K(void 0),A.current="idle");return}A.current==="idle"&&(A.current="pending"),J?A.current==="pending"&&z!==void 0&&z!==i&&(P(z),A.current="auto-populated"):A.current==="auto-populated"&&(P(void 0),A.current="pending")},[o,z,J,i]);const Tt=l.useMemo(()=>e.filter(O=>!O.deleted),[e]);l.useEffect(()=>{let O=!0;if(!o||!T){Gt(new Map);return}return(async()=>{const dt=new Map;await Promise.all(Tt.map(async bt=>{const Re=await T(bt.id);O&&dt.set(bt.id,Re)})),O&&Gt(dt)})(),()=>{O=!1}},[o,Tt,T]);const Bt=l.useMemo(()=>Tt[0],[Tt]),Qt=l.useRef(null),Ut=l.useRef(void 0),Rt=l.useCallback(()=>{var O;(O=Ut.current)==null||O.call(Ut),x(ho)},[]),je=l.useCallback(()=>{const O=!rt;at(O),Lt(!O),f==null||f(w,O)},[rt,f,w]);l.useEffect(()=>{M(!1)},[o]),l.useEffect(()=>{if(o&&!rt&&!ot){const O=setTimeout(()=>{at(!0),f==null||f(w,!0)},N*1e3);return gt.current=O,()=>clearTimeout(O)}gt.current&&(clearTimeout(gt.current),gt.current=void 0)},[o,rt,ot,N,w,f]);const Kt=l.useMemo(()=>({singleReply:a["%comment_thread_single_reply%"],multipleReplies:a["%comment_thread_multiple_replies%"]}),[a]),qt=l.useMemo(()=>{if(i===void 0)return;if(i==="")return a["%comment_assign_unassigned%"]??"Unassigned";const O=Sr(i,a);return D.formatReplacementString(a["%comment_assigned_to%"],{assignedUser:O})},[i,a]),B=l.useMemo(()=>Tt.slice(1),[Tt]),W=l.useMemo(()=>B.length??0,[B.length]),nt=l.useMemo(()=>W>0,[W]),et=l.useMemo(()=>q||W<=2?B:B.slice(-2),[B,W,q]),wt=l.useMemo(()=>q||W<=2?0:W-2,[W,q]),mt=l.useMemo(()=>W===1?Kt.singleReply:D.formatReplacementString(Kt.multipleReplies,{count:W}),[W,Kt]),vt=l.useMemo(()=>wt===1?Kt.singleReply:D.formatReplacementString(Kt.multipleReplies,{count:wt}),[wt,Kt]);l.useEffect(()=>{!o&&Y&&nt&<(!1)},[o,Y,nt]);const ut=l.useCallback(async O=>{O&&O.stopPropagation();const ct=Zt(j)?Ar(j):void 0;if(R!==void 0){await g({threadId:w,contents:ct,assignedUser:R})&&(K(R),ct&&Rt());return}ct&&await g({threadId:w,contents:ct})&&Rt()},[Rt,j,g,R,w]),jt=l.useCallback(async O=>{const ct=Zt(j)?Ar(j):void 0,dt=O.status?O.assignedUser:R??O.assignedUser,bt=await g({...O,contents:ct,assignedUser:dt});return bt&&(dt!==void 0&&K(dt),ct&&Rt()),bt},[Rt,j,g,R]);if(Tt.length!==0)return r.jsx(fn,{role:"option","aria-selected":o,id:w,className:h("tw:group tw:w-full tw:rounded-none tw:border-none tw:p-4 tw:outline-hidden tw:transition-all tw:duration-200 tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",{"tw:cursor-pointer tw:hover:shadow-md":!o},{"tw:bg-primary-foreground":!o&&u!=="Resolved"&&rt,"tw:bg-background":o&&u!=="Resolved"&&rt,"tw:bg-muted":u==="Resolved","tw:bg-accent":!rt&&!o&&u!=="Resolved"}),onClick:()=>{c(w)},tabIndex:-1,children:r.jsxs(mn,{className:"tw:flex tw:flex-col tw:gap-2 tw:p-0",children:[r.jsxs("div",{className:"tw:flex tw:flex-col tw:content-center tw:items-start tw:gap-4",children:[r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-2",children:[qt&&r.jsx(pe,{className:"tw:rounded-sm tw:bg-input tw:text-sm tw:font-normal tw:text-primary tw:hover:bg-input",children:qt}),r.jsx(F,{variant:"ghost",size:"icon",onClick:O=>{O.stopPropagation(),je()},className:"tw:text-muted-foreground tw:transition tw:hover:text-foreground","aria-label":rt?a["%comment_aria_mark_as_unread%"]??"Mark as unread":a["%comment_aria_mark_as_read%"]??"Mark as read",children:rt?r.jsx($.MailOpen,{}):r.jsx($.Mail,{})}),U&&u!=="Resolved"&&r.jsx(F,{variant:"ghost",size:"icon",className:h("tw:ms-auto","tw:text-primary tw:transition-opacity tw:duration-200 tw:hover:bg-primary/10","tw:opacity-0 tw:group-hover:opacity-100"),onClick:O=>{O.stopPropagation(),jt({threadId:w,status:"Resolved"})},"aria-label":a["%comment_aria_resolve_thread%"]??"Resolve thread",children:r.jsx($.Check,{className:"tw:h-4 tw:w-4"})})]}),r.jsx("div",{className:"tw:flex tw:max-w-full tw:flex-wrap tw:items-baseline tw:gap-2",children:r.jsxs("p",{ref:Qt,className:h("tw:flex-1 tw:overflow-hidden tw:text-ellipsis tw:text-sm tw:font-normal tw:text-muted-foreground",{"tw:overflow-visible tw:text-clip tw:whitespace-normal tw:break-words":V},{"tw:whitespace-nowrap":!V}),children:[n&&E?r.jsx(F,{variant:"ghost",size:"sm",className:"tw:h-auto tw:px-1 tw:py-0 tw:text-sm tw:font-normal tw:text-muted-foreground",onClick:O=>{O.stopPropagation(),E(d)},children:n}):n,r.jsxs("span",{className:t,children:[Bt.contextBefore,r.jsx("span",{className:"tw:font-bold",children:Bt.selectedText}),Bt.contextAfter]})]})}),r.jsx(go,{comment:Bt,localizedStrings:a,isThreadExpanded:o,threadStatus:u,handleAddCommentToThread:jt,handleUpdateComment:m,handleDeleteComment:p,onEditingChange:lt,canEditOrDelete:(!Y&&Ft.get(Bt.id))??!1,canUserResolveThread:U})]}),r.jsxs(r.Fragment,{children:[nt&&!o&&r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-5",children:[r.jsx("div",{className:"tw:w-8",children:r.jsx($e,{})}),r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:mt})]}),!o&&Zt(j)&&r.jsx($r,{editorSerializedState:j,onSerializedChange:O=>x(O),placeholder:a["%comment_replyOrAssign%"]}),o&&r.jsxs(r.Fragment,{children:[wt>0&&r.jsxs("div",{className:"tw:flex tw:cursor-pointer tw:items-center tw:gap-5 tw:py-2",onClick:O=>{O.stopPropagation(),M(!0)},role:"button",tabIndex:0,onKeyDown:O=>{(O.key==="Enter"||O.key===" ")&&(O.preventDefault(),O.stopPropagation(),M(!0))},children:[r.jsx("div",{className:"tw:w-8",children:r.jsx($e,{})}),r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-2",children:[r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:vt}),q?r.jsx($.ChevronUp,{}):r.jsx($.ChevronDown,{})]})]}),et.map(O=>r.jsx("div",{children:r.jsx(go,{comment:O,localizedStrings:a,isReply:!0,isThreadExpanded:o,handleUpdateComment:m,handleDeleteComment:p,onEditingChange:lt,canEditOrDelete:(!Y&&Ft.get(O.id))??!1})},O.id)),b!==!1&&(!Y||Zt(j))&&r.jsxs("div",{role:"textbox",tabIndex:-1,className:"tw:w-full tw:space-y-2",onClick:O=>O.stopPropagation(),onKeyDownCapture:O=>{Aa(O)&&(O.preventDefault(),O.stopPropagation(),(Zt(j)||R!==void 0&&R!==G)&&ut())},onKeyDown:O=>{Ia(O),(O.key==="Enter"||O.key===" ")&&O.stopPropagation()},children:[r.jsx($r,{editorSerializedState:j,onSerializedChange:O=>x(O),placeholder:u==="Resolved"?a["%comment_reopenResolved%"]:a["%comment_replyOrAssign%"],autoFocus:!0,onClear:O=>{Ut.current=O}}),r.jsxs("div",{className:"tw:flex tw:flex-row tw:items-center tw:justify-end tw:gap-2",children:[R!==void 0&&(Zt(j)||R!==G)&&r.jsx("span",{className:"tw:flex-1 tw:text-sm tw:text-muted-foreground",children:D.formatReplacementString(a["%comment_assigning_to%"]??"Assigning to: {assignedUser}",{assignedUser:Sr(R,a)})}),r.jsxs(se,{open:kt,onOpenChange:St,children:[r.jsx(me,{asChild:!0,children:r.jsx(F,{size:"icon",variant:"outline",className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!J||!y||y.length===0||!y.includes(s),"aria-label":a["%comment_aria_assign_user%"]??"Assign user",children:r.jsx($.AtSign,{})})}),r.jsx(ce,{className:"tw:w-auto tw:p-0",align:"end",onKeyDown:O=>{O.key==="Escape"&&(O.stopPropagation(),St(!1))},children:r.jsx(he,{children:r.jsx(fe,{children:y==null?void 0:y.map(O=>r.jsx(ie,{onSelect:()=>{P(O!==i?O:void 0),A.current="user-selected",K(void 0),St(!1)},className:"tw:flex tw:items-center",children:r.jsx("span",{children:Sr(O,a)})},O||"unassigned"))})})})]}),r.jsx(F,{size:"icon",onClick:ut,className:"tw:flex tw:items-center tw:justify-center tw:rounded-md",disabled:!Zt(j)&&(R===void 0||R===G),"aria-label":a["%comment_aria_submit_comment%"]??"Submit comment",children:r.jsx($.ArrowUp,{})})]})]})]})]})]})})}function Tc({className:t="",classNameForVerseText:e,threads:a,currentUser:o,localizedStrings:n,handleAddCommentToThread:i,handleUpdateComment:s,handleDeleteComment:c,handleReadStatusChange:w,assignableUsers:d,canUserAddCommentToThread:u,canUserAssignThreadCallback:g,canUserResolveThreadCallback:m,canUserEditOrDeleteCommentCallback:p,selectedThreadId:f,onSelectedThreadChange:y,onVerseRefClick:b}){const[I,C]=l.useState(new Set),[T,S]=l.useState(),[N,E]=l.useState(),z=l.useCallback(async M=>{const Y=await i(M);return Y!==void 0&&M.assignedUser!==void 0&&M.assignedUser!==""&&E(M.assignedUser),Y},[i]);l.useEffect(()=>{f&&(C(M=>new Set(M).add(f)),S(f))},[f]);const j=a.filter(M=>M.comments.some(Y=>!Y.deleted)),x=j.map(M=>({id:M.id})),R=l.useCallback(M=>{C(Y=>new Set(Y).add(M.id)),S(M.id),y==null||y(M.id)},[y]),P=l.useCallback(M=>{const Y=I.has(M);C(lt=>{const kt=new Set(lt);return kt.has(M)?kt.delete(M):kt.add(M),kt}),S(M),y==null||y(Y?void 0:M)},[I,y]),{listboxRef:G,activeId:K,handleKeyDown:V}=gn({options:x,onOptionSelect:R}),q=l.useCallback(M=>{M.key==="Escape"?(T&&I.has(T)&&(C(Y=>{const lt=new Set(Y);return lt.delete(T),lt}),S(void 0),y==null||y(void 0)),M.preventDefault(),M.stopPropagation()):V(M)},[T,I,V,y]);return r.jsx("div",{id:"comment-list",role:"listbox",tabIndex:0,ref:G,"aria-activedescendant":K??void 0,"aria-label":"Comments",className:h("tw:flex tw:w-full tw:flex-col tw:space-y-3 tw:outline-hidden tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",t),onKeyDown:q,children:j.map(M=>r.jsx("div",{className:h({"tw:opacity-60":M.status==="Resolved"}),children:r.jsx(Ec,{classNameForVerseText:e,comments:M.comments,localizedStrings:n,verseRef:M.verseRef,handleSelectThread:P,threadId:M.id,thread:M,isRead:M.isRead,isSelected:I.has(M.id),currentUser:o,assignedUser:M.assignedUser,threadStatus:M.status,handleAddCommentToThread:z,handleUpdateComment:s,handleDeleteComment:c,handleReadStatusChange:w,assignableUsers:d,canUserAddCommentToThread:u,canUserAssignThreadCallback:g,canUserResolveThreadCallback:m,canUserEditOrDeleteCommentCallback:p,onVerseRefClick:b,initialAssignedUser:N})},M.id))})}function Rc({table:t}){return r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsxs(F,{variant:"outline",size:"sm",className:"tw:ml-auto tw:hidden tw:h-8 tw:lg:flex",children:[r.jsx($.FilterIcon,{className:"tw:mr-2 tw:h-4 tw:w-4"}),"View"]})}),r.jsxs(ne,{align:"end",className:"tw:w-[150px]",children:[r.jsx(Ee,{children:"Toggle columns"}),r.jsx(ye,{}),t.getAllColumns().filter(e=>e.getCanHide()).map(e=>r.jsx(ee,{className:"tw:capitalize",checked:e.getIsVisible(),onCheckedChange:a=>e.toggleVisibility(!!a),children:e.id},e.id))]})]})}function Ae({...t}){return r.jsx(k.Select.Root,{"data-slot":"select",...t})}function Cn({className:t,...e}){return r.jsx(k.Select.Group,{"data-slot":"select-group",className:h("tw:scroll-my-1 tw:p-1",t),...e})}function Pe({...t}){return r.jsx(k.Select.Value,{"data-slot":"select-value",...t})}function Le({className:t,size:e="default",children:a,...o}){const n=ft();return r.jsxs(k.Select.Trigger,{"data-slot":"select-trigger","data-size":e,className:h("pr-twp tw:flex tw:w-fit tw:items-center tw:gap-2 tw:rounded-lg tw:border tw:border-input tw:bg-transparent tw:py-2 tw:pe-2 tw:ps-2.5 tw:text-sm tw:whitespace-nowrap tw:transition-colors tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:data-placeholder:text-muted-foreground tw:data-[size=default]:h-8 tw:data-[size=sm]:h-7 tw:data-[size=sm]:rounded-[min(var(--tw-radius-md),10px)] tw:*:data-[slot=select-value]:line-clamp-1 tw:*:data-[slot=select-value]:flex tw:*:data-[slot=select-value]:flex-1 tw:*:data-[slot=select-value]:items-center tw:*:data-[slot=select-value]:gap-1.5 tw:*:data-[slot=select-value]:text-start tw:dark:bg-input/30 tw:dark:hover:bg-input/50 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t),dir:n,...o,children:[a,r.jsx(k.Select.Icon,{asChild:!0,children:r.jsx(ht.IconSelector,{className:"tw:pointer-events-none tw:size-4 tw:text-muted-foreground"})})]})}function Be({className:t,children:e,position:a="popper",align:o="center",...n}){const i=ft();return r.jsx(k.Select.Portal,{children:r.jsxs(k.Select.Content,{"data-slot":"select-content","data-align-trigger":a==="item-aligned",className:h("pr-twp tw:relative tw:z-50 tw:max-h-(--radix-select-content-available-height) tw:data-[align-trigger=true]:min-w-(--radix-select-trigger-width) tw:data-[align-trigger=false]:min-w-36 tw:origin-(--radix-select-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-lg tw:bg-popover tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[align-trigger=true]:animate-none tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",a==="popper"&&"tw:data-[side=bottom]:translate-y-1 tw:data-[side=left]:-translate-x-1 tw:rtl:data-[side=left]:translate-x-1 tw:data-[side=right]:translate-x-1 tw:rtl:data-[side=right]:-translate-x-1 tw:data-[side=top]:-translate-y-1",t),position:a,align:o,...n,children:[r.jsx(Sn,{}),r.jsx(k.Select.Viewport,{"data-position":a,className:h("tw:data-[position=popper]:h-(--radix-select-trigger-height) tw:data-[position=popper]:w-full tw:data-[position=popper]:min-w-(--radix-select-trigger-width)",a==="popper"&&"tw:"),children:r.jsx("div",{dir:i,children:e})}),r.jsx(En,{})]})})}function zc({className:t,...e}){return r.jsx(k.Select.Label,{"data-slot":"select-label",className:h("pr-twp tw:px-1.5 tw:py-1 tw:text-xs tw:text-muted-foreground",t),...e})}function Jt({className:t,children:e,...a}){return r.jsxs(k.Select.Item,{"data-slot":"select-item",className:h("pr-twp tw:relative tw:flex tw:w-full tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:*:[span]:last:flex tw:*:[span]:last:items-center tw:*:[span]:last:gap-2",t),...a,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2 tw:flex tw:size-4 tw:items-center tw:justify-center",children:r.jsx(k.Select.ItemIndicator,{children:r.jsx(ht.IconCheck,{className:"tw:pointer-events-none"})})}),r.jsx(k.Select.ItemText,{children:e})]})}function Dc({className:t,...e}){return r.jsx(k.Select.Separator,{"data-slot":"select-separator",className:h("pr-twp tw:pointer-events-none tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function Sn({className:t,...e}){return r.jsx(k.Select.ScrollUpButton,{"data-slot":"select-scroll-up-button",className:h("pr-twp tw:z-10 tw:flex tw:cursor-default tw:items-center tw:justify-center tw:bg-popover tw:py-1 tw:[&_svg:not([class*=size-])]:size-4",t),...e,children:r.jsx(ht.IconChevronUp,{})})}function En({className:t,...e}){return r.jsx(k.Select.ScrollDownButton,{"data-slot":"select-scroll-down-button",className:h("pr-twp tw:z-10 tw:flex tw:cursor-default tw:items-center tw:justify-center tw:bg-popover tw:py-1 tw:[&_svg:not([class*=size-])]:size-4",t),...e,children:r.jsx(ht.IconChevronDown,{})})}function Ic({table:t}){return r.jsx("div",{className:"tw:flex tw:items-center tw:justify-between tw:px-2 tw:pb-3 tw:pt-3",children:r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-6 tw:lg:space-x-8",children:[r.jsxs("div",{className:"tw:flex-1 tw:text-sm tw:text-muted-foreground",children:[t.getFilteredSelectedRowModel().rows.length," of"," ",t.getFilteredRowModel().rows.length," row(s) selected"]}),r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-2",children:[r.jsx("p",{className:"tw:text-nowrap tw:text-sm tw:font-medium",children:"Rows per page"}),r.jsxs(Ae,{value:`${t.getState().pagination.pageSize}`,onValueChange:e=>{t.setPageSize(Number(e))},children:[r.jsx(Le,{className:"tw:h-8 tw:w-[70px]",children:r.jsx(Pe,{placeholder:t.getState().pagination.pageSize})}),r.jsx(Be,{side:"top",children:[10,20,30,40,50].map(e=>r.jsx(Jt,{value:`${e}`,children:e},e))})]})]}),r.jsxs("div",{className:"tw:flex tw:w-[100px] tw:items-center tw:justify-center tw:text-sm tw:font-medium",children:["Page ",t.getState().pagination.pageIndex+1," of ",t.getPageCount()]}),r.jsxs("div",{className:"tw:flex tw:items-center tw:space-x-2",children:[r.jsxs(F,{variant:"outline",size:"icon",className:"tw:hidden tw:h-8 tw:w-8 tw:p-0 tw:lg:flex",onClick:()=>t.setPageIndex(0),disabled:!t.getCanPreviousPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to first page"}),r.jsx($.ArrowLeftIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:h-8 tw:w-8 tw:p-0",onClick:()=>t.previousPage(),disabled:!t.getCanPreviousPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to previous page"}),r.jsx($.ChevronLeftIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:h-8 tw:w-8 tw:p-0",onClick:()=>t.nextPage(),disabled:!t.getCanNextPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to next page"}),r.jsx($.ChevronRightIcon,{className:"tw:h-4 tw:w-4"})]}),r.jsxs(F,{variant:"outline",size:"icon",className:"tw:hidden tw:h-8 tw:w-8 tw:p-0 tw:lg:flex",onClick:()=>t.setPageIndex(t.getPageCount()-1),disabled:!t.getCanNextPage(),children:[r.jsx("span",{className:"tw:sr-only",children:"Go to last page"}),r.jsx($.ArrowRightIcon,{className:"tw:h-4 tw:w-4"})]})]})]})})}const fo=` a[href], area[href], input:not([disabled]), @@ -11,7 +11,4444 @@ embed, [contenteditable], tr:not([disabled]) -`;function Mc(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function lr(t,e){const a=e?`${fo}, ${e}`:fo;return Array.from(t.querySelectorAll(a)).filter(o=>!o.hasAttribute("disabled")&&!o.getAttribute("aria-hidden")&&Mc(o))}function Ur({className:t,stickyHeader:e,ref:a,...o}){const n=l.useRef(null);l.useEffect(()=>{typeof a=="function"?a(n.current):a&&"current"in a&&(a.current=n.current)},[a]),l.useEffect(()=>{const s=n.current;if(!s)return;const c=()=>{requestAnimationFrame(()=>{lr(s,'[tabindex]:not([tabindex="-1"])').forEach(u=>{u.setAttribute("tabindex","-1")})})};c();const w=new MutationObserver(()=>{c()});return w.observe(s,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["tabindex"]}),()=>{w.disconnect()}},[]);const i=s=>{const{current:c}=n;if(c){if(s.key==="ArrowDown"){s.preventDefault(),lr(c)[0].focus();return}s.key===" "&&document.activeElement===c&&s.preventDefault()}};return r.jsx("div",{"data-slot":"table-container",className:h("pr-twp tw:relative tw:w-full",{"tw:p-1":e}),children:r.jsx("table",{"data-slot":"table",tabIndex:0,ref:n,onKeyDown:i,className:h("tw:w-full tw:caption-bottom tw:text-sm","tw:outline-hidden","tw:focus:relative tw:focus:z-10 tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",t),"aria-label":"Table","aria-labelledby":"table-label",...o})})}function qr({className:t,stickyHeader:e,...a}){return r.jsx("thead",{"data-slot":"table-header",className:h({"tw:sticky tw:top-[-1px] tw:z-20 tw:bg-background tw:drop-shadow-sm":e},"tw:[&_tr]:border-b",t),...a})}function Kr({className:t,...e}){return r.jsx("tbody",{"data-slot":"table-body",className:h("tw:[&_tr:last-child]:border-0",t),...e})}function Oc({className:t,...e}){return r.jsx("tfoot",{"data-slot":"table-footer",className:h("tw:border-t tw:bg-muted/50 tw:font-medium tw:[&>tr]:last:border-b-0",t),...e})}function $c(t){l.useEffect(()=>{const e=t.current;if(!e)return;const a=o=>{if(e.contains(document.activeElement)){if(o.key==="ArrowRight"||o.key==="ArrowLeft"){o.preventDefault(),o.stopPropagation();const n=t.current?lr(t.current):[],i=n.indexOf(document.activeElement),s=o.key==="ArrowRight"?i+1:i-1;s>=0&&s{e.removeEventListener("keydown",a)}},[t])}function Ac(t,e,a){let o;return a==="ArrowLeft"&&e>0?o=t[e-1]:a==="ArrowRight"&&eo.focus()),!0):!1}function Pc(t,e,a){let o;return a==="ArrowDown"&&e0&&(o=t[e-1]),o?(requestAnimationFrame(()=>o.focus()),!0):!1}function be({className:t,onKeyDown:e,onSelect:a,setFocusAlsoRunsSelect:o=!1,ref:n,...i}){const s=l.useRef(null);l.useEffect(()=>{typeof n=="function"?n(s.current):n&&"current"in n&&(n.current=s.current)},[n]),$c(s);const c=l.useMemo(()=>s.current?lr(s.current):[],[s]),w=l.useCallback(u=>{const{current:g}=s;if(!g||!g.parentElement)return;const m=g.closest("table"),p=m?lr(m).filter(b=>b.tagName==="TR"):[],f=p.indexOf(g),y=c.indexOf(document.activeElement);if(u.key==="ArrowDown"||u.key==="ArrowUp")u.preventDefault(),Pc(p,f,u.key);else if(u.key==="ArrowLeft"||u.key==="ArrowRight")u.preventDefault(),Ac(c,y,u.key);else if(u.key==="Escape"){u.preventDefault();const b=g.closest("table");b&&b.focus()}e==null||e(u)},[s,c,e]),d=l.useCallback(u=>{o&&(a==null||a(u))},[o,a]);return r.jsx("tr",{"data-slot":"table-row",ref:s,tabIndex:-1,onKeyDown:w,onFocus:d,className:h("tw:border-b tw:transition-colors tw:hover:bg-muted/50 tw:has-aria-expanded:bg-muted/50 tw:data-[state=selected]:bg-muted","tw:outline-hidden","tw:focus:relative tw:focus:z-10 tw:focus:ring-2 tw:focus:ring-ring tw:focus:ring-offset-1 tw:focus:ring-offset-background",t),...i})}function dr({className:t,...e}){return r.jsx("th",{"data-slot":"table-head",className:h("tw:h-10 tw:px-2 tw:text-start tw:align-middle tw:font-medium tw:whitespace-nowrap tw:text-foreground tw:[&:has([role=checkbox])]:pe-0",t),...e})}function Oe({className:t,...e}){return r.jsx("td",{"data-slot":"table-cell",className:h("tw:p-2 tw:align-middle tw:whitespace-nowrap tw:[&:has([role=checkbox])]:pe-0",t),...e})}function Lc({className:t,...e}){return r.jsx("caption",{"data-slot":"table-caption",className:h("tw:mt-4 tw:text-sm tw:text-muted-foreground",t),...e})}function Pr({className:t,...e}){return r.jsx("div",{"data-slot":"skeleton",className:h("pr-twp tw:animate-pulse tw:rounded-md tw:bg-muted",t),...e})}function Tn({columns:t,data:e,enablePagination:a=!1,showPaginationControls:o=!1,showColumnVisibilityControls:n=!1,stickyHeader:i=!1,onRowClickHandler:s=()=>{},id:c,isLoading:w=!1,noResultsMessage:d}){var E;const[u,g]=l.useState([]),[m,p]=l.useState([]),[f,y]=l.useState({}),[b,I]=l.useState({}),C=l.useMemo(()=>e??[],[e]),T=Mt.useReactTable({data:C,columns:t,getCoreRowModel:Mt.getCoreRowModel(),...a&&{getPaginationRowModel:Mt.getPaginationRowModel()},onSortingChange:g,getSortedRowModel:Mt.getSortedRowModel(),onColumnFiltersChange:p,getFilteredRowModel:Mt.getFilteredRowModel(),onColumnVisibilityChange:y,onRowSelectionChange:I,state:{sorting:u,columnFilters:m,columnVisibility:f,rowSelection:b}}),S=T.getVisibleFlatColumns();let N;return w?N=Array.from({length:10}).map((x,R)=>`skeleton-row-${R}`).map(x=>r.jsx(be,{className:"tw:hover:bg-transparent",children:r.jsx(Oe,{colSpan:S.length??t.length,className:"tw:border-0 tw:p-0",children:r.jsx("div",{className:"tw:w-full tw:py-2",children:r.jsx(Pr,{className:"tw:h-14 tw:w-full tw:rounded-md"})})})},x)):((E=T.getRowModel().rows)==null?void 0:E.length)>0?N=T.getRowModel().rows.map(z=>r.jsx(be,{onClick:()=>s(z,T),"data-state":z.getIsSelected()&&"selected",children:z.getVisibleCells().map(j=>r.jsx(Oe,{children:Mt.flexRender(j.column.columnDef.cell,j.getContext())},j.id))},z.id)):N=r.jsx(be,{children:r.jsx(Oe,{colSpan:t.length,className:"tw:h-24 tw:text-center",children:d})}),r.jsxs("div",{className:"pr-twp",id:c,children:[n&&r.jsx(Rc,{table:T}),r.jsxs(Ur,{stickyHeader:i,children:[r.jsx(qr,{stickyHeader:i,children:T.getHeaderGroups().map(z=>r.jsx(be,{children:z.headers.map(j=>r.jsx(dr,{className:"tw:p-0",children:j.isPlaceholder?void 0:Mt.flexRender(j.column.columnDef.header,j.getContext())},j.id))},z.id))}),r.jsx(Kr,{children:N})]}),a&&r.jsxs("div",{className:"tw:flex tw:items-center tw:justify-end tw:space-x-2 tw:py-4",children:[r.jsx(F,{variant:"outline",size:"sm",onClick:()=>T.previousPage(),disabled:!T.getCanPreviousPage(),children:"Previous"}),r.jsx(F,{variant:"outline",size:"sm",onClick:()=>T.nextPage(),disabled:!T.getCanNextPage(),children:"Next"})]}),a&&o&&r.jsx(Ic,{table:T})]})}function Bc(t){const e=new Map;return t.forEach(a=>{const o=e.get(a.projectId),n={scrollGroupId:a.scrollGroupId,scrollGroupScrRefLabel:a.scrollGroupScrRefLabel};o?o.some(i=>i.scrollGroupId===a.scrollGroupId)||o.push(n):e.set(a.projectId,[n])}),e.forEach(a=>a.sort((o,n)=>o.scrollGroupId-n.scrollGroupId)),e}function mo(t,e,a){return t.some(o=>o.projectId===e&&o.scrollGroupId===a)}function aa(t){const e=Bc(t.openTabs);if(t.mode==="project"){const n=t.selection.projectId;return t.projects.map(i=>{const s=e.get(i.id)??[];return{rowKey:i.id,projectId:i.id,shortName:i.shortName,fullName:i.fullName,language:i.language,languageCode:i.languageCode,scrollGroupId:void 0,scrollGroupScrRefLabel:void 0,openGroups:s.map(c=>c.scrollGroupId),isSelected:n===i.id,isMuted:s.length===0,isBoundButClosed:!1,isDisabled:i.isDisabled===!0,disabledReason:i.disabledReason}})}let a=[];t.mode==="project-multi"?a=t.selection.pairs:t.selection.projectId!==void 0&&(a=[{projectId:t.selection.projectId,scrollGroupId:t.selection.scrollGroupId}]);const o=[];return t.projects.forEach(n=>{const i=e.get(n.id);if(!i||i.length===0){o.push({rowKey:`project:${n.id}`,projectId:n.id,shortName:n.shortName,fullName:n.fullName,language:n.language,languageCode:n.languageCode,scrollGroupId:void 0,scrollGroupScrRefLabel:void 0,openGroups:[],isSelected:mo(a,n.id,void 0),isMuted:!0,isBoundButClosed:!1,isDisabled:n.isDisabled===!0,disabledReason:n.disabledReason});return}i.forEach(s=>{o.push({rowKey:`tab:${n.id}:${s.scrollGroupId}`,projectId:n.id,shortName:n.shortName,fullName:n.fullName,language:n.language,languageCode:n.languageCode,scrollGroupId:s.scrollGroupId,scrollGroupScrRefLabel:s.scrollGroupScrRefLabel,openGroups:[],isSelected:mo(a,n.id,s.scrollGroupId),isMuted:!1,isBoundButClosed:!1,isDisabled:n.isDisabled===!0,disabledReason:n.disabledReason})})}),a.forEach(n=>{if(n.scrollGroupId===void 0||o.some(s=>s.projectId===n.projectId&&s.scrollGroupId===n.scrollGroupId))return;const i=t.projects.find(s=>s.id===n.projectId);i&&o.push({rowKey:`closed:${i.id}:${n.scrollGroupId}`,projectId:i.id,shortName:i.shortName,fullName:i.fullName,language:i.language,languageCode:i.languageCode,scrollGroupId:n.scrollGroupId,scrollGroupScrRefLabel:void 0,openGroups:[],isSelected:!0,isMuted:!1,isBoundButClosed:!0,isDisabled:i.isDisabled===!0,disabledReason:i.disabledReason})}),o}function vo(t){return t.isBoundButClosed?!1:t.scrollGroupId!==void 0?!0:t.openGroups.length>0}function oa(t,e){if(t.isSelected!==e.isSelected)return t.isSelected?-1:1;const a=t.shortName.localeCompare(e.shortName,void 0,{sensitivity:"base"});if(a!==0)return a;const o=t.scrollGroupId??Number.POSITIVE_INFINITY,n=e.scrollGroupId??Number.POSITIVE_INFINITY;return o-n}function Vc(t,e){if(!e)return[{kind:"flat",rows:[...t].sort(oa)}];const a=t.filter(vo).sort(oa),o=t.filter(i=>!vo(i)).sort(oa);if(a.length===0)return[{kind:"flat",rows:o}];const n=[{kind:"openTabs",rows:a}];return o.length>0&&n.push({kind:"other",rows:o}),n}const Fc={searchPlaceholder:"Search projects & resources",filterAriaLabel:"Filter",groupSectionLabel:"Group",filterSectionLabel:"Filter",filterGroupByOpenTabs:"By open tabs",filterShowSelectedOnly:"Show selected only",openTabsSectionHeading:"Opened project & resource tabs",otherProjectsSectionHeading:"Your projects & resources",boundButClosedTooltip:"Bound to {group} · not currently open",openButtonLabel:"Open",selectAll:"Select all",clearAll:"Clear all"};function Gc(t){return{...Fc,...t}}function wr(t){return D.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[D.getLocalizeKeyForScrollGroupId(t)]??String(t)}const Uc={backgroundImage:"linear-gradient(to top right, transparent calc(50% - 1px), currentColor calc(50% - 0.5px), currentColor calc(50% + 0.5px), transparent calc(50% + 1px))"};function qc({scrollGroupId:t,isBoundButClosed:e}){const a=wr(t);return e?r.jsx(pe,{variant:"outline",className:"tw:relative tw:text-muted-foreground",style:Uc,children:a}):r.jsx(pe,{variant:"secondary",children:a})}function Kc({row:t,mode:e,strings:a,onClick:o,onOpen:n}){const[i,s]=l.useState(!1),c=l.useRef(null),w=!!(t.language||t.languageCode),d=w||!!t.scrollGroupScrRefLabel||t.isBoundButClosed||t.isDisabled&&!!t.disabledReason,u=l.useCallback(()=>{if(d){s(!0);return}const b=c.current;b&&b.scrollWidth>b.clientWidth&&s(!0)},[d]),g=r.jsx($.Check,{className:h("tw:h-4 tw:w-4",t.isSelected?"tw:opacity-100":"tw:opacity-0")});let m;e==="project"?t.openGroups.length>0&&(m=r.jsx("span",{className:"tw:ms-auto tw:flex tw:shrink-0 tw:gap-1",children:t.openGroups.map(b=>r.jsx(pe,{variant:"secondary",children:wr(b)},b))})):t.scrollGroupId!==void 0&&(m=r.jsxs("span",{className:"tw:ms-auto tw:flex tw:shrink-0 tw:items-center tw:gap-2",children:[r.jsx(qc,{scrollGroupId:t.scrollGroupId,isBoundButClosed:t.isBoundButClosed}),t.isBoundButClosed&&n&&r.jsxs(F,{size:"sm",variant:"ghost",className:"tw:h-6 tw:gap-1 tw:px-2 tw:text-xs",onClick:b=>{b.stopPropagation(),n(t)},onMouseDown:b=>b.stopPropagation(),"aria-label":a.openButtonLabel,title:a.openButtonLabel,children:[r.jsx($.ArrowRight,{className:"tw:h-3 tw:w-3"}),a.openButtonLabel]})]}));const p=r.jsxs(ie,{value:`${t.rowKey} ${t.shortName} ${t.fullName} ${t.language??""} ${t.languageCode??""}`,onSelect:()=>{t.isDisabled||o(t)},disabled:t.isDisabled,onPointerEnter:u,onPointerLeave:()=>s(!1),className:"tw:flex tw:items-center tw:gap-2 tw:pe-4","data-selected":t.isSelected,children:[r.jsx("span",{className:"tw:flex tw:h-4 tw:w-4 tw:shrink-0 tw:items-center tw:justify-center",children:g}),r.jsxs("span",{ref:c,className:"tw:min-w-0 tw:flex-1 tw:truncate tw:text-start",children:[r.jsx("span",{children:t.shortName}),r.jsxs("span",{className:"tw:text-muted-foreground",children:[" • ",t.fullName]})]}),m]}),f=t.scrollGroupId!==void 0?wr(t.scrollGroupId):void 0,y=t.isBoundButClosed&&f?a.boundButClosedTooltip.replace("{group}",f):void 0;return r.jsxs($t,{open:i,delayDuration:400,children:[r.jsx(At,{asChild:!0,children:p}),r.jsxs(Pt,{side:"top",align:"center",sideOffset:8,collisionPadding:16,className:"tw:max-w-xs tw:text-center",style:{zIndex:Vr},children:[r.jsx("div",{className:"tw:font-semibold",children:t.fullName}),w&&r.jsxs("div",{className:"tw:text-sm",children:[t.language,t.languageCode&&r.jsxs("span",{className:"tw:text-muted-foreground",children:[" (",t.languageCode,")"]})]}),!t.isBoundButClosed&&t.scrollGroupScrRefLabel&&f&&r.jsxs("div",{className:"tw:text-sm",children:[t.scrollGroupScrRefLabel,r.jsxs("span",{className:"tw:text-muted-foreground",children:[" (",f,")"]})]}),y&&r.jsx("div",{className:"tw:text-sm tw:italic",children:y}),t.isDisabled&&t.disabledReason&&r.jsx("div",{className:"tw:text-sm tw:italic tw:text-muted-foreground",children:t.disabledReason})]})]})}function Hc({groupByOpenTabs:t,onChangeGroupByOpenTabs:e,showSelectedOnly:a,onChangeShowSelectedOnly:o,strings:n}){const i=!!a;return r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsx(F,{variant:"ghost",size:"sm",className:h("tw:h-8 tw:w-8 tw:shrink-0 tw:p-0",i&&"tw:bg-accent tw:text-accent-foreground tw:hover:bg-accent/80 tw:data-[state=open]:bg-accent"),"aria-label":n.filterAriaLabel,"aria-pressed":i,title:n.filterAriaLabel,onMouseDown:s=>s.preventDefault(),children:r.jsx($.Filter,{className:"tw:h-4 tw:w-4"})})}),r.jsxs(ne,{align:"end",className:"tw:w-56",style:{zIndex:Vr},children:[r.jsx(Ee,{children:n.groupSectionLabel}),r.jsx(ee,{checked:t,onCheckedChange:e,onSelect:s=>s.preventDefault(),children:n.filterGroupByOpenTabs}),o&&r.jsxs(r.Fragment,{children:[r.jsx(ye,{}),r.jsx(Ee,{children:n.filterSectionLabel}),r.jsx(ee,{checked:!!a,onCheckedChange:o,onSelect:s=>s.preventDefault(),children:n.filterShowSelectedOnly})]})]})]})}function Rn(t){const[e,a]=l.useState(!1),[o,n]=l.useState(""),[i,s]=l.useState(t.defaultGroupByOpenTabs??!0),[c,w]=l.useState(!1),d=Gc(t.localizedStrings),u=l.useMemo(()=>t.mode==="project"?aa({mode:"project",projects:t.projects,openTabs:t.openTabs,selection:t.selection}):t.mode==="project-multi"?aa({mode:"project-multi",projects:t.projects,openTabs:t.openTabs,selection:t.selection}):aa({mode:"projectScrollGroup",projects:t.projects,openTabs:t.openTabs,selection:t.selection}),[t.mode,t.projects,t.openTabs,t.selection]),g=l.useMemo(()=>{const N=o.trim().toLowerCase();let E=u;return N&&(E=E.filter(z=>z.shortName.toLowerCase().includes(N)||z.fullName.toLowerCase().includes(N)||(z.language??"").toLowerCase().includes(N)||(z.languageCode??"").toLowerCase().includes(N))),t.mode==="project-multi"&&c&&(E=E.filter(z=>z.isSelected)),E},[u,o,t.mode,c]),m=l.useMemo(()=>Vc(g,i),[g,i]),p=l.useMemo(()=>{if(t.mode!=="project-multi")return[];const N=[];return t.projects.forEach(E=>{const z=t.openTabs.filter(x=>x.projectId===E.id);if(z.length===0){N.push({projectId:E.id});return}const j=new Set;z.forEach(x=>{j.has(x.scrollGroupId)||(j.add(x.scrollGroupId),N.push({projectId:E.id,scrollGroupId:x.scrollGroupId}))})}),N},[t.mode,t.projects,t.openTabs]),f=N=>{if(N.scrollGroupId!==void 0){if(t.mode==="projectScrollGroup"){t.onOpenProjectInGroup(N.projectId,N.scrollGroupId);return}t.mode==="project-multi"&&t.onOpenProjectInGroup&&t.onOpenProjectInGroup(N.projectId,N.scrollGroupId)}},y=N=>{switch(t.mode){case"project":{t.onChangeSelection({projectId:N.projectId}),a(!1);return}case"project-multi":{const E=t.selection.pairs,z=x=>x.projectId===N.projectId&&x.scrollGroupId===N.scrollGroupId,j=E.some(z)?E.filter(x=>!z(x)):[...E,{projectId:N.projectId,scrollGroupId:N.scrollGroupId}];t.onChangeSelection({pairs:j}),j.length===0&&c&&w(!1);return}case"projectScrollGroup":{if(N.isBoundButClosed&&N.scrollGroupId!==void 0){t.onOpenProjectInGroup(N.projectId,N.scrollGroupId),a(!1);return}if(N.scrollGroupId!==void 0){t.onChangeSelection({projectId:N.projectId,scrollGroupId:N.scrollGroupId}),a(!1);return}const E=t.selection.scrollGroupId??0;t.onChangeSelection({projectId:N.projectId,scrollGroupId:E}),t.onOpenProjectInGroup(N.projectId,E),a(!1)}}},b=()=>{if(t.mode!=="project-multi")return;const N=t.selection.pairs,E=new Set(N.map(j=>`${j.projectId}:${j.scrollGroupId??""}`)),z=[...N];p.forEach(j=>{const x=`${j.projectId}:${j.scrollGroupId??""}`;E.has(x)||(E.add(x),z.push(j))}),t.onChangeSelection({pairs:z})},I=()=>{t.mode==="project-multi"&&(t.onChangeSelection({pairs:[]}),c&&w(!1))},C=l.useMemo(()=>{switch(t.mode){case"project":{const N=t.projects.find(z=>z.id===t.selection.projectId),E=N?N.shortName:t.buttonPlaceholder??"";return{node:E,title:E}}case"project-multi":{const{pairs:N}=t.selection;if(N.length===0){const x=t.buttonPlaceholder??"";return{node:x,title:x}}const E=[];if(N.forEach(x=>{const R=t.projects.find(P=>P.id===x.projectId);R&&E.push({project:R,scrollGroupId:x.scrollGroupId})}),E.length===0){const x=t.buttonPlaceholder??"";return{node:x,title:x}}if(t.getSelectedText){const x=t.getSelectedText(E);return{node:x,title:x}}const z=E.map(({project:x,scrollGroupId:R})=>R===void 0?x.shortName:`${x.shortName} (${wr(R)})`).join(", ");if(E.length===1)return{node:z,title:z};const j=E.length.toString();return{node:r.jsxs(r.Fragment,{children:[r.jsx(pe,{variant:"muted",className:"tw:shrink-0",children:j}),r.jsx("span",{className:"tw:min-w-0 tw:truncate",children:z})]}),title:`${j} ${z}`}}case"projectScrollGroup":{const N=t.projects.find(j=>j.id===t.selection.projectId);if(!N){const j=t.buttonPlaceholder??"";return{node:j,title:j}}const E=t.selection.scrollGroupId;if(E===void 0)return{node:N.shortName,title:N.shortName};const z=`${N.shortName} · ${wr(E)}`;return{node:z,title:z}}default:return{node:"",title:""}}},[t]),T=t.mode==="project-multi"?r.jsx($.ChevronsUpDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"}):r.jsx($.ChevronDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"}),S=t.mode==="projectScrollGroup"||t.mode==="project-multi"&&t.onOpenProjectInGroup?f:void 0;return r.jsxs(se,{open:e,onOpenChange:a,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:t.buttonVariant??"outline",role:"combobox","aria-expanded":e,"aria-label":t.ariaLabel,disabled:t.isDisabled??!1,className:h("tw:flex tw:w-[180px] tw:items-center tw:justify-between tw:overflow-hidden",t.buttonClassName),children:[r.jsx("span",{className:"tw:flex tw:min-w-0 tw:flex-1 tw:items-baseline tw:gap-2 tw:overflow-hidden tw:whitespace-nowrap tw:text-start",children:typeof C.node=="string"?r.jsx("span",{className:"tw:min-w-0 tw:truncate",children:C.node}):C.node}),T]})}),r.jsx(ce,{align:t.alignDropDown??"start",collisionPadding:16,className:h("tw:w-80 tw:max-w-[calc(100vw-2rem)] tw:p-0",t.popoverContentClassName),style:t.popoverContentStyle,children:r.jsx(Ot,{delayDuration:400,children:r.jsxs(he,{shouldFilter:!1,children:[r.jsxs("div",{className:"tw:flex tw:items-center tw:border-b tw:pe-2",children:[r.jsx("div",{className:"tw:flex-1",children:r.jsx(Fe,{value:o,onValueChange:n,placeholder:d.searchPlaceholder,className:"tw:border-0"})}),r.jsx(Hc,{groupByOpenTabs:i,onChangeGroupByOpenTabs:s,showSelectedOnly:t.mode==="project-multi"?c:void 0,onChangeShowSelectedOnly:t.mode==="project-multi"?w:void 0,strings:d})]}),t.mode==="project-multi"&&r.jsxs("div",{className:"tw:flex tw:justify-between tw:border-b tw:py-2 tw:pe-4 tw:ps-2",children:[r.jsx(F,{variant:"ghost",size:"sm",onClick:b,children:`${d.selectAll} (${p.length.toString()})`}),r.jsx(F,{variant:"ghost",size:"sm",onClick:I,children:`${d.clearAll} (${t.selection.pairs.length.toString()})`})]}),r.jsxs(fe,{children:[r.jsx(Ze,{children:t.commandEmptyMessage??"No projects found"}),m.map((N,E)=>r.jsxs(l.Fragment,{children:[r.jsx(re,{heading:Yc(N,d),children:N.rows.map(z=>r.jsx(Kc,{row:z,mode:t.mode,strings:d,onClick:y,onOpen:S},z.rowKey))}),E({overrides:{a:{props:{target:o}}}}),[o]);return r.jsx("div",{id:t,className:h("pr-twp tw:prose",{"tw:line-clamp-3 tw:max-h-10 tw:overflow-hidden tw:text-ellipsis tw:break-words":n},a),children:r.jsx(bi,{options:i,children:e})})}const zn=Object.freeze(["%webView_error_dump_header%","%webView_error_dump_info_message%"]),bo=(t,e)=>t[e]??e;function Dn({errorDetails:t,handleCopyNotify:e,localizedStrings:a,id:o}){const n=bo(a,"%webView_error_dump_header%"),i=bo(a,"%webView_error_dump_info_message%");function s(){navigator.clipboard.writeText(t),e&&e()}return r.jsxs("div",{id:o,className:"tw:inline-flex tw:w-full tw:flex-col tw:items-start tw:justify-start tw:gap-4",children:[r.jsxs("div",{className:"tw:inline-flex tw:items-start tw:justify-start tw:gap-4 tw:self-stretch",children:[r.jsxs("div",{className:"tw:inline-flex tw:flex-1 tw:flex-col tw:items-start tw:justify-start",children:[r.jsx("div",{className:"tw:text-color-text tw:justify-center tw:text-center tw:text-lg tw:font-semibold tw:leading-loose",children:n}),r.jsx("div",{className:"tw:justify-center tw:self-stretch tw:text-sm tw:font-normal tw:leading-tight tw:text-muted-foreground",children:i})]}),r.jsx(F,{variant:"secondary",size:"icon",className:"size-8",onClick:()=>s(),children:r.jsx($.Copy,{})})]}),r.jsx("div",{className:"tw:prose tw:w-full",children:r.jsx("pre",{className:"tw:text-xs",children:t})})]})}const Xc=Object.freeze([...zn,"%webView_error_dump_copied_message%"]);function Zc({errorDetails:t,handleCopyNotify:e,localizedStrings:a,children:o,className:n,id:i}){const[s,c]=l.useState(!1),w=()=>{c(!0),e&&e()},d=u=>{u||c(!1)};return r.jsxs(se,{onOpenChange:d,children:[r.jsx(me,{asChild:!0,children:o}),r.jsxs(ce,{id:i,className:h("tw:min-w-80 tw:max-w-96",n),children:[s&&a["%webView_error_dump_copied_message%"]&&r.jsx(yt,{children:a["%webView_error_dump_copied_message%"]}),r.jsx(Dn,{errorDetails:t,handleCopyNotify:w,localizedStrings:a})]})]})}var In=(t=>(t[t.Check=0]="Check",t[t.Radio=1]="Radio",t))(In||{});function Jc({id:t,label:e,groups:a}){const[o,n]=l.useState(Object.fromEntries(a.map((d,u)=>d.itemType===0?[u,[]]:void 0).filter(d=>!!d))),[i,s]=l.useState({}),c=(d,u)=>{const g=!o[d][u];n(p=>(p[d][u]=g,{...p}));const m=a[d].items[u];m.onUpdate(m.id,g)},w=(d,u)=>{s(m=>(m[d]=u,{...m}));const g=a[d].items.find(m=>m.id===u);g?g.onUpdate(u):console.error(`Could not find dropdown radio item with id '${u}'!`)};return r.jsx("div",{id:t,children:r.jsxs(ae,{children:[r.jsx(oe,{asChild:!0,children:r.jsxs(F,{variant:"default",children:[r.jsx($.Filter,{size:16,className:"tw:mr-2 tw:h-4 tw:w-4"}),e,r.jsx($.ChevronDown,{size:16,className:"tw:ml-2 tw:h-4 tw:w-4"})]})}),r.jsx(ne,{children:a.map((d,u)=>r.jsxs("div",{children:[r.jsx(Ee,{children:d.label}),r.jsx(La,{children:d.itemType===0?r.jsx(r.Fragment,{children:d.items.map((g,m)=>r.jsx("div",{children:r.jsx(ee,{checked:o[u][m],onCheckedChange:()=>c(u,m),children:g.label})},g.id))}):r.jsx(yn,{value:i[u],onValueChange:g=>w(u,g),children:d.items.map(g=>r.jsx("div",{children:r.jsx(kn,{value:g.id,children:g.label})},g.id))})}),r.jsx(ye,{})]},d.label))})]})})}function Qc({id:t,category:e,downloads:a,languages:o,moreInfoUrl:n,handleMoreInfoLinkClick:i,supportUrl:s,handleSupportLinkClick:c}){const w=new D.NumberFormat("en",{notation:"compact",compactDisplay:"short"}).format(Object.values(a).reduce((u,g)=>u+g,0)),d=()=>{window.scrollTo(0,document.body.scrollHeight)};return r.jsxs("div",{id:t,className:"pr-twp tw:flex tw:items-center tw:justify-center tw:gap-4 tw:divide-x tw:border-b tw:border-t tw:py-2 tw:text-center",children:[e&&r.jsxs("div",{className:"tw:flex tw:flex-col tw:items-center tw:gap-1",children:[r.jsx("div",{className:"tw:flex",children:r.jsx("span",{className:"tw:text-xs tw:font-semibold tw:text-foreground",children:e})}),r.jsx("span",{className:"tw:text-xs tw:text-foreground",children:"CATEGORY"})]}),r.jsxs("div",{className:"tw:flex tw:flex-col tw:items-center tw:gap-1 tw:ps-4",children:[r.jsxs("div",{className:"tw:flex tw:gap-1",children:[r.jsx($.User,{className:"tw:h-4 tw:w-4"}),r.jsx("span",{className:"tw:text-xs tw:font-semibold tw:text-foreground",children:w})]}),r.jsx("span",{className:"tw:text-xs tw:text-foreground",children:"USERS"})]}),r.jsxs("div",{className:"tw:flex tw:flex-col tw:items-center tw:gap-1 tw:ps-4",children:[r.jsx("div",{className:"tw:flex tw:gap-2",children:o.slice(0,3).map(u=>r.jsx("span",{className:"tw:text-xs tw:font-semibold tw:text-foreground",children:u.toUpperCase()},u))}),o.length>3&&r.jsxs("button",{type:"button",onClick:()=>d(),className:"tw:text-xs tw:text-foreground tw:underline",children:["+",o.length-3," more languages"]})]}),(n||s)&&r.jsxs("div",{className:"tw:flex tw:flex-col tw:gap-1 tw:ps-4",children:[n&&r.jsx("div",{className:"tw:flex tw:gap-1",children:r.jsxs(F,{onClick:()=>i(),variant:"link",className:"tw:flex tw:h-auto tw:gap-1 tw:py-0 tw:text-xs tw:font-semibold tw:text-foreground",children:["Website",r.jsx($.Link,{className:"tw:h-4 tw:w-4"})]})}),s&&r.jsx("div",{className:"tw:flex tw:gap-1",children:r.jsxs(F,{onClick:()=>c(),variant:"link",className:"tw:flex tw:h-auto tw:gap-1 tw:py-0 tw:text-xs tw:font-semibold tw:text-foreground",children:["Support",r.jsx($.CircleHelp,{className:"tw:h-4 tw:w-4"})]})})]})]})}function tl({id:t,versionHistory:e}){const[a,o]=l.useState(!1),n=new Date;function i(c){const w=new Date(c),d=new Date(n.getTime()-w.getTime()),u=d.getUTCFullYear()-1970,g=d.getUTCMonth(),m=d.getUTCDate()-1;let p="";return u>0?p=`${u.toString()} year${u===1?"":"s"} ago`:g>0?p=`${g.toString()} month${g===1?"":"s"} ago`:m===0?p="today":p=`${m.toString()} day${m===1?"":"s"} ago`,p}const s=Object.entries(e).sort((c,w)=>w[0].localeCompare(c[0]));return r.jsxs("div",{className:"pr-twp",id:t,children:[r.jsx("h3",{className:"tw:text-md tw:font-semibold",children:"What`s New"}),r.jsx("ul",{className:"tw:list-disc tw:pl-5 tw:pr-4 tw:text-xs tw:text-foreground",children:(a?s:s.slice(0,5)).map(c=>r.jsxs("div",{className:"tw:mt-3 tw:flex tw:justify-between",children:[r.jsx("div",{className:"tw:text-foreground",children:r.jsx("li",{className:"tw:prose tw:text-xs",children:r.jsx("span",{children:c[1].description})})}),r.jsxs("div",{className:"tw:justify-end tw:text-right",children:[r.jsxs("div",{children:["Version ",c[0]]}),r.jsx("div",{children:i(c[1].date)})]})]},c[0]))}),s.length>5&&r.jsx("button",{type:"button",onClick:()=>o(!a),className:"tw:text-xs tw:text-foreground tw:underline",children:a?"Show Less Version History":"Show All Version History"})]})}function el({id:t,publisherDisplayName:e,fileSize:a,locales:o,versionHistory:n,currentVersion:i}){const s=l.useMemo(()=>D.formatBytes(a),[a]),w=(d=>{const u=new Intl.DisplayNames(D.getCurrentLocale(),{type:"language"});return d.map(g=>u.of(g))})(o);return r.jsx("div",{id:t,className:"pr-twp tw:border-t tw:py-2",children:r.jsxs("div",{className:"tw:flex tw:flex-col tw:gap-2 tw:divide-y",children:[Object.entries(n).length>0&&r.jsx(tl,{versionHistory:n}),r.jsxs("div",{className:"tw:flex tw:flex-col tw:gap-2 tw:py-2",children:[r.jsx("h2",{className:"tw:text-md tw:font-semibold",children:"Information"}),r.jsxs("div",{className:"tw:flex tw:items-start tw:justify-between tw:text-xs tw:text-foreground",children:[r.jsxs("p",{className:"tw:flex tw:flex-col tw:justify-start tw:gap-1",children:[r.jsx("span",{children:"Publisher"}),r.jsx("span",{className:"tw:font-semibold",children:e}),r.jsx("span",{children:"Size"}),r.jsx("span",{className:"tw:font-semibold",children:s})]}),r.jsx("div",{className:"tw:flex tw:w-3/4 tw:items-center tw:justify-between tw:text-xs tw:text-foreground",children:r.jsxs("p",{className:"tw:flex tw:flex-col tw:justify-start tw:gap-1",children:[r.jsx("span",{children:"Version"}),r.jsx("span",{className:"tw:font-semibold",children:i}),r.jsx("span",{children:"Languages"}),r.jsx("span",{className:"tw:font-semibold",children:w.join(", ")})]})})]})]})]})})}function Mn({entries:t,selected:e,onChange:a,placeholder:o,hasToggleAllFeature:n=!1,selectAllText:i="Select All",clearAllText:s="Clear All",commandEmptyMessage:c="No entries found",customSelectedText:w,isOpen:d=void 0,onOpenChange:u=void 0,isDisabled:g=!1,sortSelected:m=!1,icon:p=void 0,className:f=void 0,variant:y="ghost",id:b}){const[I,C]=l.useState(!1),T=l.useCallback(R=>{var G;const P=(G=t.find(q=>q.label===R))==null?void 0:G.value;P&&a(e.includes(P)?e.filter(q=>q!==P):[...e,P])},[t,e,a]),S=()=>w||o,N=l.useMemo(()=>{if(!m)return t;const R=t.filter(G=>G.starred).sort((G,q)=>G.label.localeCompare(q.label)),P=t.filter(G=>!G.starred).sort((G,q)=>{const V=e.includes(G.value),K=e.includes(q.value);return V&&!K?-1:!V&&K?1:G.label.localeCompare(q.label)});return[...R,...P]},[t,e,m]),E=()=>{a(t.map(R=>R.value))},z=()=>{a([])},j=d??I,x=u??C;return r.jsx("div",{id:b,className:f,children:r.jsxs(se,{open:j,onOpenChange:x,children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:y,role:"combobox","aria-expanded":j,className:"tw:group tw:w-full tw:justify-between",disabled:g,children:[r.jsxs("div",{className:"tw:flex tw:min-w-0 tw:flex-1 tw:items-center tw:gap-2",children:[p&&r.jsx("div",{className:"tw:ml-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50",children:r.jsx("span",{className:"tw:flex tw:h-full tw:w-full tw:items-center tw:justify-center",children:p})}),r.jsx("span",{className:h("tw:min-w-0 tw:overflow-hidden tw:text-ellipsis tw:whitespace-nowrap tw:text-start tw:font-normal"),children:S()})]}),r.jsx($.ChevronsUpDown,{className:"tw:ml-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})}),r.jsx(ce,{align:"start",className:"tw:w-full tw:p-0",children:r.jsxs(he,{children:[r.jsx(Fe,{placeholder:`Search ${o.toLowerCase()}...`}),n&&r.jsxs("div",{className:"tw:flex tw:justify-between tw:border-b tw:p-2",children:[r.jsx(F,{variant:"ghost",size:"sm",onClick:E,children:i}),r.jsx(F,{variant:"ghost",size:"sm",onClick:z,children:s})]}),r.jsxs(fe,{children:[r.jsx(Ze,{children:c}),r.jsx(re,{children:N.map(R=>r.jsxs(ie,{value:R.label,onSelect:T,className:"tw:flex tw:items-center tw:gap-2",children:[r.jsx("div",{className:"w-4",children:r.jsx($.Check,{className:h("tw:h-4 tw:w-4",e.includes(R.value)?"tw:opacity-100":"tw:opacity-0")})}),R.starred&&r.jsx($.Star,{className:"tw:h-4 tw:w-4"}),r.jsx("div",{className:"tw:flex-grow",children:R.label}),R.secondaryLabel&&r.jsx("div",{className:"tw:text-end tw:text-muted-foreground",children:R.secondaryLabel})]},R.label))})]})]})})]})})}function rl({entries:t,selected:e,onChange:a,placeholder:o,commandEmptyMessage:n,customSelectedText:i,isDisabled:s,sortSelected:c,icon:w,className:d,badgesPlaceholder:u,id:g}){return r.jsxs("div",{id:g,className:"tw:flex tw:items-center tw:gap-2",children:[r.jsx(Mn,{entries:t,selected:e,onChange:a,placeholder:o,commandEmptyMessage:n,customSelectedText:i,isDisabled:s,sortSelected:c,icon:w,className:d}),e.length>0?r.jsx("div",{className:"tw:flex tw:flex-wrap tw:items-center tw:gap-2",children:e.map(m=>{var p;return r.jsxs(pe,{variant:"muted",className:"tw:flex tw:items-center tw:gap-1",children:[r.jsx(F,{variant:"ghost",size:"icon",className:"tw:h-4 tw:w-4 tw:p-0 tw:hover:bg-transparent",onClick:()=>a(e.filter(f=>f!==m)),children:r.jsx($.X,{className:"tw:h-3 tw:w-3"})}),(p=t.find(f=>f.value===m))==null?void 0:p.label]},m)})}):r.jsx(yt,{children:u})]})}function ha({className:t,...e}){return r.jsx("kbd",{"data-slot":"kbd",className:h("pr-twp tw:pointer-events-none tw:inline-flex tw:h-5 tw:w-fit tw:min-w-5 tw:items-center tw:justify-center tw:gap-1 tw:rounded-sm tw:bg-muted tw:px-1 tw:font-sans tw:text-xs tw:font-medium tw:text-muted-foreground tw:select-none tw:in-data-[slot=tooltip-content]:bg-background/20 tw:in-data-[slot=tooltip-content]:text-background tw:dark:in-data-[slot=tooltip-content]:bg-background/10 tw:[&_svg:not([class*=size-])]:size-3",t),...e})}const On=Object.freeze(["%undoButton_tooltip%","%redoButton_tooltip%"]),xo=(t,e)=>t[e]??e;function $n({onUndoClick:t,onRedoClick:e,canUndo:a=!0,canRedo:o=!0,localizedStrings:n={},showKeyboardShortcuts:i=!0,className:s="tw:h-6 tw:w-6",variant:c="ghost"}){const w=l.useMemo(()=>/Macintosh/i.test(navigator.userAgent),[]),d=xo(n,"%undoButton_tooltip%"),u=xo(n,"%redoButton_tooltip%"),g=c==="secondary"||c==="default";return r.jsxs(Gr,{children:[r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":d,className:s,size:"icon",onClick:t,disabled:!a,variant:c,children:r.jsx($.Undo,{})})}),r.jsx(Pt,{children:r.jsxs("p",{children:[d,i&&r.jsxs(r.Fragment,{children:[" ",r.jsx(ha,{children:w?"⌘Z":"Ctrl+Z"})]})]})})]})}),e&&g&&r.jsx(Ma,{}),e&&r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":u,className:s,size:"icon",onClick:e,disabled:!o,variant:c,children:r.jsx($.Redo,{})})}),r.jsx(Pt,{children:r.jsxs("p",{children:[u,i&&r.jsxs(r.Fragment,{children:[" ",r.jsx(ha,{children:w?"⌘⇧Z":"Ctrl+Y"})]})]})})]})})]})}function An({children:t,editorRef:e,canUndo:a=!0,canRedo:o=!0}){const n=l.useRef(null);return l.useEffect(()=>{var w;const i=/Macintosh/i.test(navigator.userAgent),s=((w=n.current)==null?void 0:w.querySelector(".editor-input"))??void 0,c=d=>{var g,m,p,f;if(!s||document.activeElement!==s)return;const u=d.key.toLowerCase();if(i){if(!d.metaKey)return;!d.shiftKey&&u==="z"?(d.preventDefault(),a&&((g=e.current)==null||g.undo())):d.shiftKey&&u==="z"&&(d.preventDefault(),o&&((m=e.current)==null||m.redo()))}else{if(!d.ctrlKey)return;!d.shiftKey&&u==="z"?(d.preventDefault(),a&&((p=e.current)==null||p.undo())):(u==="y"||d.shiftKey&&u==="z")&&(d.preventDefault(),o&&((f=e.current)==null||f.redo()))}};return document.addEventListener("keydown",c),()=>document.removeEventListener("keydown",c)},[o,a,e]),r.jsx("div",{ref:n,children:t})}const al=(t,e,a)=>t==="generated"?r.jsxs(r.Fragment,{children:[r.jsx("p",{children:"+"})," ",e["%footnoteEditor_callerDropdown_item_generated%"]]}):t==="hidden"?r.jsxs(r.Fragment,{children:[r.jsx("p",{children:"-"})," ",e["%footnoteEditor_callerDropdown_item_hidden%"]]}):r.jsxs(r.Fragment,{children:[r.jsx("p",{children:a})," ",e["%footnoteEditor_callerDropdown_item_custom%"]]});function ol({callerType:t,updateCallerType:e,customCaller:a,updateCustomCaller:o,localizedStrings:n}){const i=l.useRef(null),s=l.useRef(null),c=l.useRef(!1),[w,d]=l.useState(t),[u,g]=l.useState(a),[m,p]=l.useState(!1);l.useEffect(()=>{d(t)},[t]),l.useEffect(()=>{u!==a&&g(a)},[a]);const f=b=>{c.current=!1,p(b),b||(w!=="custom"||u?(e(w),o(u)):(d(t),g(a)))},y=b=>{var I,C,T,S;b.stopPropagation(),document.activeElement===s.current&&b.key==="ArrowDown"||b.key==="ArrowRight"?((I=i.current)==null||I.focus(),c.current=!0):document.activeElement===i.current&&b.key==="ArrowUp"?((C=s.current)==null||C.focus(),c.current=!1):document.activeElement===i.current&&b.key==="ArrowLeft"&&((T=i.current)==null?void 0:T.selectionStart)===0&&((S=s.current)==null||S.focus(),c.current=!1),w==="custom"&&b.key==="Enter"&&(document.activeElement===s.current||document.activeElement===i.current)&&f(!1)};return r.jsxs(ae,{open:m,onOpenChange:f,children:[r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(oe,{asChild:!0,children:r.jsx(F,{variant:"outline",className:"tw:h-6",children:al(t,n,a)})})}),r.jsx(Pt,{children:n["%footnoteEditor_callerDropdown_tooltip%"]})]})}),r.jsxs(ne,{style:{zIndex:xa},onClick:()=>{c.current&&(c.current=!1)},onKeyDown:y,onMouseMove:()=>{var b;c.current&&((b=i.current)==null||b.focus())},children:[r.jsx(Ee,{children:n["%footnoteEditor_callerDropdown_label%"]}),r.jsx(ye,{}),r.jsx(ee,{checked:w==="generated",onCheckedChange:()=>d("generated"),children:r.jsxs("div",{className:"tw:flex tw:w-full tw:justify-between",children:[r.jsx("span",{children:n["%footnoteEditor_callerDropdown_item_generated%"]}),r.jsx("span",{className:"tw:w-10 tw:text-center",children:Xt.GENERATOR_NOTE_CALLER})]})}),r.jsx(ee,{checked:w==="hidden",onCheckedChange:()=>d("hidden"),children:r.jsxs("div",{className:"tw:flex tw:w-full tw:justify-between",children:[r.jsx("span",{children:n["%footnoteEditor_callerDropdown_item_hidden%"]}),r.jsx("span",{className:"tw:w-10 tw:text-center",children:Xt.HIDDEN_NOTE_CALLER})]})}),r.jsx(ee,{ref:s,checked:w==="custom",onCheckedChange:()=>d("custom"),onClick:b=>{var I;b.stopPropagation(),c.current=!0,(I=i.current)==null||I.focus()},onSelect:b=>b.preventDefault(),children:r.jsxs("div",{className:"tw:flex tw:w-full tw:justify-between",children:[r.jsx("span",{children:n["%footnoteEditor_callerDropdown_item_custom%"]}),r.jsx(Xe,{tabIndex:0,onMouseDown:b=>{b.stopPropagation(),d("custom"),c.current=!0},ref:i,className:"tw:h-auto tw:w-10 tw:p-0 tw:text-center",value:u,onKeyDown:b=>{b.key==="Enter"||b.key==="ArrowUp"||b.key==="ArrowDown"||b.key==="ArrowLeft"||b.key==="ArrowRight"||b.stopPropagation()},maxLength:1,onChange:b=>g(b.target.value)})]})})]})]})}const nl=(t,e)=>t==="f"?r.jsxs(r.Fragment,{children:[r.jsx($.FunctionSquare,{})," ",e["%footnoteEditor_noteType_footnote_label%"]]}):t==="fe"?r.jsxs(r.Fragment,{children:[r.jsx($.SquareSigma,{})," ",e["%footnoteEditor_noteType_endNote_label%"]]}):r.jsxs(r.Fragment,{children:[r.jsx($.SquareX,{})," ",e["%footnoteEditor_noteType_crossReference_label%"]]}),il=(t,e)=>{if(t==="x")return e["%footnoteEditor_noteType_crossReference_label%"];let a=e["%footnoteEditor_noteType_endNote_label%"];return t==="f"&&(a=e["%footnoteEditor_noteType_footnote_label%"]),D.formatReplacementString(e["%footnoteEditor_noteType_tooltip%"]??"",{noteType:a})};function sl({noteType:t,handleNoteTypeChange:e,localizedStrings:a,isTypeSwitchable:o}){return r.jsxs(ae,{children:[r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(oe,{asChild:!0,children:r.jsx(F,{variant:"outline",className:"tw:h-6",children:nl(t,a)})})}),r.jsx(Pt,{children:r.jsx("p",{children:il(t,a)})})]})}),r.jsxs(ne,{style:{zIndex:xa},children:[r.jsx(Ee,{children:a["%footnoteEditor_noteTypeDropdown_label%"]}),r.jsx(ye,{}),r.jsxs(ee,{disabled:t!=="x"&&!o,checked:t==="x",onCheckedChange:()=>e("x"),className:"tw:gap-2",children:[r.jsx($.SquareX,{}),r.jsx("span",{children:a["%footnoteEditor_noteType_crossReference_label%"]})]}),r.jsxs(ee,{disabled:t==="x"&&!o,checked:t==="f",onCheckedChange:()=>e("f"),className:"tw:gap-2",children:[r.jsx($.FunctionSquare,{}),r.jsx("span",{children:a["%footnoteEditor_noteType_footnote_label%"]})]}),r.jsxs(ee,{disabled:t==="x"&&!o,checked:t==="fe",onCheckedChange:()=>e("fe"),className:"tw:gap-2",children:[r.jsx($.SquareSigma,{}),r.jsx("span",{children:a["%footnoteEditor_noteType_endNote_label%"]})]})]})]})}const Pn=Object.freeze(["%markerMenu_deprecated_label%","%markerMenu_disallowed_label%","%markerMenu_noResults%","%markerMenu_searchPlaceholder%"]);function cl({icon:t,className:e}){const a=t??$.Ban;return r.jsx(a,{className:e,size:16})}function yo({item:t,localizedStrings:e}){return r.jsxs(ie,{className:"tw:flex tw:gap-2 tw:hover:bg-accent",disabled:t.isDisallowed||t.isDeprecated,onSelect:t.action,children:[r.jsx("div",{className:"tw:w-8 tw:min-w-8",children:t.marker?r.jsx("span",{className:"tw:text-xs",children:t.marker}):r.jsx("div",{children:r.jsx(cl,{icon:t.icon})})}),r.jsxs("div",{children:[r.jsx("p",{className:"tw:text-sm",children:t.title}),t.subtitle&&r.jsx("p",{className:"tw:text-xs tw:text-muted-foreground",children:t.subtitle})]}),(t.isDisallowed||t.isDeprecated)&&r.jsx(Oi,{className:"tw:font-sans",children:t.isDisallowed?e["%markerMenu_disallowed_label%"]:e["%markerMenu_deprecated_label%"]})]})}function Ln({localizedStrings:t,markerMenuItems:e,searchRef:a}){const[o,n]=l.useState(""),[i,s]=l.useMemo(()=>{const c=o.trim().toLowerCase();if(!c)return[e,[]];const w=e.filter(u=>{var g;return(g=u.marker)==null?void 0:g.toLowerCase().includes(c)}),d=e.filter(u=>u.title.toLowerCase().includes(c)&&!w.includes(u));return[w,d]},[o,e]);return r.jsxs(he,{className:"tw:p-1",shouldFilter:!1,loop:!0,children:[r.jsx(Fe,{className:"marker-menu-search",ref:a,value:o,onValueChange:c=>n(c),placeholder:t["%markerMenu_searchPlaceholder%"]}),r.jsxs(fe,{children:[r.jsx(Ze,{children:t["%markerMenu_noResults%"]}),r.jsx(re,{children:i.map(c=>{var w;return r.jsx(yo,{item:c,localizedStrings:t},`item-${c.marker??((w=c.icon)==null?void 0:w.displayName)}-${c.title.replaceAll(" ","")}`)})}),s.length>0&&r.jsxs(r.Fragment,{children:[i.length>0&&r.jsx(ka,{alwaysRender:!0}),r.jsx(re,{children:s.map(c=>{var w;return r.jsx(yo,{item:c,localizedStrings:t},`item-${c.marker??((w=c.icon)==null?void 0:w.displayName)}-${c.title.replaceAll(" ","")}`)})})]})]})]})}function ll(t,e,a,o){if(!o||o==="p")return[];const n=D.usfmMarkers[o];if(!(n!=null&&n.children))return[];const i=[];return Object.entries(n.children).forEach(([,s])=>{i.push(...s.map(c=>({marker:c,title:a[D.usfmMarkers[c].description]??D.usfmMarkers[c].description,action:()=>{var w;(w=t.current)==null||w.insertMarker(c),e()}})))}),i.sort((s,c)=>(s.marker??s.title).localeCompare(c.marker??c.title))}function dl(t){var a;const e=(a=t.attributes)==null?void 0:a.char;e.style&&(e.style==="ft"&&(e.style="xt"),e.style==="fr"&&(e.style="xo"),e.style==="fq"&&(e.style="xq"))}function wl(t){var a;const e=(a=t.attributes)==null?void 0:a.char;e.style&&(e.style==="xt"&&(e.style="ft"),e.style==="xo"&&(e.style="fr"),e.style==="xq"&&(e.style="fq"))}const ul={type:"USJ",version:"3.1",content:[{type:"para"}]};function pl({classNameForEditor:t,noteOps:e,onChange:a,onClose:o,scrRef:n,noteKey:i,editorOptions:s,defaultMarkerMenuTrigger:c,localizedStrings:w,parentEditorRef:d}){const u=l.useRef(null),g=l.useRef(null),m=l.useRef(null),p=l.useRef(null);l.useLayoutEffect(()=>{if(!p.current)return;const{width:B}=p.current.getBoundingClientRect();B>0&&(p.current.style.width=`${B}px`)},[]);const[f,y]=l.useState("generated"),[b,I]=l.useState("generated"),[C,T]=l.useState("*"),[S,N]=l.useState("*"),[E,z]=l.useState("f"),[j,x]=l.useState(!1),[R,P]=l.useState(!0),[G,q]=l.useState(!1),V=l.useRef(!1),K=l.useRef(""),[M,Y]=l.useState(!1),[lt,kt]=l.useState(),[St,J]=l.useState(),[Et,U]=l.useState(),[tt,rt]=l.useState(),at=l.useRef(null),ot=l.useMemo(()=>({...s,markerMenuTrigger:c,hasExternalUI:!0,view:{...s.view??Xt.getDefaultViewOptions(),noteMode:"expanded"}}),[s,c]),Lt=l.useMemo(()=>ll(u,()=>Y(!1),w,tt),[w,tt]);l.useEffect(()=>{var B;M||(B=u.current)==null||B.focus()},[E,M]),l.useEffect(()=>{var nt,et;let B;V.current=!1,P(!0);const W=e==null?void 0:e.at(0);if(W&&Xt.isInsertEmbedOpOfType("note",W)){const wt=(nt=W.insert.note)==null?void 0:nt.caller;let mt="custom";wt===Xt.GENERATOR_NOTE_CALLER?mt="generated":wt===Xt.HIDDEN_NOTE_CALLER?mt="hidden":wt&&(T(wt),N(wt)),y(mt),I(mt),z(((et=W.insert.note)==null?void 0:et.style)??"f"),B=setTimeout(()=>{var vt;(vt=u.current)==null||vt.applyUpdate([W])},0)}return()=>{B&&clearTimeout(B)}},[e,i]);const gt=l.useCallback((B,W,nt=!1)=>{var wt,mt,vt;const et=(mt=(wt=u.current)==null?void 0:wt.getNoteOps(0))==null?void 0:mt.at(0);if(et&&Xt.isInsertEmbedOpOfType("note",et)){if(et.insert.note){let ut;B==="custom"?ut=W:B==="generated"?ut=Xt.GENERATOR_NOTE_CALLER:ut=Xt.HIDDEN_NOTE_CALLER,et.insert.note.caller=ut}a==null||a([et]),nt&&d&&i&&((vt=d.current)==null||vt.replaceEmbedUpdate(i,[et]))}},[i,a,d]),Ft=l.useCallback(()=>{gt(f,C,!0),o()},[f,C,o,gt]),Gt=l.useRef(Ft);l.useLayoutEffect(()=>{Gt.current=Ft});const A=l.useRef({book:n.book,chapterNum:n.chapterNum});l.useLayoutEffect(()=>{(A.current.book!==n.book||A.current.chapterNum!==n.chapterNum)&&(A.current={book:n.book,chapterNum:n.chapterNum},Gt.current())},[n.book,n.chapterNum]);const Tt=()=>{var W;const B=(W=g.current)==null?void 0:W.getElementsByClassName("editor-input")[0];B!=null&&B.textContent&&navigator.clipboard.writeText(B.textContent)},Bt=l.useCallback(B=>{y(B),gt(B,C)},[C,gt]),Qt=l.useCallback(B=>{T(B),gt(f,B)},[f,gt]),Ut=B=>{var nt,et,wt,mt,vt;z(B);const W=(et=(nt=u.current)==null?void 0:nt.getNoteOps(0))==null?void 0:et.at(0);if(W&&Xt.isInsertEmbedOpOfType("note",W)){W.insert.note&&(W.insert.note.style=B);const ut=(mt=(wt=W.insert.note)==null?void 0:wt.contents)==null?void 0:mt.ops;E!=="x"&&B==="x"?ut==null||ut.forEach(jt=>dl(jt)):E==="x"&&B!=="x"&&(ut==null||ut.forEach(jt=>wl(jt))),(vt=u.current)==null||vt.applyUpdate([W,{delete:1}])}},Rt=B=>{rt(B.contextMarker),q(B.canRedo)},je=l.useCallback(B=>{var nt,et,wt,mt,vt;const W=(et=(nt=u.current)==null?void 0:nt.getNoteOps(0))==null?void 0:et.at(0);if(W&&Xt.isInsertEmbedOpOfType("note",W)){B.content.length>1&&setTimeout(()=>{var O;(O=u.current)==null||O.applyUpdate([{retain:2},{delete:1}])},0);const ut=(wt=W.insert.note)==null?void 0:wt.style,jt=(vt=(mt=W.insert.note)==null?void 0:mt.contents)==null?void 0:vt.ops;if(ut||x(!1),x(ut==="x"?!!(jt!=null&&jt.every(O=>{var dt,bt;if(!((dt=O.attributes)!=null&&dt.char))return!0;const ct=((bt=O.attributes)==null?void 0:bt.char).style;return ct==="xt"||ct==="xo"||ct==="xq"})):!!(jt!=null&&jt.every(O=>{var dt,bt;if(!((dt=O.attributes)!=null&&dt.char))return!0;const ct=((bt=O.attributes)==null?void 0:bt.char).style;return ct==="ft"||ct==="fr"||ct==="fq"}))),!V.current){V.current=!0,K.current=JSON.stringify(W),P(!0);return}P(JSON.stringify(W)===K.current),gt(f,C)}else x(!1),P(!0)},[f,C,gt]),qt=l.useCallback(()=>{const B=window.getSelection();if(m.current&&Lt.length&&B&&B.rangeCount>0){const W=B.getRangeAt(0).getBoundingClientRect(),nt=m.current.getBoundingClientRect();kt(W.left-nt.left),J(W.top-nt.top),U(W.height),Y(!0)}},[Lt,m]);l.useEffect(()=>{const B=()=>{M&&Y(!1)};return window.addEventListener("click",B),()=>{window.removeEventListener("click",B)}},[M]),l.useEffect(()=>{var B;M&&((B=at.current)==null||B.focus())},[M]),l.useEffect(()=>{var nt;const B=((nt=g.current)==null?void 0:nt.querySelector(".editor-input"))??void 0,W=et=>{!M&&B&&document.activeElement===B&&et.key===c?(et.preventDefault(),qt()):M&&et.key==="Escape"&&(et.preventDefault(),Y(!1))};return document.addEventListener("keydown",W),()=>{document.removeEventListener("keydown",W)}},[M,qt,c]);const Kt=w["%footnoteEditor_copyButton_tooltip%"];return r.jsxs(r.Fragment,{children:[r.jsxs("div",{ref:p,className:"footnote-editor tw:grid tw:gap-[12px]",children:[r.jsxs("div",{className:"tw:flex",children:[r.jsxs("div",{className:"tw:flex tw:gap-4",children:[r.jsx(sl,{isTypeSwitchable:j,noteType:E,handleNoteTypeChange:Ut,localizedStrings:w}),r.jsx(ol,{callerType:f,updateCallerType:Bt,customCaller:C,updateCustomCaller:Qt,localizedStrings:w})]}),r.jsx("div",{className:"tw:flex tw:w-full tw:justify-end",children:r.jsxs(Gr,{children:[r.jsx($n,{onUndoClick:()=>{var B;return(B=u.current)==null?void 0:B.undo()},onRedoClick:()=>{var B;return(B=u.current)==null?void 0:B.redo()},canUndo:!R,canRedo:G,localizedStrings:w}),r.jsx($a,{onCancelClick:o,onAcceptClick:Ft,canAccept:!R||b!==f||f==="custom"&&C!==S,localizedStrings:w,acceptLabel:w["%footnoteEditor_saveButton_tooltip%"]})]})})]}),r.jsxs("div",{ref:g,className:"tw:relative tw:rounded-[6px] tw:border-2 tw:border-ring",children:[r.jsx("div",{className:t,children:r.jsx(An,{editorRef:u,canUndo:!R,canRedo:G,children:r.jsx(Xt.Editorial,{options:ot,onStateChange:Rt,onUsjChange:je,defaultUsj:ul,onScrRefChange:()=>{},scrRef:n,ref:u})})}),r.jsx("div",{className:"tw:absolute tw:bottom-0 tw:right-0",children:r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsx(F,{"aria-label":Kt,onClick:Tt,className:"tw:h-6 tw:w-6",variant:"ghost",size:"icon",children:r.jsx($.Copy,{})})}),r.jsx(Pt,{children:r.jsx("p",{children:Kt})})]})})})]})]}),r.jsx("div",{className:"tw:absolute",ref:m,style:{top:0,left:0,height:0,width:0}}),r.jsxs(se,{open:M,children:[r.jsx($o,{className:"tw:absolute",style:{top:St,left:lt,height:Et,width:0,pointerEvents:"none"}}),r.jsx(ce,{className:"tw:w-[500px] tw:p-0",onClick:B=>{B.preventDefault(),B.stopPropagation()},children:r.jsx(Ln,{markerMenuItems:Lt,localizedStrings:w,searchRef:at})})]})]})}const gl=Object.freeze([...Pn,...Object.entries(D.usfmMarkers).map(([,t])=>t.description).filter(t=>!!t),"%footnoteEditor_callerDropdown_item_custom%","%footnoteEditor_callerDropdown_item_generated%","%footnoteEditor_callerDropdown_item_hidden%","%footnoteEditor_callerDropdown_label%","%footnoteEditor_callerDropdown_tooltip%","%footnoteEditor_copyButton_tooltip%","%footnoteEditor_noteType_crossReference_label%","%footnoteEditor_noteType_endNote_label%","%footnoteEditor_noteType_footnote_label%","%footnoteEditor_noteType_tooltip%","%footnoteEditor_noteTypeDropdown_label%","%footnoteEditor_saveButton_tooltip%",...On,...Oa]);function Bn(t,e){if(!e||e.length===0)return t??"empty";const a=e.find(n=>typeof n=="string");if(a)return`key-${t??"unknown"}-${a.slice(0,10)}`;const o=typeof e[0]=="string"?"impossible":e[0].marker??"unknown";return`key-${t??"unknown"}-${o}`}function hl(t,e,a=!0,o=void 0){if(!e||e.length===0)return;const n=[],i=[];let s=[];return e.forEach(c=>{typeof c!="string"&&c.marker==="fp"?(s.length>0&&i.push(s),s=[c]):s.push(c)}),s.length>0&&i.push(s),i.map((c,w)=>{const d=w===i.length-1;return r.jsxs("p",{children:[Ba(t,c,a,!0,n),d&&o]},Bn(t,c))})}function Ba(t,e,a=!0,o=!0,n=[]){if(!(!e||e.length===0))return e.map(i=>{if(typeof i=="string"){const s=`${t}-text-${i.slice(0,10)}`;if(o){const c=h(`usfm_${t}`);return r.jsx("span",{className:c,children:i},s)}return r.jsxs("span",{className:"tw:inline-flex tw:items-center tw:gap-1 tw:underline tw:decoration-destructive",children:[r.jsx($.AlertCircle,{className:"tw:h-4 tw:w-4 tw:fill-destructive"}),r.jsx("span",{children:i}),r.jsx($.AlertCircle,{className:"tw:h-4 tw:w-4 tw:fill-destructive"})]},s)}return fl(i,Bn(`${t}\\${i.marker}`,[i]),a,[...n,t??"unknown"])})}function fl(t,e,a,o=[]){const{marker:n}=t;return r.jsxs("span",{children:[n?a&&r.jsx("span",{className:"marker",children:`\\${n} `}):r.jsx($.AlertCircle,{className:"tw:text-error tw:mr-1 tw:inline-block tw:h-4 tw:w-4","aria-label":"Missing marker"}),Ba(n,t.content,a,!0,[...o,n??"unknown"])]},e)}function Vn({footnote:t,layout:e="horizontal",formatCaller:a,showMarkers:o=!0}){const n=a?a(t.caller):t.caller,i=n!==t.caller;let s,c=t.content;Array.isArray(t.content)&&t.content.length>0&&typeof t.content[0]!="string"&&(t.content[0].marker==="fr"||t.content[0].marker==="xo")&&([s,...c]=t.content);const w=o?r.jsx("span",{className:"marker",children:`\\${t.marker} `}):void 0,d=o?r.jsx("span",{className:"marker",children:` \\${t.marker}*`}):void 0,u=n&&r.jsxs("span",{className:h("note-caller tw:inline-block",{formatted:i}),children:[n," "]}),g=s&&r.jsxs(r.Fragment,{children:[Ba(t.marker,[s],o,!1)," "]}),m=e==="horizontal"?"horizontal":"vertical",p=o?"marker-visible":"",f=e==="horizontal"?"tw:col-span-1":"tw:col-span-2 tw:col-start-1 tw:row-start-2",y=h(m,p);return r.jsxs(r.Fragment,{children:[r.jsxs("div",{className:h("textual-note-header tw:col-span-1 tw:w-fit tw:text-nowrap",y),children:[w,u]}),r.jsx("div",{className:h("textual-note-header tw:col-span-1 tw:w-fit tw:text-nowrap",y),children:g}),r.jsx("div",{className:h("textual-note-body tw:flex tw:flex-col tw:gap-1",f,y),children:c&&c.length>0&&r.jsx(r.Fragment,{children:hl(t.marker,c,o,d)})})]})}function ml({className:t,classNameForItems:e,footnotes:a,layout:o="horizontal",listId:n,selectedFootnote:i,showMarkers:s=!0,suppressFormatting:c=!1,formatCaller:w,onFootnoteSelected:d}){const u=w??D.getFormatCallerFunction(a,void 0),g=(C,T)=>{d==null||d(C,T,n)},m=i?a.findIndex(C=>C===i):-1,[p,f]=l.useState(m),y=(C,T,S)=>{if(a.length)switch(C.key){case"Enter":case" ":C.preventDefault(),d==null||d(T,S,n);break}},b=C=>{if(a.length)switch(C.key){case"ArrowDown":C.preventDefault(),f(T=>Math.min(T+1,a.length-1));break;case"ArrowUp":C.preventDefault(),f(T=>Math.max(T-1,0));break}},I=l.useRef([]);return l.useEffect(()=>{var C;p>=0&&p{const S=C===i,N=`${n}-${T}`;return r.jsxs(r.Fragment,{children:[r.jsx("li",{ref:E=>{I.current[T]=E},role:"option","aria-selected":S,"data-marker":C.marker,"data-state":S?"selected":void 0,tabIndex:T===p?0:-1,className:h("tw:gap-x-3 tw:gap-y-1 tw:p-2 tw:data-[state=selected]:bg-muted",d&&"tw:hover:bg-muted/50","tw:w-full tw:rounded-sm tw:border-0 tw:bg-transparent tw:shadow-none","tw:focus:outline-hidden tw:focus-visible:outline-hidden","tw:focus-visible:ring-offset-0.5 tw:focus-visible:relative tw:focus-visible:z-10 tw:focus-visible:ring-2 tw:focus-visible:ring-ring","tw:grid tw:grid-flow-col tw:grid-cols-subgrid",o==="horizontal"?"tw:col-span-3":"tw:col-span-2 tw:row-span-2",e),onClick:()=>g(C,T),onKeyDown:E=>y(E,C,T),children:r.jsx(Vn,{footnote:C,layout:o,formatCaller:()=>u(C.caller,T),showMarkers:s})},N),Ta&&e.push(t.substring(a,n.index)),e.push(r.jsx("strong",{children:n[1]},n.index)),a=o.lastIndex;return a0?e:[t]}function bl({occurrenceData:t,setScriptureReference:e,localizedStrings:a,classNameForText:o}){const n=a["%webView_inventory_occurrences_table_header_reference%"],i=a["%webView_inventory_occurrences_table_header_occurrence%"],s=l.useMemo(()=>{const c=[],w=new Set;return t.forEach(d=>{const u=`${d.reference.book}:${d.reference.chapterNum}:${d.reference.verseNum}:${d.text}`;w.has(u)||(w.add(u),c.push(d))}),c},[t]);return r.jsxs(Ur,{stickyHeader:!0,children:[r.jsx(qr,{stickyHeader:!0,children:r.jsxs(be,{children:[r.jsx(dr,{children:n}),r.jsx(dr,{children:i})]})}),r.jsx(Kr,{children:s.length>0&&s.map(c=>r.jsxs(be,{onClick:()=>{e(c.reference)},children:[r.jsx(Oe,{children:D.formatScrRef(c.reference,"English")}),r.jsx(Oe,{className:o,children:vl(c.text)})]},`${c.reference.book} ${c.reference.chapterNum}:${c.reference.verseNum}-${c.text}`))})]})}function Va({className:t,...e}){return r.jsx(k.Checkbox.Root,{"data-slot":"checkbox",className:h("pr-twp tw:peer tw:relative tw:flex tw:size-4 tw:shrink-0 tw:items-center tw:justify-center tw:rounded-[4px] tw:border tw:border-input tw:transition-colors tw:outline-none tw:group-has-disabled/field:opacity-50 tw:after:absolute tw:after:-inset-x-3 tw:after:-inset-y-2 tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:disabled:cursor-not-allowed tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:aria-invalid:aria-checked:border-primary tw:dark:bg-input/30 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:data-checked:border-primary tw:data-checked:bg-primary tw:data-checked:text-primary-foreground tw:dark:data-checked:bg-primary",t),...e,children:r.jsx(k.Checkbox.Indicator,{"data-slot":"checkbox-indicator",className:"tw:grid tw:place-content-center tw:text-current tw:transition-none tw:[&>svg]:size-3.5",children:r.jsx(ht.IconCheck,{})})})}const xl=t=>{if(t==="asc")return r.jsx($.ArrowUpIcon,{className:"tw:h-4 tw:w-4"});if(t==="desc")return r.jsx($.ArrowDownIcon,{className:"tw:h-4 tw:w-4"})},ur=(t,e,a)=>r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsxs(At,{className:h("tw:flex tw:w-full tw:justify-start",a),variant:"ghost",onClick:()=>t.toggleSorting(void 0),children:[r.jsx("span",{className:"tw:w-6 tw:max-w-fit tw:flex-1 tw:overflow-hidden tw:text-ellipsis",children:e}),xl(t.getIsSorted())]}),r.jsx(Pt,{side:"bottom",children:e})]})}),yl=t=>({accessorKey:"item",accessorFn:e=>e.items[0],header:({column:e})=>ur(e,t)}),kl=(t,e)=>({accessorKey:`item${e}`,accessorFn:a=>a.items[e],header:({column:a})=>ur(a,t)}),jl=t=>({accessorKey:"count",header:({column:e})=>ur(e,t,"tw:justify-end"),cell:({row:e})=>r.jsx("div",{className:"tw:flex tw:justify-end tw:tabular-nums",children:e.getValue("count")})}),na=(t,e,a,o,n,i)=>{let s=[...a];t.forEach(w=>{e==="approved"?s.includes(w)||s.push(w):s=s.filter(d=>d!==w)}),o(s);let c=[...n];t.forEach(w=>{e==="unapproved"?c.includes(w)||c.push(w):c=c.filter(d=>d!==w)}),i(c)},_l=(t,e,a,o,n)=>({accessorKey:"status",header:({column:i})=>ur(i,t,"tw:justify-center"),cell:({row:i})=>{const s=i.getValue("status"),c=i.getValue("item");return r.jsxs(Da,{value:s,variant:"outline",type:"single",className:"tw:gap-0",children:[r.jsx(sr,{onClick:w=>{w.stopPropagation(),na([c],"approved",e,a,o,n)},value:"approved",className:"tw:rounded-e-none tw:border-e-0",children:r.jsx($.CircleCheckIcon,{})}),r.jsx(sr,{onClick:w=>{w.stopPropagation(),na([c],"unapproved",e,a,o,n)},value:"unapproved",className:"tw:rounded-none",children:r.jsx($.CircleXIcon,{})}),r.jsx(sr,{onClick:w=>{w.stopPropagation(),na([c],"unknown",e,a,o,n)},value:"unknown",className:"tw:rounded-s-none tw:border-s-0",children:r.jsx($.CircleHelpIcon,{})})]})}}),Nl=t=>t.split(/(?:\r?\n|\r)|(?=(?:\\(?:v|c|id)))/g),Cl=t=>{const e=/^\\[vc]\s+(\d+)/,a=t.match(e);if(a)return+a[1]},Sl=t=>{const e=t.match(/^\\id\s+([A-Za-z]+)/);return e?e[1]:""},Fn=(t,e,a)=>a.includes(t)?"unapproved":e.includes(t)?"approved":"unknown",El=Object.freeze(["%webView_inventory_all%","%webView_inventory_approved%","%webView_inventory_unapproved%","%webView_inventory_unknown%","%webView_inventory_scope_currentBook%","%webView_inventory_scope_chapter%","%webView_inventory_scope_verse%","%webView_inventory_filter_text%","%webView_inventory_show_additional_items%","%webView_inventory_occurrences_table_header_reference%","%webView_inventory_occurrences_table_header_occurrence%","%webView_inventory_no_results%"]),Tl=(t,e,a)=>{let o=t;return e!=="all"&&(o=o.filter(n=>e==="approved"&&n.status==="approved"||e==="unapproved"&&n.status==="unapproved"||e==="unknown"&&n.status==="unknown")),a!==""&&(o=o.filter(n=>n.items[0].includes(a))),o},Rl=(t,e,a)=>t.map(o=>{const n=D.isString(o.key)?o.key:o.key[0];return{items:D.isString(o.key)?[o.key]:o.key,count:o.count,status:o.status||Fn(n,e,a),occurrences:o.occurrences||[]}}),le=(t,e)=>t[e]??e;function zl({inventoryItems:t,setVerseRef:e,localizedStrings:a,additionalItemsLabels:o,approvedItems:n,unapprovedItems:i,scope:s,onScopeChange:c,columns:w,id:d,areInventoryItemsLoading:u=!1,classNameForVerseText:g,onItemSelected:m}){const p=le(a,"%webView_inventory_all%"),f=le(a,"%webView_inventory_approved%"),y=le(a,"%webView_inventory_unapproved%"),b=le(a,"%webView_inventory_unknown%"),I=le(a,"%webView_inventory_scope_currentBook%"),C=le(a,"%webView_inventory_scope_chapter%"),T=le(a,"%webView_inventory_scope_verse%"),S=le(a,"%webView_inventory_filter_text%"),N=le(a,"%webView_inventory_show_additional_items%"),E=le(a,"%webView_inventory_no_results%"),[z,j]=l.useState(!1),[x,R]=l.useState("all"),[P,G]=l.useState(""),[q,V]=l.useState([]),K=l.useMemo(()=>{const U=t??[];return U.length===0?[]:Rl(U,n,i)},[t,n,i]),M=l.useMemo(()=>{if(z)return K;const U=[];return K.forEach(tt=>{const rt=tt.items[0],at=U.find(ot=>ot.items[0]===rt);at?(at.count+=tt.count,at.occurrences=at.occurrences.concat(tt.occurrences)):U.push({items:[rt],count:tt.count,occurrences:tt.occurrences,status:tt.status})}),U},[z,K]),Y=l.useMemo(()=>M.length===0?[]:Tl(M,x,P),[M,x,P]),lt=l.useMemo(()=>{var rt,at;if(!z)return w;const U=(rt=o==null?void 0:o.tableHeaders)==null?void 0:rt.length;if(!U)return w;const tt=[];for(let ot=0;ot{Y.length===0?V([]):Y.length===1&&V(Y[0].items)},[Y]);const kt=(U,tt)=>{tt.setRowSelection(()=>{const at={};return at[U.index]=!0,at});const rt=U.original.items;V(rt),m&&rt.length>0&&m(rt[0])},St=U=>{if(U==="book"||U==="chapter"||U==="verse")c(U);else throw new Error(`Invalid scope value: ${U}`)},J=U=>{if(U==="all"||U==="approved"||U==="unapproved"||U==="unknown")R(U);else throw new Error(`Invalid status filter value: ${U}`)},Et=l.useMemo(()=>{if(M.length===0||q.length===0)return[];const U=M.filter(tt=>D.deepEqual(z?tt.items:[tt.items[0]],q));if(U.length>1)throw new Error("Selected item is not unique");return U.length===0?[]:U[0].occurrences},[q,z,M]);return r.jsx("div",{id:d,className:"pr-twp tw:h-full tw:overflow-auto",children:r.jsxs("div",{className:"tw:flex tw:h-full tw:w-full tw:min-w-min tw:flex-col",children:[r.jsxs("div",{className:"tw:flex tw:items-stretch",style:{contain:"inline-size"},children:[r.jsxs(Ae,{onValueChange:U=>J(U),defaultValue:x,children:[r.jsx(Le,{className:"tw:m-1 tw:w-auto tw:flex-1",children:r.jsx(Pe,{placeholder:"Select filter"})}),r.jsxs(Be,{children:[r.jsx(Jt,{value:"all",children:p}),r.jsx(Jt,{value:"approved",children:f}),r.jsx(Jt,{value:"unapproved",children:y}),r.jsx(Jt,{value:"unknown",children:b})]})]}),r.jsxs(Ae,{onValueChange:U=>St(U),defaultValue:s,children:[r.jsx(Le,{className:"tw:m-1 tw:w-auto tw:flex-1",children:r.jsx(Pe,{placeholder:"Select scope"})}),r.jsxs(Be,{children:[r.jsx(Jt,{value:"book",children:I}),r.jsx(Jt,{value:"chapter",children:C}),r.jsx(Jt,{value:"verse",children:T})]})]}),r.jsx(Xe,{className:"tw:m-1 tw:flex-1 tw:rounded-md tw:border",placeholder:S,value:P,onChange:U=>{G(U.target.value)}}),o&&r.jsx(Ot,{children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:r.jsxs("div",{className:"tw:m-1 tw:flex tw:w-fit tw:min-w-[26px] tw:items-center tw:rounded-md tw:border",children:[r.jsx(Va,{className:"tw:m-1 tw:shrink-0",checked:z,onCheckedChange:U=>{j(U)}}),r.jsx(yt,{className:"tw:m-1 tw:truncate",children:(o==null?void 0:o.checkboxText)??N})]})}),r.jsx(Pt,{children:(o==null?void 0:o.checkboxText)??N})]})})]}),r.jsx("div",{className:"tw:m-1 tw:flex-1 tw:overflow-auto tw:rounded-md tw:border",children:r.jsx(Tn,{columns:lt,data:Y,onRowClickHandler:kt,stickyHeader:!0,isLoading:u,noResultsMessage:E})}),Et.length>0&&r.jsx("div",{className:"tw:m-1 tw:flex-1 tw:overflow-auto tw:rounded-md tw:border",children:r.jsx(bl,{classNameForText:g,occurrenceData:Et,setScriptureReference:e,localizedStrings:a})})]})})}const Dl="16rem",Il="3rem",Gn=l.createContext(void 0);function pr(){const t=l.useContext(Gn);if(!t)throw new Error("useSidebar must be used within a SidebarProvider.");return t}function Un({defaultOpen:t=!0,open:e,onOpenChange:a,className:o,style:n,children:i,side:s="primary",...c}){const[w,d]=l.useState(t),u=e??w,g=l.useCallback(T=>{const S=typeof T=="function"?T(u):T;a?a(S):d(S)},[a,u]),m=l.useCallback(()=>g(T=>!T),[g]),p=u?"expanded":"collapsed",b=ft()==="ltr"?s:s==="primary"?"secondary":"primary",I=l.useMemo(()=>({state:p,open:u,setOpen:g,toggleSidebar:m,side:b}),[p,u,g,m,b]),C={"--sidebar-width":Dl,"--sidebar-width-icon":Il,...n};return r.jsx(Gn.Provider,{value:I,children:r.jsx("div",{"data-slot":"sidebar-wrapper",style:C,className:h("pr-twp tw:group/sidebar-wrapper tw:flex tw:w-full tw:has-data-[variant=inset]:bg-sidebar",o),...c,children:i})})}function qn({variant:t="sidebar",collapsible:e="offcanvas",className:a,children:o,...n}){const i=pr();return e==="none"?r.jsx("div",{"data-slot":"sidebar",className:h("tw:flex tw:h-full tw:w-(--sidebar-width) tw:flex-col tw:bg-sidebar tw:text-sidebar-foreground",a),...n,children:o}):r.jsxs("div",{className:"tw:group tw:peer tw:hidden tw:text-sidebar-foreground tw:md:block","data-state":i.state,"data-collapsible":i.state==="collapsed"?e:"","data-variant":t,"data-side":i.side,"data-slot":"sidebar",children:[r.jsx("div",{"data-slot":"sidebar-gap",className:h("tw:relative tw:w-(--sidebar-width) tw:bg-transparent tw:transition-[width] tw:duration-200 tw:ease-linear","tw:group-data-[collapsible=offcanvas]:w-0","tw:group-data-[side=secondary]:rotate-180",t==="floating"||t==="inset"?"tw:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]":"tw:group-data-[collapsible=icon]:w-(--sidebar-width-icon)")}),r.jsx("div",{"data-slot":"sidebar-container","data-side":i.side,className:h("tw:absolute tw:inset-y-0 tw:z-10 tw:hidden tw:h-svh tw:w-(--sidebar-width) tw:transition-[left,right,width] tw:duration-200 tw:ease-linear tw:md:flex",i.side==="primary"?"tw:left-0 tw:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]":"tw:right-0 tw:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",t==="floating"||t==="inset"?"tw:p-2 tw:group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]":"tw:group-data-[collapsible=icon]:w-(--sidebar-width-icon) tw:group-data-[side=primary]:border-e tw:group-data-[side=secondary]:border-s",a),...n,children:r.jsx("div",{"data-sidebar":"sidebar","data-slot":"sidebar-inner",className:"tw:flex tw:size-full tw:flex-col tw:bg-sidebar tw:group-data-[variant=floating]:rounded-lg tw:group-data-[variant=floating]:shadow-sm tw:group-data-[variant=floating]:ring-1 tw:group-data-[variant=floating]:ring-sidebar-border",children:o})})]})}function Ml({className:t,onClick:e,...a}){const{toggleSidebar:o,side:n}=pr();return r.jsxs(F,{"data-sidebar":"trigger","data-slot":"sidebar-trigger",variant:"ghost",size:"icon-sm",className:h(t),onClick:i=>{e==null||e(i),o()},...a,children:[n==="primary"?r.jsx(ht.IconLayoutSidebar,{}):r.jsx(ht.IconLayoutSidebarRight,{}),r.jsx("span",{className:"tw:sr-only",children:"Toggle Sidebar"})]})}function Ol({className:t,...e}){const{toggleSidebar:a}=pr();return r.jsx("button",{type:"button","data-sidebar":"rail","data-slot":"sidebar-rail","aria-label":"Toggle Sidebar",tabIndex:-1,onClick:a,title:"Toggle Sidebar",className:h("tw:absolute tw:inset-y-0 tw:z-20 tw:hidden tw:w-4 tw:transition-all tw:ease-linear tw:group-data-[side=primary]:-right-4 tw:group-data-[side=secondary]:left-0 tw:after:absolute tw:after:inset-y-0 tw:after:start-1/2 tw:after:w-[2px] tw:hover:after:bg-sidebar-border tw:sm:flex tw:ltr:-translate-x-1/2 tw:rtl:translate-x-1/2","tw:in-data-[side=primary]:cursor-w-resize tw:rtl:in-data-[side=primary]:cursor-e-resize tw:in-data-[side=secondary]:cursor-e-resize tw:rtl:in-data-[side=secondary]:cursor-w-resize","tw:[[data-side=primary][data-state=collapsed]_&]:cursor-e-resize tw:rtl:[[data-side=primary][data-state=collapsed]_&]:cursor-w-resize tw:[[data-side=secondary][data-state=collapsed]_&]:cursor-w-resize tw:rtl:[[data-side=secondary][data-state=collapsed]_&]:cursor-e-resize","tw:group-data-[collapsible=offcanvas]:translate-x-0 tw:group-data-[collapsible=offcanvas]:after:start-full tw:hover:group-data-[collapsible=offcanvas]:bg-sidebar","tw:[[data-side=primary][data-collapsible=offcanvas]_&]:-end-2","tw:[[data-side=secondary][data-collapsible=offcanvas]_&]:-start-2",t),...e})}function Kn({className:t,...e}){return r.jsx("main",{"data-slot":"sidebar-inset",className:h("tw:relative tw:flex tw:w-full tw:flex-1 tw:flex-col tw:bg-background tw:md:peer-data-[variant=inset]:m-2 tw:md:peer-data-[variant=inset]:ms-0 tw:md:peer-data-[variant=inset]:rounded-xl tw:md:peer-data-[variant=inset]:shadow-sm tw:md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ms-2",t),...e})}function $l({className:t,...e}){return r.jsx(Xe,{"data-slot":"sidebar-input","data-sidebar":"input",className:h("tw:h-8 tw:w-full tw:bg-background tw:shadow-none",t),...e})}function Al({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-header","data-sidebar":"header",className:h("tw:flex tw:flex-col tw:gap-2 tw:p-2",t),...e})}function Pl({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-footer","data-sidebar":"footer",className:h("tw:flex tw:flex-col tw:gap-2 tw:p-2",t),...e})}function Ll({className:t,...e}){return r.jsx($e,{"data-slot":"sidebar-separator","data-sidebar":"separator",className:h("tw:mx-2 tw:w-auto tw:bg-sidebar-border",t),...e})}function Hn({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-content","data-sidebar":"content",className:h("tw:no-scrollbar tw:flex tw:min-h-0 tw:flex-1 tw:flex-col tw:gap-0 tw:overflow-auto tw:group-data-[collapsible=icon]:overflow-hidden",t),...e})}function fa({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-group","data-sidebar":"group",className:h("tw:relative tw:flex tw:w-full tw:min-w-0 tw:flex-col tw:p-2",t),...e})}function ma({className:t,asChild:e=!1,...a}){const o=e?k.Slot.Root:"div";return r.jsx(o,{"data-slot":"sidebar-group-label","data-sidebar":"group-label",className:h("tw:flex tw:h-8 tw:shrink-0 tw:items-center tw:rounded-md tw:px-2 tw:text-xs tw:font-medium tw:text-sidebar-foreground/70 tw:ring-sidebar-ring tw:outline-hidden tw:transition-[margin,opacity] tw:duration-200 tw:ease-linear tw:group-data-[collapsible=icon]:-mt-8 tw:group-data-[collapsible=icon]:opacity-0 tw:focus-visible:ring-2 tw:[&>svg]:size-4 tw:[&>svg]:shrink-0",t),...a})}function Bl({className:t,asChild:e=!1,...a}){const o=e?k.Slot.Root:"button";return r.jsx(o,{"data-slot":"sidebar-group-action","data-sidebar":"group-action",className:h("tw:absolute tw:top-3.5 tw:end-3 tw:flex tw:aspect-square tw:w-5 tw:items-center tw:justify-center tw:rounded-md tw:p-0 tw:text-sidebar-foreground tw:ring-sidebar-ring tw:outline-hidden tw:transition-transform tw:group-data-[collapsible=icon]:hidden tw:after:absolute tw:after:-inset-2 tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground tw:focus-visible:ring-2 tw:md:after:hidden tw:[&>svg]:size-4 tw:[&>svg]:shrink-0",t),...a})}function va({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-group-content","data-sidebar":"group-content",className:h("tw:w-full tw:text-sm",t),...e})}function Yn({className:t,...e}){return r.jsx("ul",{"data-slot":"sidebar-menu","data-sidebar":"menu",className:h("tw:flex tw:w-full tw:min-w-0 tw:flex-col tw:gap-0",t),...e})}function Wn({className:t,...e}){return r.jsx("li",{"data-slot":"sidebar-menu-item","data-sidebar":"menu-item",className:h("tw:group/menu-item tw:relative",t),...e})}const Vl=ge.cva("tw:peer/menu-button tw:group/menu-button tw:flex tw:w-full tw:items-center tw:gap-2 tw:overflow-hidden tw:rounded-md tw:p-2 tw:text-start tw:text-sm tw:ring-sidebar-ring tw:outline-hidden tw:transition-[width,height,padding] tw:group-has-data-[sidebar=menu-action]/menu-item:pe-8 tw:group-data-[collapsible=icon]:size-8! tw:group-data-[collapsible=icon]:p-2! tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground tw:focus-visible:ring-2 tw:active:bg-sidebar-accent tw:active:text-sidebar-accent-foreground tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-disabled:pointer-events-none tw:aria-disabled:opacity-50 tw:data-open:hover:bg-sidebar-accent tw:data-open:hover:text-sidebar-accent-foreground tw:data-active:bg-sidebar-accent tw:data-active:font-medium tw:data-active:text-sidebar-accent-foreground tw:[&_svg]:size-4 tw:[&_svg]:shrink-0 tw:[&>span:last-child]:truncate",{variants:{variant:{default:"tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground",outline:"tw:bg-background tw:shadow-[0_0_0_1px_var(--sidebar-border)] tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground tw:hover:shadow-[0_0_0_1px_var(--sidebar-accent)]"},size:{default:"tw:h-8 tw:text-sm",sm:"tw:h-7 tw:text-xs",lg:"tw:h-12 tw:text-sm tw:group-data-[collapsible=icon]:p-0!"}},defaultVariants:{variant:"default",size:"default"}});function Xn({asChild:t=!1,isActive:e=!1,variant:a="default",size:o="default",tooltip:n,className:i,...s}){const c=t?k.Slot.Root:"button",{state:w}=pr(),d=r.jsx(c,{"data-slot":"sidebar-menu-button","data-sidebar":"menu-button","data-size":o,"data-active":e,className:h(Vl({variant:a,size:o}),i),...s});if(!n)return d;const u=typeof n=="string"?{children:n}:n;return r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:d}),r.jsx(Pt,{side:"right",align:"center",hidden:w!=="collapsed",...u})]})}function Fl({className:t,asChild:e=!1,showOnHover:a=!1,...o}){const n=e?k.Slot.Root:"button";return r.jsx(n,{"data-slot":"sidebar-menu-action","data-sidebar":"menu-action",className:h("tw:absolute tw:top-1.5 tw:end-1 tw:flex tw:aspect-square tw:w-5 tw:items-center tw:justify-center tw:rounded-md tw:p-0 tw:text-sidebar-foreground tw:ring-sidebar-ring tw:outline-hidden tw:transition-transform tw:group-data-[collapsible=icon]:hidden tw:peer-hover/menu-button:text-sidebar-accent-foreground tw:peer-data-[size=default]/menu-button:top-1.5 tw:peer-data-[size=lg]/menu-button:top-2.5 tw:peer-data-[size=sm]/menu-button:top-1 tw:after:absolute tw:after:-inset-2 tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground tw:focus-visible:ring-2 tw:md:after:hidden tw:[&>svg]:size-4 tw:[&>svg]:shrink-0",a&&"tw:group-focus-within/menu-item:opacity-100 tw:group-hover/menu-item:opacity-100 tw:peer-data-active/menu-button:text-sidebar-accent-foreground tw:aria-expanded:opacity-100 tw:md:opacity-0",t),...o})}function Gl({className:t,...e}){return r.jsx("div",{"data-slot":"sidebar-menu-badge","data-sidebar":"menu-badge",className:h("tw:pointer-events-none tw:absolute tw:end-1 tw:flex tw:h-5 tw:min-w-5 tw:items-center tw:justify-center tw:rounded-md tw:px-1 tw:text-xs tw:font-medium tw:text-sidebar-foreground tw:tabular-nums tw:select-none tw:group-data-[collapsible=icon]:hidden tw:peer-hover/menu-button:text-sidebar-accent-foreground tw:peer-data-[size=default]/menu-button:top-1.5 tw:peer-data-[size=lg]/menu-button:top-2.5 tw:peer-data-[size=sm]/menu-button:top-1 tw:peer-data-active/menu-button:text-sidebar-accent-foreground",t),...e})}function Ul({className:t,showIcon:e=!1,...a}){const[o]=l.useState(()=>`${Math.floor(Math.random()*40)+50}%`),n={"--skeleton-width":o};return r.jsxs("div",{"data-slot":"sidebar-menu-skeleton","data-sidebar":"menu-skeleton",className:h("tw:flex tw:h-8 tw:items-center tw:gap-2 tw:rounded-md tw:px-2",t),...a,children:[e&&r.jsx(Pr,{className:"tw:size-4 tw:rounded-md","data-sidebar":"menu-skeleton-icon"}),r.jsx(Pr,{className:"tw:h-4 tw:max-w-(--skeleton-width) tw:flex-1","data-sidebar":"menu-skeleton-text",style:n})]})}function ql({className:t,...e}){return r.jsx("ul",{"data-slot":"sidebar-menu-sub","data-sidebar":"menu-sub",className:h("tw:mx-3.5 tw:flex tw:min-w-0 tw:translate-x-px tw:rtl:-translate-x-px tw:flex-col tw:gap-1 tw:border-s tw:border-sidebar-border tw:px-2.5 tw:py-0.5 tw:group-data-[collapsible=icon]:hidden",t),...e})}function Kl({className:t,...e}){return r.jsx("li",{"data-slot":"sidebar-menu-sub-item","data-sidebar":"menu-sub-item",className:h("tw:group/menu-sub-item tw:relative",t),...e})}function Hl({asChild:t=!1,size:e="md",isActive:a=!1,className:o,...n}){const i=t?k.Slot.Root:"a";return r.jsx(i,{"data-slot":"sidebar-menu-sub-button","data-sidebar":"menu-sub-button","data-size":e,"data-active":a,className:h("tw:flex tw:h-7 tw:min-w-0 tw:-translate-x-px tw:rtl:translate-x-px tw:items-center tw:gap-2 tw:overflow-hidden tw:rounded-md tw:px-2 tw:text-sidebar-foreground tw:ring-sidebar-ring tw:outline-hidden tw:group-data-[collapsible=icon]:hidden tw:hover:bg-sidebar-accent tw:hover:text-sidebar-accent-foreground tw:focus-visible:ring-2 tw:active:bg-sidebar-accent tw:active:text-sidebar-accent-foreground tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-disabled:pointer-events-none tw:aria-disabled:opacity-50 tw:data-[size=md]:text-sm tw:data-[size=sm]:text-xs tw:data-active:bg-sidebar-accent tw:data-active:text-sidebar-accent-foreground tw:[&>span:last-child]:truncate tw:[&>svg]:size-4 tw:[&>svg]:shrink-0 tw:[&>svg]:text-sidebar-accent-foreground",o),...n})}function Zn({id:t,extensionLabels:e,projectInfo:a,handleSelectSidebarItem:o,selectedSidebarItem:n,extensionsSidebarGroupLabel:i,projectsSidebarGroupLabel:s,buttonPlaceholderText:c,className:w}){const d=l.useCallback((p,f)=>{o(p,f)},[o]),u=l.useCallback(p=>{const f=a.find(y=>y.projectId===p);return f?f.projectName:p},[a]),g=l.useMemo(()=>a.map(p=>({id:p.projectId,shortName:p.projectName,fullName:p.projectName})),[a]),m=l.useCallback(p=>!n.projectId&&p===n.label,[n]);return r.jsx(qn,{id:t,collapsible:"none",variant:"inset",className:h("tw:w-96 tw:gap-2 tw:overflow-y-auto",w),children:r.jsxs(Hn,{children:[r.jsxs(fa,{children:[r.jsx(ma,{className:"tw:text-sm",children:i}),r.jsx(va,{children:r.jsx(Yn,{children:Object.entries(e).map(([p,f])=>r.jsx(Wn,{children:r.jsx(Xn,{onClick:()=>d(p),isActive:m(p),children:r.jsx("span",{className:"tw:pl-3",children:f})})},p))})})]}),r.jsxs(fa,{children:[r.jsx(ma,{className:"tw:text-sm",children:s}),r.jsx(va,{className:"tw:pl-3",children:r.jsxs("div",{className:h("tw:flex tw:w-full tw:items-center tw:gap-2 tw:rounded-md tw:px-2 tw:py-1",{"tw:bg-sidebar-accent tw:text-sidebar-accent-foreground":n==null?void 0:n.projectId}),children:[r.jsx($.ScrollText,{className:"tw:h-4 tw:w-4 tw:shrink-0"}),r.jsx(Rn,{mode:"project",projects:g,openTabs:[],selection:{projectId:(n==null?void 0:n.projectId)??""},onChangeSelection:({projectId:p})=>{if(!p)return;const f=u(p);d(f,p)},buttonVariant:"ghost",buttonClassName:"tw:h-8 tw:w-full tw:flex-1 tw:justify-start tw:font-normal",buttonPlaceholder:c,ariaLabel:s,popoverContentStyle:{zIndex:Vr}})]})})]})]})})}const Hr=l.forwardRef(({value:t,onSearch:e,placeholder:a,isFullWidth:o,className:n,isDisabled:i=!1,id:s},c)=>{const w=ft();return r.jsxs("div",{id:s,className:h("tw:relative",{"tw:w-full":o},n),children:[r.jsx($.Search,{className:h("tw:absolute tw:top-1/2 tw:h-4 tw:w-4 tw:-translate-y-1/2 tw:transform tw:opacity-50",{"tw:right-3":w==="rtl"},{"tw:left-3":w==="ltr"})}),r.jsx(Xe,{ref:c,className:"tw:w-full tw:text-ellipsis tw:pe-9 tw:ps-9",placeholder:a,value:t,onChange:d=>e(d.target.value),disabled:i}),t&&r.jsxs(F,{variant:"ghost",size:"icon",className:h("tw:absolute tw:top-1/2 tw:h-7 tw:-translate-y-1/2 tw:transform tw:hover:bg-transparent",{"tw:left-0":w==="rtl"},{"tw:right-0":w==="ltr"}),onClick:()=>{e("")},children:[r.jsx($.X,{className:"tw:h-4 tw:w-4"}),r.jsx("span",{className:"tw:sr-only",children:"Clear"})]})]})});Hr.displayName="SearchBar";function Yl({id:t,extensionLabels:e,projectInfo:a,children:o,handleSelectSidebarItem:n,selectedSidebarItem:i,searchValue:s,onSearch:c,extensionsSidebarGroupLabel:w,projectsSidebarGroupLabel:d,buttonPlaceholderText:u}){return r.jsxs("div",{className:"tw:box-border tw:flex tw:h-full tw:flex-col",children:[r.jsx("div",{className:"tw:box-border tw:flex tw:items-center tw:justify-center tw:py-4",children:r.jsx(Hr,{className:"tw:w-9/12",value:s,onSearch:c,placeholder:"Search app settings, extension settings, and project settings"})}),r.jsxs(Un,{id:t,className:"tw:h-full tw:flex-1 tw:gap-4 tw:overflow-auto tw:border-t",children:[r.jsx(Zn,{className:"tw:w-1/2 tw:min-w-[140px] tw:max-w-[220px] tw:border-e",extensionLabels:e,projectInfo:a,handleSelectSidebarItem:n,selectedSidebarItem:i,extensionsSidebarGroupLabel:w,projectsSidebarGroupLabel:d,buttonPlaceholderText:u}),r.jsx(Kn,{className:"tw:min-w-[215px]",children:o})]})]})}const Ne="scrBook",Wl="scrRef",Me="source",Xl="details",Zl="Scripture Reference",Jl="Scripture Book",Jn="Type",Ql="Details";function td(t,e){const a=e??!1;return[{accessorFn:o=>`${o.start.book} ${o.start.chapterNum}:${o.start.verseNum}`,id:Ne,header:(t==null?void 0:t.scriptureReferenceColumnName)??Zl,cell:o=>{const n=o.row.original;return o.row.getIsGrouped()?st.Canon.bookIdToEnglishName(n.start.book):o.row.groupingColumnId===Ne?D.formatScrRef(n.start):void 0},getGroupingValue:o=>st.Canon.bookIdToNumber(o.start.book),sortingFn:(o,n)=>D.compareScrRefs(o.original.start,n.original.start),enableGrouping:!0},{accessorFn:o=>D.formatScrRef(o.start),id:Wl,header:void 0,cell:o=>{const n=o.row.original;return o.row.getIsGrouped()?void 0:D.formatScrRef(n.start)},sortingFn:(o,n)=>D.compareScrRefs(o.original.start,n.original.start),enableGrouping:!1},{accessorFn:o=>o.source.displayName,id:Me,header:a?(t==null?void 0:t.typeColumnName)??Jn:void 0,cell:o=>a||o.row.getIsGrouped()?o.getValue():void 0,getGroupingValue:o=>o.source.id,sortingFn:(o,n)=>o.original.source.displayName.localeCompare(n.original.source.displayName),enableGrouping:!0},{accessorFn:o=>o.detail,id:Xl,header:(t==null?void 0:t.detailsColumnName)??Ql,cell:o=>o.getValue(),enableGrouping:!1}]}const ed=t=>{if(!("offset"in t.start))throw new Error("No offset available in range start");if(t.end&&!("offset"in t.end))throw new Error("No offset available in range end");const{offset:e}=t.start;let a=0;return t.end&&({offset:a}=t.end),!t.end||D.compareScrRefs(t.start,t.end)===0?`${D.scrRefToBBBCCCVVV(t.start)}+${e}`:`${D.scrRefToBBBCCCVVV(t.start)}+${e}-${D.scrRefToBBBCCCVVV(t.end)}+${a}`},ko=t=>`${ed({start:t.start,end:t.end})} ${t.source.displayName} ${t.detail}`;function rd({sources:t,showColumnHeaders:e=!1,showSourceColumn:a=!1,scriptureReferenceColumnName:o,scriptureBookGroupName:n,typeColumnName:i,detailsColumnName:s,onRowSelected:c,id:w}){const[d,u]=l.useState([]),[g,m]=l.useState([{id:Ne,desc:!1}]),[p,f]=l.useState({}),y=l.useMemo(()=>t.flatMap(x=>x.data.map(R=>({...R,source:x.source}))),[t]),b=l.useMemo(()=>td({scriptureReferenceColumnName:o,typeColumnName:i,detailsColumnName:s},a),[o,i,s,a]);l.useEffect(()=>{d.includes(Me)?m([{id:Me,desc:!1},{id:Ne,desc:!1}]):m([{id:Ne,desc:!1}])},[d]);const I=Mt.useReactTable({data:y,columns:b,state:{grouping:d,sorting:g,rowSelection:p},onGroupingChange:u,onSortingChange:m,onRowSelectionChange:f,getExpandedRowModel:Mt.getExpandedRowModel(),getGroupedRowModel:Mt.getGroupedRowModel(),getCoreRowModel:Mt.getCoreRowModel(),getSortedRowModel:Mt.getSortedRowModel(),getRowId:ko,autoResetExpanded:!1,enableMultiRowSelection:!1,enableSubRowSelection:!1});l.useEffect(()=>{if(c){const x=I.getSelectedRowModel().rowsById,R=Object.keys(x);if(R.length===1){const P=y.find(G=>ko(G)===R[0])||void 0;P&&c(P)}}},[p,y,c,I]);const C=n??Jl,T=i??Jn,S=[{label:"No Grouping",value:[]},{label:`Group by ${C}`,value:[Ne]},{label:`Group by ${T}`,value:[Me]},{label:`Group by ${C} and ${T}`,value:[Ne,Me]},{label:`Group by ${T} and ${C}`,value:[Me,Ne]}],N=x=>{u(JSON.parse(x))},E=(x,R)=>{!x.getIsGrouped()&&!x.getIsSelected()&&x.getToggleSelectedHandler()(R)},z=(x,R)=>x.getIsGrouped()?"":h("banded-row",R%2===0?"even":"odd"),j=(x,R,P)=>{if(!((x==null?void 0:x.length)===0||R.depth{N(x)},children:[r.jsx(Le,{className:"tw:mb-1 tw:mt-2",children:r.jsx(Pe,{})}),r.jsx(Be,{position:"item-aligned",children:r.jsx(Cn,{children:S.map(x=>r.jsx(Jt,{value:JSON.stringify(x.value),children:x.label},x.label))})})]}),r.jsxs(Ur,{className:"tw:relative tw:flex tw:flex-col tw:overflow-y-auto tw:p-0",children:[e&&r.jsx(qr,{children:I.getHeaderGroups().map(x=>r.jsx(be,{children:x.headers.filter(R=>R.column.columnDef.header).map(R=>r.jsx(dr,{colSpan:R.colSpan,className:"tw:sticky top-0",children:R.isPlaceholder?void 0:r.jsxs("div",{children:[R.column.getCanGroup()?r.jsx(F,{variant:"ghost",title:`Toggle grouping by ${R.column.columnDef.header}`,onClick:R.column.getToggleGroupingHandler(),type:"button",children:R.column.getIsGrouped()?"🛑":"👊 "}):void 0," ",Mt.flexRender(R.column.columnDef.header,R.getContext())]})},R.id))},x.id))}),r.jsx(Kr,{children:I.getRowModel().rows.map((x,R)=>{const P=ft();return r.jsx(be,{"data-state":x.getIsSelected()?"selected":"",className:h(z(x,R)),onClick:G=>E(x,G),children:x.getVisibleCells().map(G=>{if(!(G.getIsPlaceholder()||G.column.columnDef.enableGrouping&&!G.getIsGrouped()&&(G.column.columnDef.id!==Me||!a)))return r.jsx(Oe,{className:h(G.column.columnDef.id,"tw:p-[1px]",j(d,x,G)),children:G.getIsGrouped()?r.jsxs(F,{variant:"link",onClick:x.getToggleExpandedHandler(),type:"button",children:[x.getIsExpanded()&&r.jsx($.ChevronDown,{}),!x.getIsExpanded()&&(P==="ltr"?r.jsx($.ChevronRight,{}):r.jsx($.ChevronLeft,{}))," ",Mt.flexRender(G.column.columnDef.cell,G.getContext())," (",x.subRows.length,")"]}):Mt.flexRender(G.column.columnDef.cell,G.getContext())},G.id)})},x.id)})})]})]})}const Fa=(t,e)=>t.filter(a=>{try{return D.getSectionForBook(a)===e}catch{return!1}}),Qn=(t,e,a)=>Fa(t,e).every(o=>a.includes(o));function ad({section:t,availableBookIds:e,selectedBookIds:a,onToggle:o,localizedStrings:n}){const i=Fa(e,t).length===0,s=n["%scripture_section_ot_short%"],c=n["%scripture_section_nt_short%"],w=n["%scripture_section_dc_short%"],d=n["%scripture_section_extra_short%"];return r.jsx(F,{variant:"outline",size:"sm",onClick:()=>o(t),className:h(Qn(e,t,a)&&!i&&"tw:bg-primary tw:text-primary-foreground tw:hover:bg-primary/70 tw:hover:text-primary-foreground"),disabled:i,children:$i(t,s,c,w,d)})}const jo=5,ia=6;function od({availableBookInfo:t,selectedBookIds:e,onChangeSelectedBookIds:a,localizedStrings:o,localizedBookNames:n}){const i=o["%webView_book_selector_books_selected%"],s=o["%webView_book_selector_select_books%"],c=o["%webView_book_selector_search_books%"],w=o["%webView_book_selector_select_all%"],d=o["%webView_book_selector_clear_all%"],u=o["%webView_book_selector_no_book_found%"],g=o["%webView_book_selector_more%"],{otLong:m,ntLong:p,dcLong:f,extraLong:y}={otLong:o==null?void 0:o["%scripture_section_ot_long%"],ntLong:o==null?void 0:o["%scripture_section_nt_long%"],dcLong:o==null?void 0:o["%scripture_section_dc_long%"],extraLong:o==null?void 0:o["%scripture_section_extra_long%"]},[b,I]=l.useState(!1),[C,T]=l.useState(""),S=l.useRef(void 0),N=l.useRef(!1);if(t.length!==st.Canon.allBookIds.length)throw new Error("availableBookInfo length must match Canon.allBookIds length");const E=l.useMemo(()=>st.Canon.allBookIds.filter((V,K)=>t[K]==="1"&&!st.Canon.isObsolete(st.Canon.bookIdToNumber(V))),[t]),z=l.useMemo(()=>{if(!C.trim()){const M={[D.Section.OT]:[],[D.Section.NT]:[],[D.Section.DC]:[],[D.Section.Extra]:[]};return E.forEach(Y=>{const lt=D.getSectionForBook(Y);M[lt].push(Y)}),M}const V=E.filter(M=>_a(M,C,n)),K={[D.Section.OT]:[],[D.Section.NT]:[],[D.Section.DC]:[],[D.Section.Extra]:[]};return V.forEach(M=>{const Y=D.getSectionForBook(M);K[Y].push(M)}),K},[E,C,n]),j=l.useCallback((V,K=!1)=>{if(!K||!S.current){a(e.includes(V)?e.filter(J=>J!==V):[...e,V]),S.current=V;return}const M=E.findIndex(J=>J===S.current),Y=E.findIndex(J=>J===V);if(M===-1||Y===-1)return;const[lt,kt]=[Math.min(M,Y),Math.max(M,Y)],St=E.slice(lt,kt+1).map(J=>J);a(e.includes(V)?e.filter(J=>!St.includes(J)):[...new Set([...e,...St])])},[e,a,E]),x=V=>{j(V,N.current),N.current=!1},R=(V,K)=>{V.preventDefault(),j(K,V.shiftKey)},P=l.useCallback(V=>{const K=Fa(E,V).map(M=>M);a(Qn(E,V,e)?e.filter(M=>!K.includes(M)):[...new Set([...e,...K])])},[e,a,E]),G=()=>{a(E.map(V=>V))},q=()=>{a([])};return r.jsxs("div",{className:"tw:space-y-2",children:[r.jsx("div",{className:"tw:flex tw:flex-wrap tw:gap-2",children:Object.values(D.Section).map(V=>r.jsx(ad,{section:V,availableBookIds:E,selectedBookIds:e,onToggle:P,localizedStrings:o},V))}),r.jsxs(se,{open:b,onOpenChange:V=>{I(V),V||T("")},children:[r.jsx(me,{asChild:!0,children:r.jsxs(F,{variant:"outline",role:"combobox","aria-expanded":b,className:"tw:max-w-64 tw:justify-between",children:[e.length>0?`${i}: ${e.length}`:s,r.jsx($.ChevronsUpDown,{className:"tw:ml-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})}),r.jsx(ce,{className:"tw:w-[500px] tw:max-w-[calc(100vw-2rem)] tw:p-0",align:"start",children:r.jsxs(he,{shouldFilter:!1,onKeyDown:V=>{V.key==="Enter"&&(N.current=V.shiftKey)},children:[r.jsx(Fe,{placeholder:c,value:C,onValueChange:T}),r.jsxs("div",{className:"tw:flex tw:justify-between tw:border-b tw:p-2",children:[r.jsx(F,{variant:"ghost",size:"sm",onClick:G,children:w}),r.jsx(F,{variant:"ghost",size:"sm",onClick:q,children:d})]}),r.jsxs(fe,{children:[r.jsx(Ze,{children:u}),Object.values(D.Section).map((V,K)=>{const M=z[V];if(M.length!==0)return r.jsxs(l.Fragment,{children:[r.jsx(re,{heading:Do(V,m,p,f,y),children:M.map(Y=>r.jsx(Mo,{bookId:Y,isSelected:e.includes(Y),onSelect:()=>x(Y),onMouseDown:lt=>R(lt,Y),section:D.getSectionForBook(Y),showCheck:!0,localizedBookNames:n,commandValue:Ao(Y,n),className:"tw:flex tw:items-center"},Y))}),K0&&r.jsxs("div",{className:"tw:mt-2 tw:flex tw:flex-wrap tw:gap-1",children:[e.slice(0,e.length===ia?ia:jo).map(V=>r.jsx(pe,{className:"tw:hover:bg-secondary",variant:"secondary",children:Ce(V,n)},V)),e.length>ia&&r.jsx(pe,{className:"tw:hover:bg-secondary",variant:"secondary",children:`+${e.length-jo} ${g}`})]})]})}const nd=Object.freeze(["%webView_scope_selector_selected_text%","%webView_scope_selector_verse%","%webView_scope_selector_chapter%","%webView_scope_selector_book%","%webView_scope_selector_current_verse%","%webView_scope_selector_current_chapter%","%webView_scope_selector_current_book%","%webView_scope_selector_choose_books%","%webView_scope_selector_scope%","%webView_scope_selector_select_books%","%webView_scope_selector_range%","%webView_scope_selector_select_range%","%webView_scope_selector_range_start%","%webView_scope_selector_range_end%","%webView_scope_selector_ok%","%webView_scope_selector_cancel%","%webView_scope_selector_navigate%","%webView_book_selector_books_selected%","%webView_book_selector_select_books%","%webView_book_selector_search_books%","%webView_book_selector_select_all%","%webView_book_selector_clear_all%","%webView_book_selector_no_book_found%","%webView_book_selector_more%","%scripture_section_ot_long%","%scripture_section_ot_short%","%scripture_section_nt_long%","%scripture_section_nt_short%","%scripture_section_dc_long%","%scripture_section_dc_short%","%scripture_section_extra_long%","%scripture_section_extra_short%"]),Ct=(t,e)=>t[e]??e,id=Object.freeze([" ","-"]);function sd({scope:t,availableScopes:e,onScopeChange:a,availableBookInfo:o,selectedBookIds:n,onSelectedBookIdsChange:i,localizedStrings:s,localizedBookNames:c,id:w,variant:d="radio",rangeStart:u,rangeEnd:g,onRangeStartChange:m,onRangeEndChange:p,currentScrRef:f,onCurrentScrRefChange:y,bookChapterControlLocalizedStrings:b,getEndVerse:I,hideLabel:C=!1,buttonClassName:T}){const S=Ct(s,"%webView_scope_selector_selected_text%"),N=Ct(s,"%webView_scope_selector_verse%"),E=Ct(s,"%webView_scope_selector_chapter%"),z=Ct(s,"%webView_scope_selector_book%"),j=Ct(s,"%webView_scope_selector_current_verse%"),x=Ct(s,"%webView_scope_selector_current_chapter%"),R=Ct(s,"%webView_scope_selector_current_book%"),P=Ct(s,"%webView_scope_selector_choose_books%"),G=Ct(s,"%webView_scope_selector_scope%"),q=Ct(s,"%webView_scope_selector_select_books%"),V=Ct(s,"%webView_scope_selector_range%"),K=Ct(s,"%webView_scope_selector_select_range%"),M=Ct(s,"%webView_scope_selector_range_start%"),Y=Ct(s,"%webView_scope_selector_range_end%"),lt=Ct(s,"%webView_scope_selector_ok%"),kt=Ct(s,"%webView_scope_selector_cancel%"),St=Ct(s,"%webView_scope_selector_navigate%"),J=L=>{if(!f)return;const X=f.book.toUpperCase();switch(L){case"verse":return D.formatScrRef(f,"id");case"chapter":return`${X} ${f.chapterNum}`;case"book":return X;default:return}},Et=[{value:"selectedText",label:S,id:"scope-selected-text"},{value:"verse",label:N,dropdownLabel:j,scrRefSuffix:J("verse"),id:"scope-verse"},{value:"chapter",label:E,dropdownLabel:x,scrRefSuffix:J("chapter"),id:"scope-chapter"},{value:"book",label:z,dropdownLabel:R,scrRefSuffix:J("book"),id:"scope-book"},{value:"selectedBooks",label:P,id:"scope-selected"},{value:"range",label:V,id:"scope-range"}],U=(L,X,Vt=!1)=>r.jsxs(r.Fragment,{children:[L,X&&!Vt&&r.jsxs("span",{className:"tw:text-muted-foreground",children:[": ",X]})]}),tt=e?Et.filter(L=>e.includes(L.value)):Et,rt=f??D.defaultScrRef,at=u??rt,ot=g??rt,Lt=()=>{},gt=l.useRef(null),Ft=l.useRef(null),Gt=l.useRef(!1),A=l.useRef(null),Tt=l.useRef(!1),[Bt,Qt]=l.useState(void 0),Ut=l.useRef(!1),Rt=l.useRef(!1),je=l.useRef(null),qt=l.useCallback(L=>{if(L){Qt("start"),Ut.current=!1;return}Qt(X=>X==="start"?void 0:X),Ut.current&&(Ut.current=!1,requestAnimationFrame(()=>{var Vt;const X=(Vt=gt.current)==null?void 0:Vt.querySelector("button");X==null||X.click()}))},[]),Kt=l.useCallback(L=>{if(L){Qt("end"),Rt.current=!1;return}Qt(X=>X==="end"?void 0:X)},[]),B=l.useCallback(L=>{m==null||m(L),p==null||p(L),Ut.current=!0},[m,p]),W=l.useCallback(L=>{p==null||p(L),Rt.current=!0},[p]),nt=l.useCallback(L=>{a(L),L==="selectedBooks"&&n.length===0&&(f!=null&&f.book)&&i([f.book])},[a,n,f,i]),et=tt.find(L=>L.value===t),wt=()=>t==="selectedBooks"&&n.length>0?n.map(L=>L.toUpperCase()).join(", "):t==="range"?D.formatScrRefRange(at,ot,{optionOrLocalizedBookName:"id",endRefOptionOrLocalizedBookName:"id",repeatBookName:!0}):et?U(et.label,et.scrRefSuffix):t,mt=tt.filter(L=>L.value!=="selectedBooks"&&L.value!=="range"),vt=tt.find(L=>L.value==="selectedBooks"),ut=tt.find(L=>L.value==="range"),[jt,O]=l.useState(!1),[ct,dt]=l.useState(void 0),[bt,Re]=l.useState(void 0),[_e,Qe]=l.useState(void 0),[ze,tr]=l.useState(void 0),[er,gr]=l.useState([]),hr=d==="dropdown"&&ct==="selectedBooks",_=r.jsx(od,{availableBookInfo:o,selectedBookIds:hr?er:n,onChangeSelectedBookIds:hr?gr:i,localizedStrings:s,localizedBookNames:c}),H=Bt==="end",Z=Bt==="start",_t="tw:text-muted-foreground",Wt=d==="dropdown"&&ct==="range",Ue=Wt?Qe:B,zt=Wt?tr:p?W:Lt,xt=r.jsxs("div",{className:"tw:flex tw:flex-wrap tw:items-end tw:gap-4",children:[r.jsxs("div",{className:"tw:grid tw:gap-2",children:[r.jsx(yt,{htmlFor:"scope-range-start",className:h(H&&_t),children:M}),r.jsx(Nr,{id:"scope-range-start",scrRef:Wt?_e??at:at,handleSubmit:Ue,localizedBookNames:c,localizedStrings:b,getEndVerse:I,submitKeys:id,onOpenChange:qt,className:h(H&&_t),modal:!0})]}),r.jsxs("div",{ref:gt,className:"tw:grid tw:gap-2",children:[r.jsx(yt,{htmlFor:"scope-range-end",className:h(Z&&_t),children:Y}),r.jsx(Nr,{id:"scope-range-end",scrRef:Wt?ze??ot:ot,handleSubmit:zt,localizedBookNames:c,localizedStrings:b,getEndVerse:I,disableReferencesUpTo:Wt?_e??at:at,onOpenChange:Kt,onCloseAutoFocus:L=>{var X;Rt.current&&(Rt.current=!1,L.preventDefault(),(X=je.current)==null||X.focus())},className:h(Z&&_t),modal:!0,align:"start"})]})]}),Nt=l.useRef({}),pt=l.useCallback(L=>X=>{Nt.current[L]=X},[]),Ht=l.useRef(null);l.useEffect(()=>{if(!jt)return;let L=0;const X=requestAnimationFrame(()=>{L=requestAnimationFrame(()=>{var Vt;(Vt=Nt.current[t])==null||Vt.focus()})});return()=>{cancelAnimationFrame(X),L&&cancelAnimationFrame(L)}},[jt,t]);const[Yt,De]=l.useState(null),[fr,li]=l.useState(null),[mr,di]=l.useState(null),wi=200,[ui,pi]=l.useState(!1);l.useEffect(()=>{if(!mr||typeof ResizeObserver>"u")return;const L=new ResizeObserver(([X])=>{pi(X.contentRect.widthL.disconnect()},[mr]);const Ka=l.useCallback(L=>{Re(L),Qe(at),tr(ot),gr(n),O(!1),dt(L)},[at,ot,n]),Ha=l.useCallback(()=>{bt!==void 0&&(bt==="range"?(_e&&(m==null||m(_e)),ze&&(p==null||p(ze))):bt==="selectedBooks"&&i(er),nt(bt),dt(void 0),Re(void 0))},[bt,_e,ze,er,m,p,i,nt]),vr=l.useCallback(L=>{L||(dt(void 0),Re(void 0))},[]),Ya=l.useCallback(L=>{var X;L.preventDefault(),(X=Ht.current)==null||X.focus()},[]),Wa=L=>t===L?r.jsx("span",{className:"tw:absolute tw:flex tw:h-3.5 tw:w-3.5 tw:items-center tw:justify-center tw:ltr:left-2 tw:rtl:right-2",children:r.jsx($.Check,{className:"tw:h-4 tw:w-4"})}):void 0;return r.jsxs("div",{id:w,className:"tw:grid tw:gap-4",children:[r.jsxs("div",{className:"tw:grid tw:gap-2",children:[!C&&r.jsx(yt,{children:G}),d==="dropdown"?r.jsxs(ae,{open:jt,onOpenChange:O,children:[r.jsx(oe,{asChild:!0,children:r.jsxs(F,{ref:Ht,variant:"outline",role:"combobox",className:h("tw:w-full tw:justify-between tw:overflow-hidden tw:font-normal",T),children:[r.jsx("span",{className:"tw:min-w-0 tw:flex-1 tw:truncate tw:text-start",children:wt()}),r.jsx($.ChevronDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})}),r.jsx(ne,{ref:di,className:"tw:w-[var(--radix-dropdown-menu-trigger-width)] tw:min-w-[12rem]",align:"start",children:r.jsxs(jr,{container:mr,children:[mt.map(({value:L,label:X,dropdownLabel:Vt,scrRefSuffix:ar,id:gi})=>r.jsxs(Se,{ref:pt(L),className:"tw:relative tw:ps-8 data-[highlighted]:tw:bg-accent data-[highlighted]:tw:text-accent-foreground",onSelect:()=>nt(L),"data-selected":t===L?"true":void 0,children:[t===L&&r.jsx("span",{className:"tw:absolute tw:flex tw:h-3.5 tw:w-3.5 tw:items-center tw:justify-center tw:ltr:left-2 tw:rtl:right-2",children:r.jsx($.Check,{className:"tw:h-4 tw:w-4"})}),U(Vt??X,ar,ui)]},gi)),(vt||ut)&&r.jsx(ye,{}),vt&&r.jsxs(Se,{ref:pt("selectedBooks"),className:h("tw:relative tw:ps-8","data-[highlighted]:tw:bg-accent data-[highlighted]:tw:text-accent-foreground"),onSelect:()=>Ka("selectedBooks"),"data-selected":t==="selectedBooks"?"true":void 0,children:[Wa("selectedBooks"),`${vt.label}…`]}),ut&&r.jsxs(Se,{ref:pt("range"),className:h("tw:relative tw:ps-8","data-[highlighted]:tw:bg-accent data-[highlighted]:tw:text-accent-foreground"),onSelect:()=>Ka("range"),"data-selected":t==="range"?"true":void 0,children:[Wa("range"),`${ut.label}…`]}),y&&r.jsxs(r.Fragment,{children:[r.jsx(ye,{}),r.jsx(Ee,{className:"tw:px-2 tw:py-1.5 tw:text-xs tw:font-medium tw:text-muted-foreground",children:St}),r.jsx(Se,{ref:A,className:"tw:p-0",onSelect:L=>{var X,Vt;if(L.preventDefault(),Gt.current){Gt.current=!1;return}Tt.current||(Vt=(X=Ft.current)==null?void 0:X.querySelector("button"))==null||Vt.click()},children:r.jsx("div",{ref:Ft,className:"tw:w-full tw:px-1 tw:pb-1",onPointerDownCapture:L=>{const X=L.target instanceof HTMLElement?L.target:void 0;X!=null&&X.closest("button")&&(Gt.current=!0,requestAnimationFrame(()=>{Gt.current=!1}))},children:r.jsx(Nr,{id:"scope-navigate",scrRef:f??D.defaultScrRef,handleSubmit:y,localizedBookNames:c,localizedStrings:b,getEndVerse:I,triggerVariant:"ghost",onOpenChange:L=>{Tt.current=L},onCloseAutoFocus:L=>{var X;L.preventDefault(),(X=A.current)==null||X.focus()},modal:!0,className:"tw:w-full tw:min-w-0 tw:max-w-none tw:justify-between tw:px-2 tw:font-normal",triggerContent:r.jsxs(r.Fragment,{children:[r.jsx("span",{className:"tw:min-w-0 tw:flex-1 tw:truncate tw:text-start",children:D.formatScrRef(f??D.defaultScrRef,"id")}),r.jsx($.ChevronDown,{className:"tw:ms-2 tw:h-4 tw:w-4 tw:shrink-0 tw:opacity-50"})]})})})})]})]})})]}):r.jsx(Na,{value:t,onValueChange:nt,className:"tw:flex tw:flex-col tw:space-y-1",children:tt.map(({value:L,label:X,scrRefSuffix:Vt,id:ar})=>r.jsxs("div",{className:"tw:flex tw:items-center",children:[r.jsx(Dr,{className:"tw:me-2",value:L,id:ar}),r.jsx(yt,{htmlFor:ar,children:U(X,Vt)})]},ar))})]}),d==="radio"&&t==="selectedBooks"&&r.jsxs("div",{className:"tw:grid tw:gap-2",children:[r.jsx(yt,{children:q}),_]}),d==="radio"&&t==="range"&&xt,d==="dropdown"&&vt&&r.jsx(Er,{open:ct==="selectedBooks",onOpenChange:vr,children:r.jsx(Tr,{ref:li,onCloseAutoFocus:Ya,onEscapeKeyDown:L=>{fr!=null&&fr.querySelector('[data-state="open"]')&&L.preventDefault()},children:r.jsxs(jr,{container:fr,children:[r.jsx(Rr,{className:"tw:pe-8",children:r.jsx(zr,{children:P})}),_,r.jsxs(da,{children:[r.jsx(F,{variant:"outline",onClick:()=>vr(!1),children:kt}),r.jsx(F,{onClick:Ha,children:lt})]})]})})}),d==="dropdown"&&ut&&r.jsx(Er,{open:ct==="range",onOpenChange:vr,children:r.jsx(Tr,{ref:De,onCloseAutoFocus:Ya,onEscapeKeyDown:L=>{Yt!=null&&Yt.querySelector('[data-state="open"]')&&L.preventDefault()},children:r.jsxs(jr,{container:Yt,children:[r.jsx(Rr,{className:"tw:pe-8",children:r.jsx(zr,{children:K})}),xt,r.jsxs(da,{children:[r.jsx(F,{variant:"outline",onClick:()=>vr(!1),children:kt}),r.jsx(F,{ref:je,onClick:Ha,children:lt})]})]})})})]})}function cd({availableScrollGroupIds:t,scrollGroupId:e,onChangeScrollGroupId:a,localizedStrings:o={},size:n="sm",className:i,id:s}){const c={...D.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,...Object.fromEntries(Object.entries(o).map(([d,u])=>[d,d===u&&d in D.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS?D.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[d]:u]))},w=ft();return r.jsxs(Ae,{value:`${e}`,onValueChange:d=>a(d==="undefined"?void 0:parseInt(d,10)),children:[r.jsx(Le,{size:n,className:h("pr-twp tw:w-auto",i),children:r.jsx(Pe,{placeholder:c[D.getLocalizeKeyForScrollGroupId(e)]??e})}),r.jsx(Be,{id:s,align:w==="rtl"?"end":"start",style:{zIndex:We},children:t.map(d=>r.jsx(Jt,{value:`${d}`,children:c[D.getLocalizeKeyForScrollGroupId(d)]},`${d}`))})]})}function ld({children:t}){return r.jsx("div",{className:"pr-twp tw:grid",children:t})}function dd({primary:t,secondary:e,children:a,isLoading:o=!1,loadingMessage:n}){return r.jsxs("div",{className:"tw:flex tw:items-center tw:justify-between tw:space-x-4 tw:py-2",children:[r.jsxs("div",{children:[r.jsx("p",{className:"tw:text-sm tw:font-medium tw:leading-none",children:t}),r.jsx("p",{className:"tw:whitespace-normal tw:break-words tw:text-sm tw:text-muted-foreground",children:e})]}),o?r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:n}):r.jsx("div",{children:a})]})}function wd({primary:t,secondary:e,includeSeparator:a=!1}){return r.jsxs("div",{className:"tw:space-y-4 tw:py-2",children:[r.jsxs("div",{children:[r.jsx("h3",{className:"tw:text-lg tw:font-medium",children:t}),r.jsx("p",{className:"tw:text-sm tw:text-muted-foreground",children:e})]}),a?r.jsx($e,{}):""]})}function ti(t,e){var a;return(a=Object.entries(t).find(([,o])=>"menuItem"in o&&o.menuItem===e))==null?void 0:a[0]}function Lr({icon:t,menuLabel:e,leading:a}){return t?r.jsx("img",{className:h("tw:max-h-5 tw:max-w-5",a?"tw:me-2":"tw:ms-2"),src:t,alt:`${a?"Leading":"Trailing"} icon for ${e}`}):void 0}const ei=(t,e,a,o)=>a?Object.entries(t).filter(([i,s])=>"column"in s&&s.column===a||i===a).sort(([,i],[,s])=>i.order-s.order).flatMap(([i])=>e.filter(c=>c.group===i).sort((c,w)=>c.order-w.order).map(c=>r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:"command"in c?r.jsxs(Se,{onClick:()=>{o(c)},children:[c.iconPathBefore&&r.jsx(Lr,{icon:c.iconPathBefore,menuLabel:c.label,leading:!0}),c.label,c.iconPathAfter&&r.jsx(Lr,{icon:c.iconPathAfter,menuLabel:c.label})]},`dropdown-menu-item-${c.label}-${c.command}`):r.jsxs(jn,{children:[r.jsx(_n,{children:c.label}),r.jsx(xn,{children:r.jsx(Nn,{children:ei(t,e,ti(t,c.id),o)})})]},`dropdown-menu-sub-${c.label}-${c.id}`)}),c.tooltip&&r.jsx(Pt,{children:c.tooltip})]},`tooltip-${c.label}-${"command"in c?c.command:c.id}`))):void 0;function Br({onSelectMenuItem:t,menuData:e,tabLabel:a,icon:o,className:n,variant:i,buttonVariant:s="ghost",id:c}){return r.jsxs(ae,{variant:i,children:[r.jsx(oe,{"aria-label":a,className:n,asChild:!0,id:c,children:r.jsx(F,{variant:s,size:"icon",children:o??r.jsx($.MenuIcon,{})})}),r.jsx(ne,{align:"start",style:{zIndex:We},children:Object.entries(e.columns).filter(([,w])=>typeof w=="object").sort(([,w],[,d])=>typeof w=="boolean"||typeof d=="boolean"?0:w.order-d.order).map(([w],d,u)=>r.jsxs(l.Fragment,{children:[r.jsx(La,{children:r.jsx(Ot,{children:ei(e.groups,e.items,w,t)})}),dr.jsx("div",{ref:o,className:`tw:sticky tw:top-0 tw:box-border tw:flex tw:h-14 tw:flex-row tw:items-center tw:justify-between tw:gap-2 tw:overflow-clip tw:px-4 tw:py-2 tw:text-foreground tw:@container/toolbar ${e}`,id:t,children:a}));function ud({onSelectProjectMenuItem:t,onSelectViewInfoMenuItem:e,projectMenuData:a,tabViewMenuData:o,id:n,className:i,startAreaChildren:s,centerAreaChildren:c,endAreaChildren:w,menuButtonIcon:d}){return r.jsxs(ri,{className:`tw:w-full tw:border ${i}`,id:n,children:[a&&r.jsx(Br,{onSelectMenuItem:t,menuData:a,tabLabel:"Project",icon:d??r.jsx($.Menu,{}),buttonVariant:"ghost"}),s&&r.jsx("div",{className:"tw:flex tw:h-full tw:shrink tw:grow-[10] tw:flex-row tw:flex-wrap tw:items-start tw:gap-x-1 tw:gap-y-2 tw:overflow-clip",children:s}),c&&r.jsx("div",{className:"tw:flex tw:h-full tw:shrink tw:grow-[1] tw:basis-0 tw:flex-row tw:flex-wrap tw:items-start tw:justify-center tw:gap-x-1 tw:gap-y-2 tw:overflow-clip tw:@sm:basis-auto",children:c}),r.jsxs("div",{className:"tw:flex tw:h-full tw:shrink tw:grow-[1] tw:flex-row-reverse tw:flex-wrap tw:items-start tw:gap-x-1 tw:gap-y-2 tw:overflow-clip",children:[o&&r.jsx(Br,{onSelectMenuItem:e,menuData:o,tabLabel:"View Info",icon:r.jsx($.EllipsisVertical,{}),className:"tw:h-full"}),w]})]})}function pd({onSelectProjectMenuItem:t,projectMenuData:e,id:a,className:o,menuButtonIcon:n}){return r.jsx(ri,{className:"tw:pointer-events-none",id:a,children:e&&r.jsx(Br,{onSelectMenuItem:t,menuData:e,tabLabel:"Project",icon:n,className:`tw:pointer-events-auto tw:shadow-lg ${o}`,buttonVariant:"outline"})})}const Ga=l.forwardRef(({className:t,...e},a)=>{const o=ft();return r.jsx(k.Tabs.Root,{orientation:"vertical",ref:a,className:h("tw:flex tw:gap-1 tw:rounded-md tw:text-muted-foreground",t),...e,dir:o})});Ga.displayName=k.Tabs.List.displayName;const Ua=l.forwardRef(({className:t,...e},a)=>r.jsx(k.Tabs.List,{ref:a,className:h("tw:flex tw:items-center tw:w-[124px] tw:justify-center tw:rounded-md tw:bg-muted tw:p-1 tw:text-muted-foreground",t),...e}));Ua.displayName=k.Tabs.List.displayName;const ai=l.forwardRef(({className:t,...e},a)=>r.jsx(k.Tabs.Trigger,{ref:a,...e,className:h("tw:inline-flex tw:w-[116px] tw:cursor-pointer tw:items-center tw:justify-center tw:break-words tw:rounded-sm tw:border-0 tw:bg-muted tw:px-3 tw:py-1.5 tw:text-sm tw:font-medium tw:text-inherit tw:ring-offset-background tw:transition-all tw:hover:text-foreground tw:focus-visible:outline-hidden tw:focus-visible:ring-2 tw:focus-visible:ring-ring tw:focus-visible:ring-offset-2 tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:data-[state=active]:bg-background tw:data-[state=active]:text-foreground tw:data-[state=active]:shadow-sm tw:overflow-clip",t)})),qa=l.forwardRef(({className:t,...e},a)=>r.jsx(k.Tabs.Content,{ref:a,className:h("tw:ms-5 tw:flex-grow tw:text-foreground tw:ring-offset-background tw:focus-visible:outline-hidden tw:focus-visible:ring-2 tw:focus-visible:ring-ring tw:focus-visible:ring-offset-2",t),...e}));qa.displayName=k.Tabs.Content.displayName;function gd({tabList:t,searchValue:e,onSearch:a,searchPlaceholder:o,headerTitle:n,searchClassName:i,id:s}){return r.jsxs("div",{id:s,className:"pr-twp",children:[r.jsxs("div",{className:"tw:sticky tw:top-0 tw:space-y-2 tw:pb-2",children:[n?r.jsx("h1",{children:n}):"",r.jsx(Hr,{className:i,value:e,onSearch:a,placeholder:o})]}),r.jsxs(Ga,{children:[r.jsx(Ua,{children:t.map(c=>r.jsx(ai,{value:c.value,children:c.value},c.key))}),t.map(c=>r.jsx(qa,{value:c.value,children:c.content},c.key))]})]})}function hd({className:t,variant:e="default",...a}){const o=l.useMemo(()=>({variant:e}),[e]);return r.jsx(Pa.Provider,{value:o,children:r.jsx(k.Menubar.Root,{"data-slot":"menubar",className:h("tw:flex tw:h-8 tw:items-center tw:gap-0.5 tw:rounded-lg tw:border tw:p-[3px]",t),...a})})}function fd({...t}){return r.jsx(k.Menubar.Menu,{"data-slot":"menubar-menu",...t})}function md({...t}){return r.jsx(k.Menubar.Portal,{"data-slot":"menubar-portal",...t})}function vd({className:t,...e}){const a=ke();return r.jsx(k.Menubar.Trigger,{"data-slot":"menubar-trigger",className:h("tw:flex tw:items-center tw:rounded-sm tw:px-1.5 tw:py-[2px] tw:text-sm tw:font-medium tw:outline-hidden tw:select-none tw:hover:bg-muted tw:aria-expanded:bg-muted","pr-twp",Ge({variant:a.variant,className:t})),...e})}function bd({className:t,align:e="start",alignOffset:a=-4,sideOffset:o=8,...n}){const i=ke();return r.jsx(md,{children:r.jsx(k.Menubar.Content,{"data-slot":"menubar-content",align:e,alignOffset:a,sideOffset:o,className:h("tw:z-50 tw:min-w-36 tw:origin-(--radix-menubar-content-transform-origin) tw:overflow-hidden tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150","pr-twp",{"tw:bg-popover":i.variant==="muted"},t),...n})})}function xd({className:t,inset:e,variant:a="default",...o}){const n=ke();return r.jsx(k.Menubar.Item,{"data-slot":"menubar-item","data-inset":e,"data-variant":a,className:h("tw:group/menubar-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:not-data-[variant=destructive]:focus:**:text-accent-foreground tw:data-inset:ps-7 tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:dark:data-[variant=destructive]:focus:bg-destructive/20 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:data-[variant=destructive]:*:[svg]:text-destructive!",Ge({variant:n.variant,className:t})),...o})}function yd({className:t,...e}){return r.jsx(k.Menubar.Separator,{"data-slot":"menubar-separator",className:h("tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function kd({...t}){return r.jsx(k.Menubar.Sub,{"data-slot":"menubar-sub",...t})}function jd({className:t,inset:e,children:a,...o}){const n=ke();return r.jsxs(k.Menubar.SubTrigger,{"data-slot":"menubar-sub-trigger","data-inset":e,className:h("tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-none tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-inset:ps-7 tw:data-open:bg-accent tw:data-open:text-accent-foreground tw:[&_svg:not([class*=size-])]:size-4",Ge({variant:n.variant,className:t})),...o,children:[a,r.jsx(ht.IconChevronRight,{className:"tw:ms-auto tw:size-4"})]})}function _d({className:t,...e}){const a=ke();return r.jsx(k.Menubar.SubContent,{"data-slot":"menubar-sub-content",className:h("tw:z-50 tw:min-w-32 tw:origin-(--radix-menubar-content-transform-origin) tw:overflow-hidden tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-lg tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",{"tw:bg-popover":a.variant==="muted"},t),...e})}const nr=(t,e)=>{setTimeout(()=>{e.forEach(a=>{var o;(o=t.current)==null||o.dispatchEvent(new KeyboardEvent("keydown",a))})},0)},oi=(t,e,a,o)=>{if(!a)return;const n=Object.entries(t).filter(([i,s])=>"column"in s&&s.column===a||i===a).sort(([,i],[,s])=>i.order-s.order);return n.flatMap(([i],s)=>{const c=e.filter(d=>d.group===i).sort((d,u)=>d.order-u.order).map(d=>r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:"command"in d?r.jsxs(xd,{onClick:()=>{o(d)},children:[d.iconPathBefore&&r.jsx(Lr,{icon:d.iconPathBefore,menuLabel:d.label,leading:!0}),d.label,d.iconPathAfter&&r.jsx(Lr,{icon:d.iconPathAfter,menuLabel:d.label})]},`menubar-item-${d.label}-${d.command}`):r.jsxs(kd,{children:[r.jsx(jd,{children:d.label}),r.jsx(_d,{children:oi(t,e,ti(t,d.id),o)})]},`menubar-sub-${d.label}-${d.id}`)}),d.tooltip&&r.jsx(Pt,{children:d.tooltip})]},`tooltip-${d.label}-${"command"in d?d.command:d.id}`)),w=[...c];return c.length>0&&s{switch(u){case"platform.app":return i;case"platform.window":return s;case"platform.layout":return c;case"platform.help":return w;default:return}};if(xi.useHotkeys(["alt","alt+p","alt+l","alt+n","alt+h"],(u,g)=>{var f,y,b,I;u.preventDefault();const m={key:"Escape",code:"Escape",keyCode:27,bubbles:!0},p={key:" ",code:"Space",keyCode:32,bubbles:!0};switch(g.hotkey){case"alt":nr(i,[m]);break;case"alt+p":(f=i.current)==null||f.focus(),nr(i,[m,p]);break;case"alt+l":(y=s.current)==null||y.focus(),nr(s,[m,p]);break;case"alt+n":(b=c.current)==null||b.focus(),nr(c,[m,p]);break;case"alt+h":(I=w.current)==null||I.focus(),nr(w,[m,p]);break}}),l.useEffect(()=>{if(!a||!n.current)return;const u=new MutationObserver(p=>{p.forEach(f=>{if(f.attributeName==="data-state"&&f.target instanceof HTMLElement){const y=f.target.getAttribute("data-state");a(y==="open")}})});return n.current.querySelectorAll("[data-state]").forEach(p=>{u.observe(p,{attributes:!0})}),()=>u.disconnect()},[a]),!!t)return r.jsx(hd,{ref:n,className:"pr-twp tw:border-0 tw:bg-transparent",variant:o,children:Object.entries(t.columns).filter(([,u])=>typeof u=="object").sort(([,u],[,g])=>typeof u=="boolean"||typeof g=="boolean"?0:u.order-g.order).map(([u,g])=>r.jsxs(fd,{children:[r.jsx(vd,{ref:d(u),children:typeof g=="object"&&"label"in g&&g.label}),r.jsx(bd,{style:{zIndex:We},children:r.jsx(Ot,{children:oi(t.groups,t.items,u,e)})})]},u))})}function Cd(t){switch(t){case void 0:return;case"darwin":return"tw:ps-[85px]";default:return"tw:pe-[calc(138px+1rem)]"}}function Sd({menuData:t,onOpenChange:e,onSelectMenuItem:a,className:o,id:n,children:i,appMenuAreaChildren:s,configAreaChildren:c,shouldUseAsAppDragArea:w,menubarVariant:d="default"}){const u=l.useRef(void 0);return r.jsx("div",{className:h("tw:border tw:px-4 tw:text-foreground",o),ref:u,style:{position:"relative"},id:n,children:r.jsxs("div",{className:"tw:flex tw:h-full tw:w-full tw:justify-between tw:overflow-hidden",style:w?{WebkitAppRegion:"drag"}:void 0,children:[r.jsx("div",{className:"tw:flex tw:grow tw:basis-0",children:r.jsxs("div",{className:"tw:flex tw:items-center tw:gap-2",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:[s,t&&r.jsx(Nd,{menuData:t,onOpenChange:e,onSelectMenuItem:a,variant:d})]})}),r.jsx("div",{className:"tw:flex tw:items-center tw:gap-2 tw:px-2",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:i}),r.jsx("div",{className:"tw:flex tw:min-w-0 tw:grow tw:basis-0 tw:justify-end",children:r.jsx("div",{className:"tw:flex tw:min-w-0 tw:items-center tw:gap-2 tw:pe-1",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:c})})]})})}const Ed=(t,e)=>t[e]??e;function Td({knownUiLanguages:t,primaryLanguage:e="en",fallbackLanguages:a=[],onLanguagesChange:o,onPrimaryLanguageChange:n,onFallbackLanguagesChange:i,localizedStrings:s,className:c,id:w}){const d=Ed(s,"%settings_uiLanguageSelector_fallbackLanguages%"),[u,g]=l.useState(!1),m=f=>{n&&n(f),o&&o([f,...a.filter(y=>y!==f)]),i&&a.find(y=>y===f)&&i([...a.filter(y=>y!==f)]),g(!1)},p=(f,y)=>{var I,C,T,S,N,E;const b=y!==f?((C=(I=t[f])==null?void 0:I.uiNames)==null?void 0:C[y])??((S=(T=t[f])==null?void 0:T.uiNames)==null?void 0:S.en):void 0;return b?`${(N=t[f])==null?void 0:N.autonym} (${b})`:(E=t[f])==null?void 0:E.autonym};return r.jsxs("div",{id:w,className:h("pr-twp tw:max-w-sm",c),children:[r.jsxs(Ae,{name:"uiLanguage",value:e,onValueChange:m,open:u,onOpenChange:f=>g(f),children:[r.jsx(Le,{children:r.jsx(Pe,{})}),r.jsx(Be,{style:{zIndex:We},children:Object.keys(t).map(f=>r.jsx(Jt,{value:f,children:p(f,e)},f))})]}),e!=="en"&&r.jsx("div",{className:"tw:pt-3",children:r.jsx(yt,{className:"tw:font-normal tw:text-muted-foreground",children:D.formatReplacementString(d,{fallbackLanguages:(a==null?void 0:a.length)>0?a.map(f=>p(f,e)).join(", "):t.en.autonym})})})]})}function Rd({item:t,createLabel:e,createComplexLabel:a}){return e?r.jsx(yt,{children:e(t)}):a?r.jsx(yt,{children:a(t)}):r.jsx(yt,{children:t})}function zd({id:t,className:e,listItems:a,selectedListItems:o,handleSelectListItem:n,createLabel:i,createComplexLabel:s}){return r.jsx("div",{id:t,className:e,children:a.map(c=>r.jsxs("div",{className:"tw:m-2 tw:flex tw:items-center",children:[r.jsx(Va,{className:"tw:me-2 tw:align-middle",checked:o.includes(c),onCheckedChange:w=>n(c,w)}),r.jsx(Rd,{item:c,createLabel:i,createComplexLabel:s})]},c))})}function Dd({scrRef:t,onClick:e,tooltipContent:a,ariaLabel:o,className:n,testId:i="linked-scr-ref-button"}){if(t==="")return;const s=r.jsx(F,{type:"button",variant:"link",onClick:e,disabled:!e,"aria-label":o,className:h("tw:h-auto tw:p-0 tw:text-start tw:font-mono tw:text-sm",n),"data-testid":i,children:t});return a?r.jsx(Ot,{delayDuration:0,children:r.jsxs($t,{children:[r.jsx(At,{asChild:!0,children:s}),r.jsx(Pt,{children:a})]})}):s}function Id({cardKey:t,isSelected:e,onSelect:a,isDenied:o,isHidden:n=!1,className:i,children:s,selectedButtons:c,hoverButtons:w,dropdownContent:d,additionalContent:u,accentColor:g,showDropdownOnHover:m=!1}){const p=f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),a())};return r.jsxs("div",{hidden:n,onClick:a,onKeyDown:p,role:"button",tabIndex:0,"aria-pressed":e,className:h("tw:group tw:relative tw:min-w-36 tw:rounded-xl tw:border tw:shadow-none tw:hover:bg-muted/50",{"tw:opacity-50 tw:hover:opacity-100":o&&!e},{"tw:bg-accent":e},{"tw:bg-transparent":!e},i),children:[r.jsxs("div",{className:"tw:flex tw:flex-col tw:gap-2 tw:p-4",children:[r.jsxs("div",{className:"tw:flex tw:justify-between tw:overflow-hidden",children:[r.jsx("div",{className:"tw:min-w-0 tw:flex-1",children:s}),e&&c,!e&&w&&r.jsx("div",{className:"tw:invisible tw:group-hover:visible",children:w}),!e&&m&&d&&r.jsx("div",{className:"tw:invisible tw:group-hover:visible",children:r.jsxs(ae,{children:[r.jsx(oe,{className:h(g&&"tw:me-1"),asChild:!0,children:r.jsx(F,{className:"tw:m-1 tw:h-6 tw:w-6",variant:"ghost",size:"icon",children:r.jsx($.MoreVertical,{})})}),r.jsx(ne,{align:"end",children:d})]})}),e&&d&&r.jsxs(ae,{children:[r.jsx(oe,{className:h(g&&"tw:me-1"),asChild:!0,children:r.jsx(F,{className:"tw:m-1 tw:h-6 tw:w-6",variant:"ghost",size:"icon",children:r.jsx($.MoreVertical,{})})}),r.jsx(ne,{align:"end",children:d})]})]}),u&&r.jsx("div",{className:"tw:w-fit tw:min-w-0 tw:max-w-full tw:overflow-hidden",children:u})]}),g&&r.jsx("div",{className:`tw:absolute tw:right-0 tw:top-0 tw:h-full tw:w-2 tw:rounded-r-xl ${g}`})]},t)}const ni=l.forwardRef(({className:t,...e},a)=>r.jsx($.LoaderCircle,{size:35,className:h("tw:animate-spin",t),...e,ref:a}));ni.displayName="Spinner";function Md({id:t,isDisabled:e=!1,hasError:a=!1,isFullWidth:o=!1,helperText:n,label:i,placeholder:s,isRequired:c=!1,className:w,defaultValue:d,value:u,onChange:g,onFocus:m,onBlur:p}){return r.jsxs("div",{className:h("tw:inline-grid tw:items-center tw:gap-1.5",{"tw:w-full":o}),children:[r.jsx(yt,{htmlFor:t,className:h({"tw:text-red-600":a,"tw:hidden":!i}),children:`${i}${c?"*":""}`}),r.jsx(Xe,{id:t,disabled:e,placeholder:s,required:c,className:h(w,{"tw:border-red-600":a}),defaultValue:d,value:u,onChange:g,onFocus:m,onBlur:p}),r.jsx("p",{className:h({"tw:hidden":!n}),children:n})]})}const Od=ge.cva("tw:group/alert tw:relative tw:grid tw:w-full tw:gap-0.5 tw:rounded-lg tw:border tw:px-2.5 tw:py-2 tw:text-start tw:text-sm tw:has-data-[slot=alert-action]:relative tw:has-data-[slot=alert-action]:pe-18 tw:has-[>svg]:grid-cols-[auto_1fr] tw:has-[>svg]:gap-x-2 tw:*:[svg]:row-span-2 tw:*:[svg]:translate-y-0.5 tw:*:[svg]:text-current tw:*:[svg:not([class*=size-])]:size-4 tw:has-[>img]:grid-cols-[auto_1fr] tw:has-[>img]:gap-x-2 tw:*:[img]:row-span-2 tw:*:[img]:translate-y-0.5 tw:*:[img]:text-current tw:*:[img:not([class*=size-])]:size-4",{variants:{variant:{default:"tw:bg-card tw:text-card-foreground",destructive:"tw:bg-card tw:text-destructive tw:*:data-[slot=alert-description]:text-destructive/90 tw:*:[svg]:text-current tw:*:[img]:text-current"}},defaultVariants:{variant:"default"}});function $d({className:t,variant:e,...a}){return r.jsx("div",{"data-slot":"alert",role:"alert",className:h("pr-twp",Od({variant:e}),t),...a})}function Ad({className:t,...e}){return r.jsx("div",{"data-slot":"alert-title",className:h("tw:font-medium tw:group-has-[>svg]/alert:col-start-2 tw:[&_a]:underline tw:[&_a]:underline-offset-3 tw:[&_a]:hover:text-foreground",t),...e})}function Pd({className:t,...e}){return r.jsx("div",{"data-slot":"alert-description",className:h("tw:text-sm tw:text-balance tw:text-muted-foreground tw:md:text-pretty tw:[&_a]:underline tw:[&_a]:underline-offset-3 tw:[&_a]:hover:text-foreground tw:[&_p:not(:last-child)]:mb-4",t),...e})}function Ld({...t}){return r.jsx(k.ContextMenu.Root,{"data-slot":"context-menu",...t})}function Bd({className:t,...e}){return r.jsx(k.ContextMenu.Trigger,{"data-slot":"context-menu-trigger",className:h("tw:select-none",t),...e})}function Vd({...t}){return r.jsx(k.ContextMenu.Group,{"data-slot":"context-menu-group",...t})}function Fd({...t}){return r.jsx(k.ContextMenu.Portal,{"data-slot":"context-menu-portal",...t})}function Gd({...t}){return r.jsx(k.ContextMenu.Sub,{"data-slot":"context-menu-sub",...t})}function Ud({...t}){return r.jsx(k.ContextMenu.RadioGroup,{"data-slot":"context-menu-radio-group",...t})}function qd({className:t,...e}){return r.jsx(k.ContextMenu.Portal,{children:r.jsx(k.ContextMenu.Content,{"data-slot":"context-menu-content",className:h("pr-twp tw:z-50 tw:max-h-(--radix-context-menu-content-available-height) tw:min-w-36 tw:origin-(--radix-context-menu-content-transform-origin) tw:overflow-x-hidden tw:overflow-y-auto tw:rounded-lg tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-md tw:ring-1 tw:ring-foreground/10 tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...e})})}function Kd({className:t,inset:e,variant:a="default",...o}){return r.jsx(k.ContextMenu.Item,{"data-slot":"context-menu-item","data-inset":e,"data-variant":a,className:h("pr-twp tw:group/context-menu-item tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-inset:ps-7 tw:data-[variant=destructive]:text-destructive tw:data-[variant=destructive]:focus:bg-destructive/10 tw:data-[variant=destructive]:focus:text-destructive tw:dark:data-[variant=destructive]:focus:bg-destructive/20 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4 tw:focus:*:[svg]:text-accent-foreground tw:data-[variant=destructive]:*:[svg]:text-destructive",t),...o})}function Hd({className:t,inset:e,children:a,...o}){return r.jsxs(k.ContextMenu.SubTrigger,{"data-slot":"context-menu-sub-trigger","data-inset":e,className:h("pr-twp tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:px-1.5 tw:py-1 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-inset:ps-7 tw:data-open:bg-accent tw:data-open:text-accent-foreground tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t),...o,children:[a,r.jsx(ht.IconChevronRight,{className:"tw:ms-auto"})]})}function Yd({className:t,...e}){return r.jsx(k.ContextMenu.SubContent,{"data-slot":"context-menu-sub-content",className:h("pr-twp tw:z-50 tw:min-w-32 tw:origin-(--radix-context-menu-content-transform-origin) tw:overflow-hidden tw:rounded-lg tw:border tw:bg-popover tw:p-1 tw:text-popover-foreground tw:shadow-lg tw:duration-100 tw:data-[side=bottom]:slide-in-from-top-2 tw:data-[side=left]:slide-in-from-right-2 tw:data-[side=right]:slide-in-from-left-2 tw:data-[side=top]:slide-in-from-bottom-2 tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-open:zoom-in-95 tw:data-closed:animate-out tw:data-closed:fade-out-0 tw:data-closed:zoom-out-95 tw:animate-none! tw:bg-popover/70 tw:before:-z-1 tw:**:data-[slot$=-item]:focus:bg-foreground/10 tw:**:data-[slot$=-item]:data-highlighted:bg-foreground/10 tw:**:data-[slot$=-separator]:bg-foreground/5 tw:**:data-[slot$=-trigger]:focus:bg-foreground/10 tw:**:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! tw:**:data-[variant=destructive]:focus:bg-foreground/10! tw:**:data-[variant=destructive]:text-accent-foreground! tw:**:data-[variant=destructive]:**:text-accent-foreground! tw:relative tw:before:pointer-events-none tw:before:absolute tw:before:inset-0 tw:before:rounded-[inherit] tw:before:backdrop-blur-2xl tw:before:backdrop-saturate-150",t),...e})}function Wd({className:t,children:e,checked:a,inset:o,...n}){return r.jsxs(k.ContextMenu.CheckboxItem,{"data-slot":"context-menu-checkbox-item","data-inset":o,className:h("pr-twp tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t),checked:a,...n,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2",children:r.jsx(k.ContextMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function Xd({className:t,children:e,inset:a,...o}){return r.jsxs(k.ContextMenu.RadioItem,{"data-slot":"context-menu-radio-item","data-inset":a,className:h("pr-twp tw:relative tw:flex tw:cursor-default tw:items-center tw:gap-1.5 tw:rounded-md tw:py-1 tw:pe-8 tw:ps-1.5 tw:text-sm tw:outline-hidden tw:select-none tw:focus:bg-accent tw:focus:text-accent-foreground tw:data-inset:ps-7 tw:data-disabled:pointer-events-none tw:data-disabled:opacity-50 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4",t),...o,children:[r.jsx("span",{className:"tw:pointer-events-none tw:absolute tw:end-2",children:r.jsx(k.ContextMenu.ItemIndicator,{children:r.jsx(ht.IconCheck,{})})}),e]})}function Zd({className:t,inset:e,...a}){return r.jsx(k.ContextMenu.Label,{"data-slot":"context-menu-label","data-inset":e,className:h("pr-twp tw:px-1.5 tw:py-1 tw:text-xs tw:font-medium tw:text-muted-foreground tw:data-inset:ps-7",t),...a})}function Jd({className:t,...e}){return r.jsx(k.ContextMenu.Separator,{"data-slot":"context-menu-separator",className:h("pr-twp tw:-mx-1 tw:my-1 tw:h-px tw:bg-border",t),...e})}function Qd({className:t,...e}){return r.jsx("span",{"data-slot":"context-menu-shortcut",className:h("pr-twp tw:ms-auto tw:text-xs tw:tracking-widest tw:text-muted-foreground tw:group-focus/context-menu-item:text-accent-foreground",t),...e})}function tw({...t}){return r.jsx(Te.Drawer.Root,{"data-slot":"drawer",...t})}function ew({...t}){return r.jsx(Te.Drawer.Trigger,{"data-slot":"drawer-trigger",...t})}function ii({...t}){return r.jsx(Te.Drawer.Portal,{"data-slot":"drawer-portal",...t})}function rw({...t}){return r.jsx(Te.Drawer.Close,{"data-slot":"drawer-close",...t})}function si({className:t,...e}){return r.jsx(Te.Drawer.Overlay,{"data-slot":"drawer-overlay",className:h("pr-twp tw:fixed tw:inset-0 tw:z-50 tw:bg-black/10 tw:supports-backdrop-filter:backdrop-blur-xs tw:data-open:animate-in tw:data-open:fade-in-0 tw:data-closed:animate-out tw:data-closed:fade-out-0",t),...e})}function aw({className:t,children:e,hideDrawerHandle:a=!1,...o}){const n=ft();return r.jsxs(ii,{"data-slot":"drawer-portal",children:[r.jsx(si,{}),r.jsxs(Te.Drawer.Content,{"data-slot":"drawer-content",className:h("pr-twp tw:group/drawer-content tw:fixed tw:z-50 tw:flex tw:h-auto tw:flex-col tw:bg-popover tw:text-sm tw:text-popover-foreground tw:data-[vaul-drawer-direction=bottom]:inset-x-0 tw:data-[vaul-drawer-direction=bottom]:bottom-0 tw:data-[vaul-drawer-direction=bottom]:mt-24 tw:data-[vaul-drawer-direction=bottom]:max-h-[80vh] tw:data-[vaul-drawer-direction=bottom]:rounded-t-xl tw:data-[vaul-drawer-direction=bottom]:border-t tw:data-[vaul-drawer-direction=left]:inset-y-0 tw:data-[vaul-drawer-direction=left]:left-0 tw:data-[vaul-drawer-direction=left]:w-3/4 tw:data-[vaul-drawer-direction=left]:rounded-r-xl tw:data-[vaul-drawer-direction=left]:border-r tw:data-[vaul-drawer-direction=left]:flex-row tw:data-[vaul-drawer-direction=right]:inset-y-0 tw:data-[vaul-drawer-direction=right]:right-0 tw:data-[vaul-drawer-direction=right]:w-3/4 tw:data-[vaul-drawer-direction=right]:rounded-l-xl tw:data-[vaul-drawer-direction=right]:border-l tw:data-[vaul-drawer-direction=right]:flex-row tw:data-[vaul-drawer-direction=top]:inset-x-0 tw:data-[vaul-drawer-direction=top]:top-0 tw:data-[vaul-drawer-direction=top]:mb-24 tw:data-[vaul-drawer-direction=top]:max-h-[80vh] tw:data-[vaul-drawer-direction=top]:rounded-b-xl tw:data-[vaul-drawer-direction=top]:border-b tw:data-[vaul-drawer-direction=left]:sm:max-w-sm tw:data-[vaul-drawer-direction=right]:sm:max-w-sm",t),dir:"ltr",...o,children:[!a&&r.jsx("div",{className:"tw:hidden tw:shrink-0 tw:rounded-full tw:bg-muted tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:mx-auto tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:mt-4 tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:h-1.5 tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:w-[100px] tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:block tw:group-data-[vaul-drawer-direction=right]/drawer-content:my-auto tw:group-data-[vaul-drawer-direction=right]/drawer-content:ms-4 tw:group-data-[vaul-drawer-direction=right]/drawer-content:h-[100px] tw:group-data-[vaul-drawer-direction=right]/drawer-content:w-1.5 tw:group-data-[vaul-drawer-direction=right]/drawer-content:block"}),r.jsx("div",{className:"tw:flex tw:min-w-0 tw:flex-1 tw:flex-col",dir:n,children:e}),!a&&r.jsx("div",{className:"tw:hidden tw:shrink-0 tw:rounded-full tw:bg-muted tw:group-data-[vaul-drawer-direction=top]/drawer-content:mx-auto tw:group-data-[vaul-drawer-direction=top]/drawer-content:mb-4 tw:group-data-[vaul-drawer-direction=top]/drawer-content:h-1.5 tw:group-data-[vaul-drawer-direction=top]/drawer-content:w-[100px] tw:group-data-[vaul-drawer-direction=top]/drawer-content:block tw:group-data-[vaul-drawer-direction=left]/drawer-content:my-auto tw:group-data-[vaul-drawer-direction=left]/drawer-content:me-4 tw:group-data-[vaul-drawer-direction=left]/drawer-content:h-[100px] tw:group-data-[vaul-drawer-direction=left]/drawer-content:w-1.5 tw:group-data-[vaul-drawer-direction=left]/drawer-content:block"})]})]})}function ow({className:t,...e}){return r.jsx("div",{"data-slot":"drawer-header",className:h("pr-twp tw:flex tw:flex-col tw:gap-0.5 tw:p-4 tw:group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center tw:group-data-[vaul-drawer-direction=top]/drawer-content:text-center tw:md:gap-0.5 tw:md:text-start",t),...e})}function nw({className:t,...e}){return r.jsx("div",{"data-slot":"drawer-footer",className:h("pr-twp tw:mt-auto tw:flex tw:flex-col tw:gap-2 tw:p-4",t),...e})}function iw({className:t,...e}){return r.jsx(Te.Drawer.Title,{"data-slot":"drawer-title",className:h("pr-twp tw:font-heading tw:text-base tw:font-medium tw:text-foreground",t),...e})}function sw({className:t,...e}){return r.jsx(Te.Drawer.Description,{"data-slot":"drawer-description",className:h("pr-twp tw:text-sm tw:text-muted-foreground",t),...e})}function cw({className:t,value:e,...a}){return r.jsx(k.Progress.Root,{"data-slot":"progress",className:h("pr-twp tw:relative tw:flex tw:h-1 tw:w-full tw:items-center tw:overflow-x-hidden tw:rounded-full tw:bg-muted",t),...a,children:r.jsx(k.Progress.Indicator,{"data-slot":"progress-indicator",className:"tw:size-full tw:flex-1 tw:bg-primary tw:transition-all",style:{transform:`translateX(-${100-(e||0)}%)`}})})}function lw({className:t,direction:e,onLayout:a,orientation:o,...n}){return r.jsx(ba.Group,{"data-slot":"resizable-panel-group",className:h("tw:flex tw:h-full tw:w-full tw:aria-[orientation=vertical]:flex-col",t),orientation:o??e,onLayoutChange:a?i=>a(Object.values(i)):void 0,...n})}function kr(t){if(t!==void 0)return typeof t=="number"?`${t}%`:t}function dw({defaultSize:t,minSize:e,maxSize:a,collapsedSize:o,...n}){return r.jsx(ba.Panel,{"data-slot":"resizable-panel",defaultSize:kr(t),minSize:kr(e),maxSize:kr(a),collapsedSize:kr(o),...n})}function ww({withHandle:t,className:e,...a}){return r.jsx(ba.Separator,{"data-slot":"resizable-handle",className:h("tw:relative tw:flex tw:w-px tw:items-center tw:justify-center tw:bg-border tw:ring-offset-background tw:after:absolute tw:after:inset-y-0 tw:after:start-1/2 tw:after:w-1 tw:after:-translate-x-1/2 tw:rtl:after:translate-x-1/2 tw:focus-visible:ring-1 tw:focus-visible:ring-ring tw:focus-visible:outline-hidden tw:aria-[orientation=horizontal]:h-px tw:aria-[orientation=horizontal]:w-full tw:aria-[orientation=horizontal]:after:start-0 tw:aria-[orientation=horizontal]:after:h-1 tw:aria-[orientation=horizontal]:after:w-full tw:aria-[orientation=horizontal]:after:translate-x-0 tw:rtl:aria-[orientation=horizontal]:after:-translate-x-0 tw:aria-[orientation=horizontal]:after:-translate-y-1/2 tw:[&[aria-orientation=horizontal]>div]:rotate-90",e),...a,children:t&&r.jsx("div",{className:"tw:z-10 tw:flex tw:h-6 tw:w-1 tw:shrink-0 tw:rounded-lg tw:bg-border"})})}function uw({...t}){const{theme:e="system"}=ki.useTheme(),a=e==="light"||e==="dark"||e==="system"?e:"system",o={"--normal-bg":"var(--popover)","--normal-text":"var(--popover-foreground)","--normal-border":"var(--border)","--border-radius":"var(--radius)"};return r.jsx(Co.Toaster,{theme:a,className:"tw:toaster tw:group",icons:{success:r.jsx(ht.IconCircleCheck,{className:"tw:size-4"}),info:r.jsx(ht.IconInfoCircle,{className:"tw:size-4"}),warning:r.jsx(ht.IconAlertTriangle,{className:"tw:size-4"}),error:r.jsx(ht.IconAlertOctagon,{className:"tw:size-4"}),loading:r.jsx(ht.IconLoader,{className:"tw:size-4 tw:animate-spin"})},style:o,toastOptions:{classNames:{toast:"cn-toast"}},...t})}function pw({className:t,defaultValue:e,value:a,min:o=0,max:n=100,...i}){const s=ft(),c=l.useMemo(()=>Array.isArray(a)?a:Array.isArray(e)?e:[o,n],[a,e,o,n]);return r.jsxs(k.Slider.Root,{"data-slot":"slider",defaultValue:e,value:a,min:o,max:n,className:h("pr-twp tw:relative tw:flex tw:w-full tw:touch-none tw:items-center tw:select-none tw:data-disabled:opacity-50 tw:data-vertical:h-full tw:data-vertical:min-h-40 tw:data-vertical:w-auto tw:data-vertical:flex-col",t),dir:s,...i,children:[r.jsx(k.Slider.Track,{"data-slot":"slider-track",className:"tw:relative tw:grow tw:overflow-hidden tw:rounded-full tw:bg-muted tw:data-horizontal:h-1 tw:data-horizontal:w-full tw:data-vertical:h-full tw:data-vertical:w-1",children:r.jsx(k.Slider.Range,{"data-slot":"slider-range",className:"tw:absolute tw:bg-primary tw:select-none tw:data-horizontal:h-full tw:data-vertical:w-full"})}),Array.from({length:c.length},(w,d)=>r.jsx(k.Slider.Thumb,{"data-slot":"slider-thumb",className:"tw:relative tw:block tw:size-3 tw:shrink-0 tw:rounded-full tw:border tw:border-ring tw:bg-white tw:ring-ring/50 tw:transition-[color,box-shadow] tw:select-none tw:after:absolute tw:after:-inset-2 tw:hover:ring-3 tw:focus-visible:ring-3 tw:focus-visible:outline-hidden tw:active:ring-3 tw:disabled:pointer-events-none tw:disabled:opacity-50"},d))]})}function gw({className:t,size:e="default",...a}){return r.jsx(k.Switch.Root,{"data-slot":"switch","data-size":e,className:h("tw:peer pr-twp tw:group/switch tw:relative tw:inline-flex tw:shrink-0 tw:items-center tw:rounded-full tw:border tw:border-transparent tw:transition-all tw:outline-none tw:after:absolute tw:after:-inset-x-3 tw:after:-inset-y-2 tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:data-[size=default]:h-[18.4px] tw:data-[size=default]:w-[32px] tw:data-[size=sm]:h-[14px] tw:data-[size=sm]:w-[24px] tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:data-checked:bg-primary tw:data-unchecked:bg-input tw:dark:data-unchecked:bg-input/80 tw:data-disabled:cursor-not-allowed tw:data-disabled:opacity-50",t),...a,children:r.jsx(k.Switch.Thumb,{"data-slot":"switch-thumb",className:"tw:pointer-events-none tw:block tw:rounded-full tw:bg-background tw:ring-0 tw:transition-transform tw:group-data-[size=default]/switch:size-4 tw:group-data-[size=sm]/switch:size-3 tw:group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] tw:rtl:group-data-[size=default]/switch:data-checked:-translate-x-[calc(100%-2px)] tw:group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] tw:rtl:group-data-[size=sm]/switch:data-checked:-translate-x-[calc(100%-2px)] tw:dark:data-checked:bg-primary-foreground tw:group-data-[size=default]/switch:data-unchecked:translate-x-0 tw:rtl:group-data-[size=default]/switch:data-unchecked:-translate-x-0 tw:group-data-[size=sm]/switch:data-unchecked:translate-x-0 tw:rtl:group-data-[size=sm]/switch:data-unchecked:-translate-x-0 tw:dark:data-unchecked:bg-foreground"})})}function hw({className:t,orientation:e="horizontal",...a}){return r.jsx(k.Tabs.Root,{"data-slot":"tabs","data-orientation":e,className:h("tw:group/tabs tw:flex tw:gap-2 tw:data-horizontal:flex-col",t),...a})}const fw=ge.cva("tw:group/tabs-list tw:inline-flex tw:w-fit tw:items-center tw:justify-center tw:rounded-lg tw:p-[3px] tw:text-muted-foreground tw:group-data-horizontal/tabs:h-8 tw:group-data-vertical/tabs:h-fit tw:group-data-vertical/tabs:flex-col tw:data-[variant=line]:rounded-none",{variants:{variant:{default:"tw:bg-muted",line:"tw:gap-1 tw:bg-transparent"}},defaultVariants:{variant:"default"}});function mw({className:t,variant:e="default",...a}){const o=ft();return r.jsx(k.Tabs.List,{"data-slot":"tabs-list","data-variant":e,className:h("pr-twp",fw({variant:e}),t),dir:o,...a})}function vw({className:t,...e}){return r.jsx(k.Tabs.Trigger,{"data-slot":"tabs-trigger",className:h("pr-twp tw:relative tw:inline-flex tw:h-[calc(100%-1px)] tw:flex-1 tw:items-center tw:justify-center tw:gap-1.5 tw:rounded-md tw:border tw:border-transparent tw:px-1.5 tw:py-0.5 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:text-foreground/60 tw:transition-all tw:group-data-vertical/tabs:w-full tw:group-data-vertical/tabs:justify-start tw:hover:text-foreground tw:focus-visible:border-ring tw:focus-visible:ring-[3px] tw:focus-visible:ring-ring/50 tw:focus-visible:outline-1 tw:focus-visible:outline-ring tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:has-data-[icon=inline-end]:pe-1 tw:has-data-[icon=inline-start]:ps-1 tw:dark:text-muted-foreground tw:dark:hover:text-foreground tw:group-data-[variant=default]/tabs-list:data-active:shadow-sm tw:group-data-[variant=line]/tabs-list:data-active:shadow-none tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4","tw:group-data-[variant=line]/tabs-list:bg-transparent tw:group-data-[variant=line]/tabs-list:data-active:bg-transparent tw:dark:group-data-[variant=line]/tabs-list:data-active:border-transparent tw:dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent","tw:data-active:bg-background tw:data-active:text-foreground tw:dark:data-active:border-input tw:dark:data-active:bg-input/30 tw:dark:data-active:text-foreground","tw:after:absolute tw:after:bg-foreground tw:after:opacity-0 tw:after:transition-opacity tw:group-data-horizontal/tabs:after:inset-x-0 tw:group-data-horizontal/tabs:after:bottom-[-5px] tw:group-data-horizontal/tabs:after:h-0.5 tw:group-data-vertical/tabs:after:inset-y-0 tw:group-data-vertical/tabs:after:-end-1 tw:group-data-vertical/tabs:after:w-0.5 tw:group-data-[variant=line]/tabs-list:data-active:after:opacity-100",t),...e})}function bw({className:t,...e}){return r.jsx(k.Tabs.Content,{"data-slot":"tabs-content",className:h("pr-twp tw:flex-1 tw:text-sm tw:outline-none",t),...e})}const xw=(t,e)=>{l.useEffect(()=>{if(!t)return()=>{};const a=t(e);return()=>{a()}},[t,e])};function yw(t){return{preserveValue:!0,...t}}const ci=(t,e,a={})=>{const o=l.useRef(e);o.current=e;const n=l.useRef(a);n.current=yw(n.current);const[i,s]=l.useState(()=>o.current),[c,w]=l.useState(!0);return l.useEffect(()=>{let d=!0;return w(!!t),(async()=>{if(t){const u=await t();d&&(s(()=>u),w(!1))}})(),()=>{d=!1,n.current.preserveValue||s(()=>o.current)}},[t]),[i,c]},sa=()=>!1,kw=(t,e)=>{const[a]=ci(l.useCallback(async()=>{if(!t)return sa;const o=await Promise.resolve(t(e));return async()=>o()},[e,t]),sa,{preserveValue:!1});l.useEffect(()=>()=>{a!==sa&&a()},[a])};function jw(t){l.useEffect(()=>{let e;return t&&(e=document.createElement("style"),e.appendChild(document.createTextNode(t)),document.head.appendChild(e)),()=>{e&&document.head.removeChild(e)}},[t])}Object.defineProperty(exports,"sonner",{enumerable:!0,get:()=>Co.toast});exports.Alert=$d;exports.AlertDescription=Pd;exports.AlertTitle=Ad;exports.Avatar=vn;exports.AvatarFallback=bn;exports.AvatarImage=Cc;exports.BOOK_CHAPTER_CONTROL_STRING_KEYS=Ki;exports.BOOK_SELECTOR_STRING_KEYS=Yi;exports.Badge=pe;exports.BookChapterControl=Nr;exports.BookSelector=Wi;exports.Button=F;exports.ButtonGroup=Gr;exports.ButtonGroupSeparator=Ma;exports.ButtonGroupText=hc;exports.CANCEL_ACCEPT_BUTTONS_STRING_KEYS=Oa;exports.COMMENT_EDITOR_STRING_KEYS=vc;exports.COMMENT_LIST_STRING_KEYS=bc;exports.CancelAcceptButtons=$a;exports.Card=fn;exports.CardContent=mn;exports.CardDescription=_c;exports.CardFooter=Nc;exports.CardHeader=kc;exports.CardTitle=jc;exports.ChapterRangeSelector=Bo;exports.Checkbox=Va;exports.Checklist=zd;exports.ComboBox=wa;exports.Command=he;exports.CommandEmpty=Ze;exports.CommandGroup=re;exports.CommandInput=Fe;exports.CommandItem=ie;exports.CommandList=fe;exports.CommentEditor=mc;exports.CommentList=Tc;exports.ContextMenu=Ld;exports.ContextMenuCheckboxItem=Wd;exports.ContextMenuContent=qd;exports.ContextMenuGroup=Vd;exports.ContextMenuItem=Kd;exports.ContextMenuLabel=Zd;exports.ContextMenuPortal=Fd;exports.ContextMenuRadioGroup=Ud;exports.ContextMenuRadioItem=Xd;exports.ContextMenuSeparator=Jd;exports.ContextMenuShortcut=Qd;exports.ContextMenuSub=Gd;exports.ContextMenuSubContent=Yd;exports.ContextMenuSubTrigger=Hd;exports.ContextMenuTrigger=Bd;exports.DataTable=Tn;exports.Dialog=Er;exports.DialogClose=Ti;exports.DialogContent=Tr;exports.DialogDescription=Ri;exports.DialogFooter=da;exports.DialogHeader=Rr;exports.DialogOverlay=zo;exports.DialogPortal=Ro;exports.DialogTitle=zr;exports.DialogTrigger=Ei;exports.Drawer=tw;exports.DrawerClose=rw;exports.DrawerContent=aw;exports.DrawerDescription=sw;exports.DrawerFooter=nw;exports.DrawerHeader=ow;exports.DrawerOverlay=si;exports.DrawerPortal=ii;exports.DrawerTitle=iw;exports.DrawerTrigger=ew;exports.DropdownMenu=ae;exports.DropdownMenuCheckboxItem=ee;exports.DropdownMenuContent=ne;exports.DropdownMenuGroup=La;exports.DropdownMenuItem=Se;exports.DropdownMenuItemType=In;exports.DropdownMenuLabel=Ee;exports.DropdownMenuPortal=xn;exports.DropdownMenuRadioGroup=yn;exports.DropdownMenuRadioItem=kn;exports.DropdownMenuSeparator=ye;exports.DropdownMenuShortcut=Sc;exports.DropdownMenuSub=jn;exports.DropdownMenuSubContent=Nn;exports.DropdownMenuSubTrigger=_n;exports.DropdownMenuTrigger=oe;exports.ERROR_DUMP_STRING_KEYS=zn;exports.ERROR_POPOVER_STRING_KEYS=Xc;exports.EditorKeyboardShortcuts=An;exports.ErrorDump=Dn;exports.ErrorPopover=Zc;exports.FOOTNOTE_EDITOR_STRING_KEYS=gl;exports.Filter=rl;exports.FilterDropdown=Jc;exports.Footer=el;exports.FootnoteEditor=pl;exports.FootnoteItem=Vn;exports.FootnoteList=ml;exports.INVENTORY_STRING_KEYS=El;exports.Input=Xe;exports.Inventory=zl;exports.Kbd=ha;exports.Label=yt;exports.LinkedScrRefButton=Dd;exports.MARKER_MENU_STRING_KEYS=Pn;exports.MarkdownRenderer=Wc;exports.MarkerMenu=Ln;exports.MoreInfo=Qc;exports.MultiSelectComboBox=Mn;exports.NavigationContentSearch=gd;exports.Popover=se;exports.PopoverAnchor=$o;exports.PopoverContent=ce;exports.PopoverDescription=Li;exports.PopoverHeader=Ai;exports.PopoverPortalContainerProvider=jr;exports.PopoverTitle=Pi;exports.PopoverTrigger=me;exports.Progress=cw;exports.ProjectSelector=Rn;exports.RadioGroup=Na;exports.RadioGroupItem=Dr;exports.RecentSearches=Po;exports.ResizableHandle=ww;exports.ResizablePanel=dw;exports.ResizablePanelGroup=lw;exports.ResultsCard=Id;exports.SCOPE_SELECTOR_STRING_KEYS=nd;exports.ScopeSelector=sd;exports.ScriptureResultsViewer=rd;exports.ScrollGroupSelector=cd;exports.SearchBar=Hr;exports.Select=Ae;exports.SelectContent=Be;exports.SelectGroup=Cn;exports.SelectItem=Jt;exports.SelectLabel=zc;exports.SelectScrollDownButton=En;exports.SelectScrollUpButton=Sn;exports.SelectSeparator=Dc;exports.SelectTrigger=Le;exports.SelectValue=Pe;exports.Separator=$e;exports.SettingsList=ld;exports.SettingsListHeader=wd;exports.SettingsListItem=dd;exports.SettingsSidebar=Zn;exports.SettingsSidebarContentSearch=Yl;exports.Sidebar=qn;exports.SidebarContent=Hn;exports.SidebarFooter=Pl;exports.SidebarGroup=fa;exports.SidebarGroupAction=Bl;exports.SidebarGroupContent=va;exports.SidebarGroupLabel=ma;exports.SidebarHeader=Al;exports.SidebarInput=$l;exports.SidebarInset=Kn;exports.SidebarMenu=Yn;exports.SidebarMenuAction=Fl;exports.SidebarMenuBadge=Gl;exports.SidebarMenuButton=Xn;exports.SidebarMenuItem=Wn;exports.SidebarMenuSkeleton=Ul;exports.SidebarMenuSub=ql;exports.SidebarMenuSubButton=Hl;exports.SidebarMenuSubItem=Kl;exports.SidebarProvider=Un;exports.SidebarRail=Ol;exports.SidebarSeparator=Ll;exports.SidebarTrigger=Ml;exports.Skeleton=Pr;exports.Slider=pw;exports.Sonner=uw;exports.Spinner=ni;exports.Switch=gw;exports.TabDropdownMenu=Br;exports.TabFloatingMenu=pd;exports.TabToolbar=ud;exports.Table=Ur;exports.TableBody=Kr;exports.TableCaption=Lc;exports.TableCell=Oe;exports.TableFooter=Oc;exports.TableHead=dr;exports.TableHeader=qr;exports.TableRow=be;exports.Tabs=hw;exports.TabsContent=bw;exports.TabsList=mw;exports.TabsTrigger=vw;exports.TextField=Md;exports.Textarea=zi;exports.ToggleGroup=Da;exports.ToggleGroupItem=sr;exports.Toolbar=Sd;exports.Tooltip=$t;exports.TooltipContent=Pt;exports.TooltipProvider=Ot;exports.TooltipTrigger=At;exports.UNDO_REDO_BUTTONS_STRING_KEYS=On;exports.UiLanguageSelector=Td;exports.UndoRedoButtons=$n;exports.VerticalTabs=Ga;exports.VerticalTabsContent=qa;exports.VerticalTabsList=Ua;exports.VerticalTabsTrigger=ai;exports.Z_INDEX_ABOVE_DOCK=We;exports.Z_INDEX_FOOTNOTE_EDITOR=xa;exports.Z_INDEX_MODAL=Eo;exports.Z_INDEX_MODAL_BACKDROP=So;exports.Z_INDEX_OVERLAY=Vr;exports.Z_INDEX_TOOLTIP=To;exports.badgeVariants=hn;exports.buttonGroupVariants=pn;exports.buttonVariants=ya;exports.cn=h;exports.getBookIdFromUSFM=Sl;exports.getInventoryHeader=ur;exports.getLinesFromUSFM=Nl;exports.getNumberFromUSFM=Cl;exports.getStatusForItem=Fn;exports.getToolbarOSReservedSpaceClassName=Cd;exports.inventoryCountColumn=jl;exports.inventoryItemColumn=yl;exports.inventoryStatusColumn=_l;exports.useEvent=xw;exports.useEventAsync=kw;exports.useListbox=gn;exports.usePromise=ci;exports.useRecentSearches=Bi;exports.useSidebar=pr;exports.useStylesheet=jw;function _w(t,e="top"){if(!t||typeof document>"u")return;const a=document.head||document.querySelector("head"),o=a.querySelector(":first-child"),n=document.createElement("style");n.appendChild(document.createTextNode(t)),e==="top"&&o?a.insertBefore(n,o):a.appendChild(n)}_w(`/* By default the editor is too tall for the footnote editor, even while empty, so this makes it +`;function Hc(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)}function Cn(t,e){const r=e?`${Go}, ${e}`:Go;return Array.from(t.querySelectorAll(r)).filter(o=>!o.hasAttribute("disabled")&&!o.getAttribute("aria-hidden")&&Hc(o))}const En=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>{const s=i.useRef(null);i.useEffect(()=>{typeof o=="function"?o(s.current):o&&"current"in o&&(o.current=s.current)},[o]),i.useEffect(()=>{const l=s.current;if(!l)return;const c=()=>{requestAnimationFrame(()=>{Cn(l,'[tabindex]:not([tabindex="-1"])').forEach(u=>{u.setAttribute("tabindex","-1")})})};c();const w=new MutationObserver(()=>{c()});return w.observe(l,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["tabindex"]}),()=>{w.disconnect()}},[]);const a=l=>{const{current:c}=s;if(c){if(l.key==="ArrowDown"){l.preventDefault(),Cn(c)[0].focus();return}l.key===" "&&document.activeElement===c&&l.preventDefault()}};return n.jsx("div",{className:f("pr-twp tw-relative tw-w-full",{"tw-p-1":e}),children:n.jsx("table",{tabIndex:0,onKeyDown:a,ref:s,className:f("tw-w-full tw-caption-bottom tw-text-sm tw-outline-none","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background",t),"aria-label":"Table","aria-labelledby":"table-label",...r})})});En.displayName="Table";const Rn=i.forwardRef(({className:t,stickyHeader:e,...r},o)=>n.jsx("thead",{ref:o,className:f({"tw-sticky tw-top-[-1px] tw-z-20 tw-bg-background tw-drop-shadow-sm":e},"[&_tr]:tw-border-b",t),...r}));Rn.displayName="TableHeader";const Tn=i.forwardRef(({className:t,...e},r)=>n.jsx("tbody",{ref:r,className:f("[&_tr:last-child]:tw-border-0",t),...e}));Tn.displayName="TableBody";const sa=i.forwardRef(({className:t,...e},r)=>n.jsx("tfoot",{ref:r,className:f("tw-border-t tw-bg-muted/50 tw-font-medium [&>tr]:last:tw-border-b-0",t),...e}));sa.displayName="TableFooter";function Uc(t){i.useEffect(()=>{const e=t.current;if(!e)return;const r=o=>{if(e.contains(document.activeElement)){if(o.key==="ArrowRight"||o.key==="ArrowLeft"){o.preventDefault(),o.stopPropagation();const s=t.current?Cn(t.current):[],a=s.indexOf(document.activeElement),l=o.key==="ArrowRight"?a+1:a-1;l>=0&&l{e.removeEventListener("keydown",r)}},[t])}function Yc(t,e,r){let o;return r==="ArrowLeft"&&e>0?o=t[e-1]:r==="ArrowRight"&&eo.focus()),!0):!1}function Xc(t,e,r){let o;return r==="ArrowDown"&&e0&&(o=t[e-1]),o?(requestAnimationFrame(()=>o.focus()),!0):!1}const xe=i.forwardRef(({className:t,onKeyDown:e,onSelect:r,setFocusAlsoRunsSelect:o=!1,...s},a)=>{const l=i.useRef(null);i.useEffect(()=>{typeof a=="function"?a(l.current):a&&"current"in a&&(a.current=l.current)},[a]),Uc(l);const c=i.useMemo(()=>l.current?Cn(l.current):[],[l]),w=i.useCallback(u=>{const{current:m}=l;if(!m||!m.parentElement)return;const h=m.closest("table"),p=h?Cn(h).filter(v=>v.tagName==="TR"):[],g=p.indexOf(m),y=c.indexOf(document.activeElement);if(u.key==="ArrowDown"||u.key==="ArrowUp")u.preventDefault(),Xc(p,g,u.key);else if(u.key==="ArrowLeft"||u.key==="ArrowRight")u.preventDefault(),Yc(c,y,u.key);else if(u.key==="Escape"){u.preventDefault();const v=m.closest("table");v&&v.focus()}e==null||e(u)},[l,c,e]),d=i.useCallback(u=>{o&&(r==null||r(u))},[o,r]);return n.jsx("tr",{ref:l,tabIndex:-1,onKeyDown:w,onFocus:d,className:f("tw-border-b tw-outline-none tw-transition-colors hover:tw-bg-muted/50","focus:tw-relative focus:tw-z-10 focus:tw-ring-2 focus:tw-ring-ring focus:tw-ring-offset-1 focus:tw-ring-offset-background","data-[state=selected]:tw-bg-muted",t),...s})});xe.displayName="TableRow";const on=i.forwardRef(({className:t,...e},r)=>n.jsx("th",{ref:r,className:f("tw-h-12 tw-px-4 tw-text-start tw-align-middle tw-font-medium tw-text-muted-foreground [&:has([role=checkbox])]:tw-pe-0",t),...e}));on.displayName="TableHead";const Me=i.forwardRef(({className:t,...e},r)=>n.jsx("td",{ref:r,className:f("tw-p-4 tw-align-middle [&:has([role=checkbox])]:tw-pe-0",t),...e}));Me.displayName="TableCell";const aa=i.forwardRef(({className:t,...e},r)=>n.jsx("caption",{ref:r,className:f("tw-mt-4 tw-text-sm tw-text-muted-foreground",t),...e}));aa.displayName="TableCaption";function Qn({className:t,...e}){return n.jsx("div",{className:f("pr-twp tw-animate-pulse tw-rounded-md tw-bg-muted",t),...e})}function ia({columns:t,data:e,enablePagination:r=!1,showPaginationControls:o=!1,showColumnVisibilityControls:s=!1,stickyHeader:a=!1,onRowClickHandler:l=()=>{},id:c,isLoading:w=!1,noResultsMessage:d}){var D;const[u,m]=i.useState([]),[h,p]=i.useState([]),[g,y]=i.useState({}),[v,j]=i.useState({}),R=i.useMemo(()=>e??[],[e]),S=Gt.useReactTable({data:R,columns:t,getCoreRowModel:Gt.getCoreRowModel(),...r&&{getPaginationRowModel:Gt.getPaginationRowModel()},onSortingChange:m,getSortedRowModel:Gt.getSortedRowModel(),onColumnFiltersChange:p,getFilteredRowModel:Gt.getFilteredRowModel(),onColumnVisibilityChange:y,onRowSelectionChange:j,state:{sorting:u,columnFilters:h,columnVisibility:g,rowSelection:v}}),C=S.getVisibleFlatColumns();let N;return w?N=Array.from({length:10}).map((b,M)=>`skeleton-row-${M}`).map(b=>n.jsx(xe,{className:"hover:tw-bg-transparent",children:n.jsx(Me,{colSpan:C.length??t.length,className:"tw-border-0 tw-p-0",children:n.jsx("div",{className:"tw-w-full tw-py-2",children:n.jsx(Qn,{className:"tw-h-14 tw-w-full tw-rounded-md"})})})},b)):((D=S.getRowModel().rows)==null?void 0:D.length)>0?N=S.getRowModel().rows.map(T=>n.jsx(xe,{onClick:()=>l(T,S),"data-state":T.getIsSelected()&&"selected",children:T.getVisibleCells().map(E=>n.jsx(Me,{children:Gt.flexRender(E.column.columnDef.cell,E.getContext())},E.id))},T.id)):N=n.jsx(xe,{children:n.jsx(Me,{colSpan:t.length,className:"tw-h-24 tw-text-center",children:d})}),n.jsxs("div",{className:"pr-twp",id:c,children:[s&&n.jsx(qc,{table:S}),n.jsxs(En,{stickyHeader:a,children:[n.jsx(Rn,{stickyHeader:a,children:S.getHeaderGroups().map(T=>n.jsx(xe,{children:T.headers.map(E=>n.jsx(on,{className:"tw-p-0",children:E.isPlaceholder?void 0:Gt.flexRender(E.column.columnDef.header,E.getContext())},E.id))},T.id))}),n.jsx(Tn,{children:N})]}),r&&n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-end tw-space-x-2 tw-py-4",children:[n.jsx(G,{variant:"outline",size:"sm",onClick:()=>S.previousPage(),disabled:!S.getCanPreviousPage(),children:"Previous"}),n.jsx(G,{variant:"outline",size:"sm",onClick:()=>S.nextPage(),disabled:!S.getCanNextPage(),children:"Next"})]}),r&&o&&n.jsx(Kc,{table:S})]})}function Wc(t){const e=new Map;return t.forEach(r=>{const o=e.get(r.projectId),s={scrollGroupId:r.scrollGroupId,scrollGroupScrRefLabel:r.scrollGroupScrRefLabel};o?o.some(a=>a.scrollGroupId===r.scrollGroupId)||o.push(s):e.set(r.projectId,[s])}),e.forEach(r=>r.sort((o,s)=>o.scrollGroupId-s.scrollGroupId)),e}function zo(t,e,r){return t.some(o=>o.projectId===e&&o.scrollGroupId===r)}function jr(t){const e=Wc(t.openTabs);if(t.mode==="project"){const s=t.selection.projectId;return t.projects.map(a=>{const l=e.get(a.id)??[];return{rowKey:a.id,projectId:a.id,shortName:a.shortName,fullName:a.fullName,language:a.language,languageCode:a.languageCode,scrollGroupId:void 0,scrollGroupScrRefLabel:void 0,openGroups:l.map(c=>c.scrollGroupId),isSelected:s===a.id,isMuted:l.length===0,isBoundButClosed:!1,isDisabled:a.isDisabled===!0,disabledReason:a.disabledReason}})}let r=[];t.mode==="project-multi"?r=t.selection.pairs:t.selection.projectId!==void 0&&(r=[{projectId:t.selection.projectId,scrollGroupId:t.selection.scrollGroupId}]);const o=[];return t.projects.forEach(s=>{const a=e.get(s.id);if(!a||a.length===0){o.push({rowKey:`project:${s.id}`,projectId:s.id,shortName:s.shortName,fullName:s.fullName,language:s.language,languageCode:s.languageCode,scrollGroupId:void 0,scrollGroupScrRefLabel:void 0,openGroups:[],isSelected:zo(r,s.id,void 0),isMuted:!0,isBoundButClosed:!1,isDisabled:s.isDisabled===!0,disabledReason:s.disabledReason});return}a.forEach(l=>{o.push({rowKey:`tab:${s.id}:${l.scrollGroupId}`,projectId:s.id,shortName:s.shortName,fullName:s.fullName,language:s.language,languageCode:s.languageCode,scrollGroupId:l.scrollGroupId,scrollGroupScrRefLabel:l.scrollGroupScrRefLabel,openGroups:[],isSelected:zo(r,s.id,l.scrollGroupId),isMuted:!1,isBoundButClosed:!1,isDisabled:s.isDisabled===!0,disabledReason:s.disabledReason})})}),r.forEach(s=>{if(s.scrollGroupId===void 0||o.some(l=>l.projectId===s.projectId&&l.scrollGroupId===s.scrollGroupId))return;const a=t.projects.find(l=>l.id===s.projectId);a&&o.push({rowKey:`closed:${a.id}:${s.scrollGroupId}`,projectId:a.id,shortName:a.shortName,fullName:a.fullName,language:a.language,languageCode:a.languageCode,scrollGroupId:s.scrollGroupId,scrollGroupScrRefLabel:void 0,openGroups:[],isSelected:!0,isMuted:!1,isBoundButClosed:!0,isDisabled:a.isDisabled===!0,disabledReason:a.disabledReason})}),o}function qo(t){return t.isBoundButClosed?!1:t.scrollGroupId!==void 0?!0:t.openGroups.length>0}function Nr(t,e){if(t.isSelected!==e.isSelected)return t.isSelected?-1:1;const r=t.shortName.localeCompare(e.shortName,void 0,{sensitivity:"base"});if(r!==0)return r;const o=t.scrollGroupId??Number.POSITIVE_INFINITY,s=e.scrollGroupId??Number.POSITIVE_INFINITY;return o-s}function Zc(t,e){if(!e)return[{kind:"flat",rows:[...t].sort(Nr)}];const r=t.filter(qo).sort(Nr),o=t.filter(a=>!qo(a)).sort(Nr);if(r.length===0)return[{kind:"flat",rows:o}];const s=[{kind:"openTabs",rows:r}];return o.length>0&&s.push({kind:"other",rows:o}),s}const Jc={searchPlaceholder:"Search projects & resources",filterAriaLabel:"Filter",groupSectionLabel:"Group",filterSectionLabel:"Filter",filterGroupByOpenTabs:"By open tabs",filterShowSelectedOnly:"Show selected only",openTabsSectionHeading:"Opened project & resource tabs",otherProjectsSectionHeading:"Your projects & resources",boundButClosedTooltip:"Bound to {group} · not currently open",openButtonLabel:"Open",selectAll:"Select all",clearAll:"Clear all"};function Qc(t){return{...Jc,...t}}function Sn(t){return I.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[I.getLocalizeKeyForScrollGroupId(t)]??String(t)}const td={backgroundImage:"linear-gradient(to top right, transparent calc(50% - 1px), currentColor calc(50% - 0.5px), currentColor calc(50% + 0.5px), transparent calc(50% + 1px))"};function ed({scrollGroupId:t,isBoundButClosed:e}){const r=Sn(t);return e?n.jsx(ae,{variant:"outline",className:"tw-relative tw-text-muted-foreground",style:td,children:r}):n.jsx(ae,{variant:"secondary",children:r})}function nd({row:t,mode:e,strings:r,onClick:o,onOpen:s}){const[a,l]=i.useState(!1),c=i.useRef(null),w=!!(t.language||t.languageCode),d=w||!!t.scrollGroupScrRefLabel||t.isBoundButClosed||t.isDisabled&&!!t.disabledReason,u=i.useCallback(()=>{if(d){l(!0);return}const v=c.current;v&&v.scrollWidth>v.clientWidth&&l(!0)},[d]),m=n.jsx(_.Check,{className:f("tw-h-4 tw-w-4",t.isSelected?"tw-opacity-100":"tw-opacity-0")});let h;e==="project"?t.openGroups.length>0&&(h=n.jsx("span",{className:"tw-ms-auto tw-flex tw-shrink-0 tw-gap-1",children:t.openGroups.map(v=>n.jsx(ae,{variant:"secondary",children:Sn(v)},v))})):t.scrollGroupId!==void 0&&(h=n.jsxs("span",{className:"tw-ms-auto tw-flex tw-shrink-0 tw-items-center tw-gap-2",children:[n.jsx(ed,{scrollGroupId:t.scrollGroupId,isBoundButClosed:t.isBoundButClosed}),t.isBoundButClosed&&s&&n.jsxs(G,{size:"sm",variant:"ghost",className:"tw-h-6 tw-gap-1 tw-px-2 tw-text-xs",onClick:v=>{v.stopPropagation(),s(t)},onMouseDown:v=>v.stopPropagation(),"aria-label":r.openButtonLabel,title:r.openButtonLabel,children:[n.jsx(_.ArrowRight,{className:"tw-h-3 tw-w-3"}),r.openButtonLabel]})]}));const p=n.jsxs(ne,{value:`${t.rowKey} ${t.shortName} ${t.fullName} ${t.language??""} ${t.languageCode??""}`,onSelect:()=>{t.isDisabled||o(t)},disabled:t.isDisabled,onPointerEnter:u,onPointerLeave:()=>l(!1),className:"tw-flex tw-items-center tw-gap-2 tw-pe-4","data-selected":t.isSelected,children:[n.jsx("span",{className:"tw-flex tw-h-4 tw-w-4 tw-shrink-0 tw-items-center tw-justify-center",children:m}),n.jsxs("span",{ref:c,className:"tw-min-w-0 tw-flex-1 tw-truncate tw-text-start",children:[n.jsx("span",{children:t.shortName}),n.jsxs("span",{className:"tw-text-muted-foreground",children:[" • ",t.fullName]})]}),h]}),g=t.scrollGroupId!==void 0?Sn(t.scrollGroupId):void 0,y=t.isBoundButClosed&&g?r.boundButClosedTooltip.replace("{group}",g):void 0;return n.jsxs(It,{open:a,delayDuration:400,children:[n.jsx(Mt,{asChild:!0,children:p}),n.jsxs(St,{side:"top",align:"center",sideOffset:8,collisionPadding:16,className:"tw-max-w-xs tw-text-center",style:{zIndex:ar},children:[n.jsx("div",{className:"tw-font-semibold",children:t.fullName}),w&&n.jsxs("div",{className:"tw-text-sm",children:[t.language,t.languageCode&&n.jsxs("span",{className:"tw-text-muted-foreground",children:[" (",t.languageCode,")"]})]}),!t.isBoundButClosed&&t.scrollGroupScrRefLabel&&g&&n.jsxs("div",{className:"tw-text-sm",children:[t.scrollGroupScrRefLabel,n.jsxs("span",{className:"tw-text-muted-foreground",children:[" (",g,")"]})]}),y&&n.jsx("div",{className:"tw-text-sm tw-italic",children:y}),t.isDisabled&&t.disabledReason&&n.jsx("div",{className:"tw-text-sm tw-italic tw-text-muted-foreground",children:t.disabledReason})]})]})}function rd({groupByOpenTabs:t,onChangeGroupByOpenTabs:e,showSelectedOnly:r,onChangeShowSelectedOnly:o,strings:s}){const a=!!r;return n.jsxs(ie,{children:[n.jsx(ve,{asChild:!0,children:n.jsx(G,{variant:"ghost",size:"sm",className:f("tw-h-8 tw-w-8 tw-shrink-0 tw-p-0",a&&"tw-bg-accent tw-text-accent-foreground hover:tw-bg-accent/80 data-[state=open]:tw-bg-accent"),"aria-label":s.filterAriaLabel,"aria-pressed":a,title:s.filterAriaLabel,onMouseDown:l=>l.preventDefault(),children:n.jsx(_.Filter,{className:"tw-h-4 tw-w-4"})})}),n.jsxs(ee,{align:"end",className:"tw-w-56",style:{zIndex:ar},children:[n.jsx(Ce,{children:s.groupSectionLabel}),n.jsx(Qt,{checked:t,onCheckedChange:e,onSelect:l=>l.preventDefault(),children:s.filterGroupByOpenTabs}),o&&n.jsxs(n.Fragment,{children:[n.jsx(ye,{}),n.jsx(Ce,{children:s.filterSectionLabel}),n.jsx(Qt,{checked:!!r,onCheckedChange:o,onSelect:l=>l.preventDefault(),children:s.filterShowSelectedOnly})]})]})]})}function la(t){const[e,r]=i.useState(!1),[o,s]=i.useState(""),[a,l]=i.useState(t.defaultGroupByOpenTabs??!0),[c,w]=i.useState(!1),d=Qc(t.localizedStrings),u=i.useMemo(()=>t.mode==="project"?jr({mode:"project",projects:t.projects,openTabs:t.openTabs,selection:t.selection}):t.mode==="project-multi"?jr({mode:"project-multi",projects:t.projects,openTabs:t.openTabs,selection:t.selection}):jr({mode:"projectScrollGroup",projects:t.projects,openTabs:t.openTabs,selection:t.selection}),[t.mode,t.projects,t.openTabs,t.selection]),m=i.useMemo(()=>{const N=o.trim().toLowerCase();let D=u;return N&&(D=D.filter(T=>T.shortName.toLowerCase().includes(N)||T.fullName.toLowerCase().includes(N)||(T.language??"").toLowerCase().includes(N)||(T.languageCode??"").toLowerCase().includes(N))),t.mode==="project-multi"&&c&&(D=D.filter(T=>T.isSelected)),D},[u,o,t.mode,c]),h=i.useMemo(()=>Zc(m,a),[m,a]),p=i.useMemo(()=>{if(t.mode!=="project-multi")return[];const N=[];return t.projects.forEach(D=>{const T=t.openTabs.filter(b=>b.projectId===D.id);if(T.length===0){N.push({projectId:D.id});return}const E=new Set;T.forEach(b=>{E.has(b.scrollGroupId)||(E.add(b.scrollGroupId),N.push({projectId:D.id,scrollGroupId:b.scrollGroupId}))})}),N},[t.mode,t.projects,t.openTabs]),g=N=>{if(N.scrollGroupId!==void 0){if(t.mode==="projectScrollGroup"){t.onOpenProjectInGroup(N.projectId,N.scrollGroupId);return}t.mode==="project-multi"&&t.onOpenProjectInGroup&&t.onOpenProjectInGroup(N.projectId,N.scrollGroupId)}},y=N=>{switch(t.mode){case"project":{t.onChangeSelection({projectId:N.projectId}),r(!1);return}case"project-multi":{const D=t.selection.pairs,T=b=>b.projectId===N.projectId&&b.scrollGroupId===N.scrollGroupId,E=D.some(T)?D.filter(b=>!T(b)):[...D,{projectId:N.projectId,scrollGroupId:N.scrollGroupId}];t.onChangeSelection({pairs:E}),E.length===0&&c&&w(!1);return}case"projectScrollGroup":{if(N.isBoundButClosed&&N.scrollGroupId!==void 0){t.onOpenProjectInGroup(N.projectId,N.scrollGroupId),r(!1);return}if(N.scrollGroupId!==void 0){t.onChangeSelection({projectId:N.projectId,scrollGroupId:N.scrollGroupId}),r(!1);return}const D=t.selection.scrollGroupId??0;t.onChangeSelection({projectId:N.projectId,scrollGroupId:D}),t.onOpenProjectInGroup(N.projectId,D),r(!1)}}},v=()=>{if(t.mode!=="project-multi")return;const N=t.selection.pairs,D=new Set(N.map(E=>`${E.projectId}:${E.scrollGroupId??""}`)),T=[...N];p.forEach(E=>{const b=`${E.projectId}:${E.scrollGroupId??""}`;D.has(b)||(D.add(b),T.push(E))}),t.onChangeSelection({pairs:T})},j=()=>{t.mode==="project-multi"&&(t.onChangeSelection({pairs:[]}),c&&w(!1))},R=i.useMemo(()=>{switch(t.mode){case"project":{const N=t.projects.find(T=>T.id===t.selection.projectId),D=N?N.shortName:t.buttonPlaceholder??"";return{node:D,title:D}}case"project-multi":{const{pairs:N}=t.selection;if(N.length===0){const b=t.buttonPlaceholder??"";return{node:b,title:b}}const D=[];if(N.forEach(b=>{const M=t.projects.find($=>$.id===b.projectId);M&&D.push({project:M,scrollGroupId:b.scrollGroupId})}),D.length===0){const b=t.buttonPlaceholder??"";return{node:b,title:b}}if(t.getSelectedText){const b=t.getSelectedText(D);return{node:b,title:b}}const T=D.map(({project:b,scrollGroupId:M})=>M===void 0?b.shortName:`${b.shortName} (${Sn(M)})`).join(", ");if(D.length===1)return{node:T,title:T};const E=D.length.toString();return{node:n.jsxs(n.Fragment,{children:[n.jsx(ae,{variant:"muted",className:"tw-shrink-0",children:E}),n.jsx("span",{className:"tw-min-w-0 tw-truncate",children:T})]}),title:`${E} ${T}`}}case"projectScrollGroup":{const N=t.projects.find(E=>E.id===t.selection.projectId);if(!N){const E=t.buttonPlaceholder??"";return{node:E,title:E}}const D=t.selection.scrollGroupId;if(D===void 0)return{node:N.shortName,title:N.shortName};const T=`${N.shortName} · ${Sn(D)}`;return{node:T,title:T}}default:return{node:"",title:""}}},[t]),S=t.mode==="project-multi"?n.jsx(_.ChevronsUpDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"}):n.jsx(_.ChevronDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"}),C=t.mode==="projectScrollGroup"||t.mode==="project-multi"&&t.onOpenProjectInGroup?g:void 0;return n.jsxs(we,{open:e,onOpenChange:r,children:[n.jsx(je,{asChild:!0,children:n.jsxs(G,{variant:t.buttonVariant??"outline",role:"combobox","aria-expanded":e,"aria-label":t.ariaLabel,disabled:t.isDisabled??!1,className:f("tw-flex tw-w-[180px] tw-items-center tw-justify-between tw-overflow-hidden",t.buttonClassName),children:[n.jsx("span",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-baseline tw-gap-2 tw-overflow-hidden tw-whitespace-nowrap tw-text-start",children:typeof R.node=="string"?n.jsx("span",{className:"tw-min-w-0 tw-truncate",children:R.node}):R.node}),S]})}),n.jsx(re,{align:t.alignDropDown??"start",collisionPadding:16,className:f("tw-w-80 tw-max-w-[calc(100vw-2rem)] tw-p-0",t.popoverContentClassName),style:t.popoverContentStyle,children:n.jsx(Ct,{delayDuration:400,children:n.jsxs(ce,{shouldFilter:!1,children:[n.jsxs("div",{className:"tw-flex tw-items-center tw-border-b tw-pe-2",children:[n.jsx("div",{className:"tw-flex-1",children:n.jsx($e,{value:o,onValueChange:s,placeholder:d.searchPlaceholder,className:"tw-border-0"})}),n.jsx(rd,{groupByOpenTabs:a,onChangeGroupByOpenTabs:l,showSelectedOnly:t.mode==="project-multi"?c:void 0,onChangeShowSelectedOnly:t.mode==="project-multi"?w:void 0,strings:d})]}),t.mode==="project-multi"&&n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-py-2 tw-pe-4 tw-ps-2",children:[t.hideSelectAll?n.jsx("span",{"aria-hidden":!0}):n.jsx(G,{variant:"ghost",size:"sm",onClick:v,children:`${d.selectAll} (${p.length.toString()})`}),n.jsx(G,{variant:"ghost",size:"sm",onClick:j,children:`${d.clearAll} (${t.selection.pairs.length.toString()})`})]}),n.jsxs(de,{children:[n.jsx(Ye,{children:t.commandEmptyMessage??"No projects found"}),h.map((N,D)=>n.jsxs(i.Fragment,{children:[n.jsx(te,{heading:od(N,d),children:N.rows.map(T=>n.jsx(nd,{row:T,mode:t.mode,strings:d,onClick:y,onOpen:C},T.rowKey))}),D({overrides:{a:{props:{target:o}}}}),[o]);return n.jsx("div",{id:t,className:f("pr-twp tw-prose",{"tw-line-clamp-3 tw-max-h-10 tw-overflow-hidden tw-text-ellipsis tw-break-words":s},r),children:n.jsx(Ki,{options:a,children:e})})}const ca=Object.freeze(["%webView_error_dump_header%","%webView_error_dump_info_message%"]),Ko=(t,e)=>t[e]??e;function da({errorDetails:t,handleCopyNotify:e,localizedStrings:r,id:o}){const s=Ko(r,"%webView_error_dump_header%"),a=Ko(r,"%webView_error_dump_info_message%");function l(){navigator.clipboard.writeText(t),e&&e()}return n.jsxs("div",{id:o,className:"tw-inline-flex tw-w-full tw-flex-col tw-items-start tw-justify-start tw-gap-4",children:[n.jsxs("div",{className:"tw-inline-flex tw-items-start tw-justify-start tw-gap-4 tw-self-stretch",children:[n.jsxs("div",{className:"tw-inline-flex tw-flex-1 tw-flex-col tw-items-start tw-justify-start",children:[n.jsx("div",{className:"tw-text-color-text tw-justify-center tw-text-center tw-text-lg tw-font-semibold tw-leading-loose",children:s}),n.jsx("div",{className:"tw-justify-center tw-self-stretch tw-text-sm tw-font-normal tw-leading-tight tw-text-muted-foreground",children:a})]}),n.jsx(G,{variant:"secondary",size:"icon",className:"size-8",onClick:()=>l(),children:n.jsx(_.Copy,{})})]}),n.jsx("div",{className:"tw-prose tw-w-full",children:n.jsx("pre",{className:"tw-text-xs",children:t})})]})}const ad=Object.freeze([...ca,"%webView_error_dump_copied_message%"]);function id({errorDetails:t,handleCopyNotify:e,localizedStrings:r,children:o,className:s,id:a}){const[l,c]=i.useState(!1),w=()=>{c(!0),e&&e()},d=u=>{u||c(!1)};return n.jsxs(we,{onOpenChange:d,children:[n.jsx(je,{asChild:!0,children:o}),n.jsxs(re,{id:a,className:f("tw-min-w-80 tw-max-w-96",s),children:[l&&r["%webView_error_dump_copied_message%"]&&n.jsx(xt,{children:r["%webView_error_dump_copied_message%"]}),n.jsx(da,{errorDetails:t,handleCopyNotify:w,localizedStrings:r})]})]})}var wa=(t=>(t[t.Check=0]="Check",t[t.Radio=1]="Radio",t))(wa||{});function ld({id:t,label:e,groups:r}){const[o,s]=i.useState(Object.fromEntries(r.map((d,u)=>d.itemType===0?[u,[]]:void 0).filter(d=>!!d))),[a,l]=i.useState({}),c=(d,u)=>{const m=!o[d][u];s(p=>(p[d][u]=m,{...p}));const h=r[d].items[u];h.onUpdate(h.id,m)},w=(d,u)=>{l(h=>(h[d]=u,{...h}));const m=r[d].items.find(h=>h.id===u);m?m.onUpdate(u):console.error(`Could not find dropdown radio item with id '${u}'!`)};return n.jsx("div",{id:t,children:n.jsxs(ie,{children:[n.jsx(ve,{asChild:!0,children:n.jsxs(G,{variant:"default",children:[n.jsx(_.Filter,{size:16,className:"tw-mr-2 tw-h-4 tw-w-4"}),e,n.jsx(_.ChevronDown,{size:16,className:"tw-ml-2 tw-h-4 tw-w-4"})]})}),n.jsx(ee,{children:r.map((d,u)=>n.jsxs("div",{children:[n.jsx(Ce,{children:d.label}),n.jsx(to,{children:d.itemType===0?n.jsx(n.Fragment,{children:d.items.map((m,h)=>n.jsx("div",{children:n.jsx(Qt,{checked:o[u][h],onCheckedChange:()=>c(u,h),children:m.label})},m.id))}):n.jsx(Qs,{value:a[u],onValueChange:m=>w(u,m),children:d.items.map(m=>n.jsx("div",{children:n.jsx(ro,{value:m.id,children:m.label})},m.id))})}),n.jsx(ye,{})]},d.label))})]})})}function cd({id:t,category:e,downloads:r,languages:o,moreInfoUrl:s,handleMoreInfoLinkClick:a,supportUrl:l,handleSupportLinkClick:c}){const w=new I.NumberFormat("en",{notation:"compact",compactDisplay:"short"}).format(Object.values(r).reduce((u,m)=>u+m,0)),d=()=>{window.scrollTo(0,document.body.scrollHeight)};return n.jsxs("div",{id:t,className:"pr-twp tw-flex tw-items-center tw-justify-center tw-gap-4 tw-divide-x tw-border-b tw-border-t tw-py-2 tw-text-center",children:[e&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1",children:[n.jsx("div",{className:"tw-flex",children:n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:e})}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"CATEGORY"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsxs("div",{className:"tw-flex tw-gap-1",children:[n.jsx(_.User,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:w})]}),n.jsx("span",{className:"tw-text-xs tw-text-foreground",children:"USERS"})]}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-items-center tw-gap-1 tw-ps-4",children:[n.jsx("div",{className:"tw-flex tw-gap-2",children:o.slice(0,3).map(u=>n.jsx("span",{className:"tw-text-xs tw-font-semibold tw-text-foreground",children:u.toUpperCase()},u))}),o.length>3&&n.jsxs("button",{type:"button",onClick:()=>d(),className:"tw-text-xs tw-text-foreground tw-underline",children:["+",o.length-3," more languages"]})]}),(s||l)&&n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-1 tw-ps-4",children:[s&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs(G,{onClick:()=>a(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Website",n.jsx(_.Link,{className:"tw-h-4 tw-w-4"})]})}),l&&n.jsx("div",{className:"tw-flex tw-gap-1",children:n.jsxs(G,{onClick:()=>c(),variant:"link",className:"tw-flex tw-h-auto tw-gap-1 tw-py-0 tw-text-xs tw-font-semibold tw-text-foreground",children:["Support",n.jsx(_.CircleHelp,{className:"tw-h-4 tw-w-4"})]})})]})]})}function dd({id:t,versionHistory:e}){const[r,o]=i.useState(!1),s=new Date;function a(c){const w=new Date(c),d=new Date(s.getTime()-w.getTime()),u=d.getUTCFullYear()-1970,m=d.getUTCMonth(),h=d.getUTCDate()-1;let p="";return u>0?p=`${u.toString()} year${u===1?"":"s"} ago`:m>0?p=`${m.toString()} month${m===1?"":"s"} ago`:h===0?p="today":p=`${h.toString()} day${h===1?"":"s"} ago`,p}const l=Object.entries(e).sort((c,w)=>w[0].localeCompare(c[0]));return n.jsxs("div",{className:"pr-twp",id:t,children:[n.jsx("h3",{className:"tw-text-md tw-font-semibold",children:"What`s New"}),n.jsx("ul",{className:"tw-list-disc tw-pl-5 tw-pr-4 tw-text-xs tw-text-foreground",children:(r?l:l.slice(0,5)).map(c=>n.jsxs("div",{className:"tw-mt-3 tw-flex tw-justify-between",children:[n.jsx("div",{className:"tw-text-foreground",children:n.jsx("li",{className:"tw-prose tw-text-xs",children:n.jsx("span",{children:c[1].description})})}),n.jsxs("div",{className:"tw-justify-end tw-text-right",children:[n.jsxs("div",{children:["Version ",c[0]]}),n.jsx("div",{children:a(c[1].date)})]})]},c[0]))}),l.length>5&&n.jsx("button",{type:"button",onClick:()=>o(!r),className:"tw-text-xs tw-text-foreground tw-underline",children:r?"Show Less Version History":"Show All Version History"})]})}function wd({id:t,publisherDisplayName:e,fileSize:r,locales:o,versionHistory:s,currentVersion:a}){const l=i.useMemo(()=>I.formatBytes(r),[r]),w=(d=>{const u=new Intl.DisplayNames(I.getCurrentLocale(),{type:"language"});return d.map(m=>u.of(m))})(o);return n.jsx("div",{id:t,className:"pr-twp tw-border-t tw-py-2",children:n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-divide-y",children:[Object.entries(s).length>0&&n.jsx(dd,{versionHistory:s}),n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-py-2",children:[n.jsx("h2",{className:"tw-text-md tw-font-semibold",children:"Information"}),n.jsxs("div",{className:"tw-flex tw-items-start tw-justify-between tw-text-xs tw-text-foreground",children:[n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Publisher"}),n.jsx("span",{className:"tw-font-semibold",children:e}),n.jsx("span",{children:"Size"}),n.jsx("span",{className:"tw-font-semibold",children:l})]}),n.jsx("div",{className:"tw-flex tw-w-3/4 tw-items-center tw-justify-between tw-text-xs tw-text-foreground",children:n.jsxs("p",{className:"tw-flex tw-flex-col tw-justify-start tw-gap-1",children:[n.jsx("span",{children:"Version"}),n.jsx("span",{className:"tw-font-semibold",children:a}),n.jsx("span",{children:"Languages"}),n.jsx("span",{className:"tw-font-semibold",children:w.join(", ")})]})})]})]})]})})}function ua({entries:t,selected:e,onChange:r,placeholder:o,hasToggleAllFeature:s=!1,selectAllText:a="Select All",clearAllText:l="Clear All",commandEmptyMessage:c="No entries found",customSelectedText:w,isOpen:d=void 0,onOpenChange:u=void 0,isDisabled:m=!1,sortSelected:h=!1,icon:p=void 0,className:g=void 0,variant:y="ghost",id:v}){const[j,R]=i.useState(!1),S=i.useCallback(M=>{var B;const $=(B=t.find(P=>P.label===M))==null?void 0:B.value;$&&r(e.includes($)?e.filter(P=>P!==$):[...e,$])},[t,e,r]),C=()=>w||o,N=i.useMemo(()=>{if(!h)return t;const M=t.filter(B=>B.starred).sort((B,P)=>B.label.localeCompare(P.label)),$=t.filter(B=>!B.starred).sort((B,P)=>{const L=e.includes(B.value),q=e.includes(P.value);return L&&!q?-1:!L&&q?1:B.label.localeCompare(P.label)});return[...M,...$]},[t,e,h]),D=()=>{r(t.map(M=>M.value))},T=()=>{r([])},E=d??j,b=u??R;return n.jsx("div",{id:v,className:g,children:n.jsxs(we,{open:E,onOpenChange:b,children:[n.jsx(je,{asChild:!0,children:n.jsxs(G,{variant:y,role:"combobox","aria-expanded":E,className:"tw-group tw-w-full tw-justify-between",disabled:m,children:[n.jsxs("div",{className:"tw-flex tw-min-w-0 tw-flex-1 tw-items-center tw-gap-2",children:[p&&n.jsx("div",{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50",children:n.jsx("span",{className:"tw-flex tw-h-full tw-w-full tw-items-center tw-justify-center",children:p})}),n.jsx("span",{className:f("tw-min-w-0 tw-overflow-hidden tw-text-ellipsis tw-whitespace-nowrap tw-text-start tw-font-normal"),children:C()})]}),n.jsx(_.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(re,{align:"start",className:"tw-w-full tw-p-0",children:n.jsxs(ce,{children:[n.jsx($e,{placeholder:`Search ${o.toLowerCase()}...`}),s&&n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx(G,{variant:"ghost",size:"sm",onClick:D,children:a}),n.jsx(G,{variant:"ghost",size:"sm",onClick:T,children:l})]}),n.jsxs(de,{children:[n.jsx(Ye,{children:c}),n.jsx(te,{children:N.map(M=>n.jsxs(ne,{value:M.label,onSelect:S,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx("div",{className:"w-4",children:n.jsx(_.Check,{className:f("tw-h-4 tw-w-4",e.includes(M.value)?"tw-opacity-100":"tw-opacity-0")})}),M.starred&&n.jsx(_.Star,{className:"tw-h-4 tw-w-4"}),n.jsx("div",{className:"tw-flex-grow",children:M.label}),M.secondaryLabel&&n.jsx("div",{className:"tw-text-end tw-text-muted-foreground",children:M.secondaryLabel})]},M.label))})]})]})})]})})}function ud({entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:w,className:d,badgesPlaceholder:u,id:m}){return n.jsxs("div",{id:m,className:"tw-flex tw-items-center tw-gap-2",children:[n.jsx(ua,{entries:t,selected:e,onChange:r,placeholder:o,commandEmptyMessage:s,customSelectedText:a,isDisabled:l,sortSelected:c,icon:w,className:d}),e.length>0?n.jsx("div",{className:"tw-flex tw-flex-wrap tw-items-center tw-gap-2",children:e.map(h=>{var p;return n.jsxs(ae,{variant:"muted",className:"tw-flex tw-items-center tw-gap-1",children:[n.jsx(G,{variant:"ghost",size:"icon",className:"tw-h-4 tw-w-4 tw-p-0 hover:tw-bg-transparent",onClick:()=>r(e.filter(g=>g!==h)),children:n.jsx(_.X,{className:"tw-h-3 tw-w-3"})}),(p=t.find(g=>g.value===h))==null?void 0:p.label]},h)})}):n.jsx(xt,{children:u})]})}const pa=Object.freeze(["%undoButton_tooltip%","%redoButton_tooltip%"]),Ho=(t,e)=>t[e]??e;function ma({onUndoClick:t,onRedoClick:e,canUndo:r=!0,canRedo:o=!0,localizedStrings:s={},showKeyboardShortcuts:a=!0,className:l="tw-h-6 tw-w-6",variant:c="ghost"}){const w=i.useMemo(()=>/Macintosh/i.test(navigator.userAgent),[]);return n.jsxs(n.Fragment,{children:[n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(G,{"aria-label":"Undo",className:l,size:"icon",onClick:t,disabled:!r,variant:c,children:n.jsx(_.Undo,{})})}),n.jsx(St,{children:n.jsxs("p",{children:[Ho(s,"%undoButton_tooltip%"),a&&` (${w?"⌘Z":"Ctrl+Z"})`]})})]})}),e&&n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(G,{"aria-label":"Redo",className:l,size:"icon",onClick:e,disabled:!o,variant:c,children:n.jsx(_.Redo,{})})}),n.jsx(St,{children:n.jsxs("p",{children:[Ho(s,"%redoButton_tooltip%"),a&&` (${w?"⌘⇧Z":"Ctrl+Y"})`]})})]})})]})}function fa({children:t,editorRef:e}){const r=i.useRef(null);return i.useEffect(()=>{var l;const o=/Macintosh/i.test(navigator.userAgent),s=((l=r.current)==null?void 0:l.querySelector(".editor-input"))??void 0,a=c=>{var d,u,m,h;if(!s||document.activeElement!==s)return;const w=c.key.toLowerCase();if(o){if(!c.metaKey)return;!c.shiftKey&&w==="z"?(c.preventDefault(),(d=e.current)==null||d.undo()):c.shiftKey&&w==="z"&&(c.preventDefault(),(u=e.current)==null||u.redo())}else{if(!c.ctrlKey)return;!c.shiftKey&&w==="z"?(c.preventDefault(),(m=e.current)==null||m.undo()):(w==="y"||c.shiftKey&&w==="z")&&(c.preventDefault(),(h=e.current)==null||h.redo())}};return document.addEventListener("keydown",a),()=>document.removeEventListener("keydown",a)},[e]),n.jsx("div",{ref:r,children:t})}const Xe=i.forwardRef(({className:t,type:e,...r},o)=>n.jsx("input",{type:e,className:f("pr-twp tw-flex tw-h-10 tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-sm tw-ring-offset-background file:tw-border-0 file:tw-bg-transparent file:tw-text-sm file:tw-font-medium file:tw-text-foreground placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50",t),ref:o,...r}));Xe.displayName="Input";const pd=(t,e,r)=>t==="generated"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"+"})," ",e["%footnoteEditor_callerDropdown_item_generated%"]]}):t==="hidden"?n.jsxs(n.Fragment,{children:[n.jsx("p",{children:"-"})," ",e["%footnoteEditor_callerDropdown_item_hidden%"]]}):n.jsxs(n.Fragment,{children:[n.jsx("p",{children:r})," ",e["%footnoteEditor_callerDropdown_item_custom%"]]});function md({callerType:t,updateCallerType:e,customCaller:r,updateCustomCaller:o,localizedStrings:s}){const a=i.useRef(null),l=i.useRef(null),c=i.useRef(!1),[w,d]=i.useState(t),[u,m]=i.useState(r),[h,p]=i.useState(!1);i.useEffect(()=>{d(t)},[t]),i.useEffect(()=>{u!==r&&m(r)},[r]);const g=v=>{c.current=!1,p(v),v||(w!=="custom"||u?(e(w),o(u)):(d(t),m(r)))},y=v=>{var j,R,S,C;v.stopPropagation(),document.activeElement===l.current&&v.key==="ArrowDown"||v.key==="ArrowRight"?((j=a.current)==null||j.focus(),c.current=!0):document.activeElement===a.current&&v.key==="ArrowUp"?((R=l.current)==null||R.focus(),c.current=!1):document.activeElement===a.current&&v.key==="ArrowLeft"&&((S=a.current)==null?void 0:S.selectionStart)===0&&((C=l.current)==null||C.focus(),c.current=!1),w==="custom"&&v.key==="Enter"&&(document.activeElement===l.current||document.activeElement===a.current)&&g(!1)};return n.jsxs(ie,{open:h,onOpenChange:g,children:[n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(ve,{asChild:!0,children:n.jsx(G,{variant:"outline",className:"tw-h-6",children:pd(t,s,r)})})}),n.jsx(St,{children:s["%footnoteEditor_callerDropdown_tooltip%"]})]})}),n.jsxs(ee,{style:{zIndex:Ar},onClick:()=>{c.current&&(c.current=!1)},onKeyDown:y,onMouseMove:()=>{var v;c.current&&((v=a.current)==null||v.focus())},children:[n.jsx(Ce,{children:s["%footnoteEditor_callerDropdown_label%"]}),n.jsx(ye,{}),n.jsx(Qt,{checked:w==="generated",onCheckedChange:()=>d("generated"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_generated%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:Jt.GENERATOR_NOTE_CALLER})]})}),n.jsx(Qt,{checked:w==="hidden",onCheckedChange:()=>d("hidden"),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_hidden%"]}),n.jsx("span",{className:"tw-w-10 tw-text-center",children:Jt.HIDDEN_NOTE_CALLER})]})}),n.jsx(Qt,{ref:l,checked:w==="custom",onCheckedChange:()=>d("custom"),onClick:v=>{var j;v.stopPropagation(),c.current=!0,(j=a.current)==null||j.focus()},onSelect:v=>v.preventDefault(),children:n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-between",children:[n.jsx("span",{children:s["%footnoteEditor_callerDropdown_item_custom%"]}),n.jsx(Xe,{tabIndex:0,onMouseDown:v=>{v.stopPropagation(),d("custom"),c.current=!0},ref:a,className:"tw-h-auto tw-w-10 tw-p-0 tw-text-center",value:u,onKeyDown:v=>{v.key==="Enter"||v.key==="ArrowUp"||v.key==="ArrowDown"||v.key==="ArrowLeft"||v.key==="ArrowRight"||v.stopPropagation()},maxLength:1,onChange:v=>m(v.target.value)})]})})]})]})}const fd=(t,e)=>t==="f"?n.jsxs(n.Fragment,{children:[n.jsx(_.FunctionSquare,{})," ",e["%footnoteEditor_noteType_footnote_label%"]]}):t==="fe"?n.jsxs(n.Fragment,{children:[n.jsx(_.SquareSigma,{})," ",e["%footnoteEditor_noteType_endNote_label%"]]}):n.jsxs(n.Fragment,{children:[n.jsx(_.SquareX,{})," ",e["%footnoteEditor_noteType_crossReference_label%"]]}),hd=(t,e)=>{if(t==="x")return e["%footnoteEditor_noteType_crossReference_label%"];let r=e["%footnoteEditor_noteType_endNote_label%"];return t==="f"&&(r=e["%footnoteEditor_noteType_footnote_label%"]),I.formatReplacementString(e["%footnoteEditor_noteType_tooltip%"]??"",{noteType:r})};function gd({noteType:t,handleNoteTypeChange:e,localizedStrings:r,isTypeSwitchable:o}){return n.jsxs(ie,{children:[n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Wo.TooltipTrigger,{asChild:!0,children:n.jsx(ve,{asChild:!0,children:n.jsx(G,{variant:"outline",className:"tw-h-6",children:fd(t,r)})})}),n.jsx(St,{children:n.jsx("p",{children:hd(t,r)})})]})}),n.jsxs(ee,{style:{zIndex:Ar},children:[n.jsx(Ce,{children:r["%footnoteEditor_noteTypeDropdown_label%"]}),n.jsx(ye,{}),n.jsxs(Qt,{disabled:t!=="x"&&!o,checked:t==="x",onCheckedChange:()=>e("x"),className:"tw-gap-2",children:[n.jsx(_.SquareX,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_crossReference_label%"]})]}),n.jsxs(Qt,{disabled:t==="x"&&!o,checked:t==="f",onCheckedChange:()=>e("f"),className:"tw-gap-2",children:[n.jsx(_.FunctionSquare,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_footnote_label%"]})]}),n.jsxs(Qt,{disabled:t==="x"&&!o,checked:t==="fe",onCheckedChange:()=>e("fe"),className:"tw-gap-2",children:[n.jsx(_.SquareSigma,{}),n.jsx("span",{children:r["%footnoteEditor_noteType_endNote_label%"]})]})]})]})}const ha=Object.freeze(["%markerMenu_deprecated_label%","%markerMenu_disallowed_label%","%markerMenu_noResults%","%markerMenu_searchPlaceholder%"]);function xd({icon:t,className:e}){const r=t??_.Ban;return n.jsx(r,{className:e,size:16})}function Uo({item:t,localizedStrings:e}){return n.jsxs(ne,{className:"tw-flex tw-gap-2 hover:tw-bg-accent",disabled:t.isDisallowed||t.isDeprecated,onSelect:t.action,children:[n.jsx("div",{className:"tw-w-8 tw-min-w-8",children:t.marker?n.jsx("span",{className:"tw-text-xs",children:t.marker}):n.jsx("div",{children:n.jsx(xd,{icon:t.icon})})}),n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm",children:t.title}),t.subtitle&&n.jsx("p",{className:"tw-text-xs tw-text-muted-foreground",children:t.subtitle})]}),(t.isDisallowed||t.isDeprecated)&&n.jsx(ls,{className:"tw-font-sans",children:t.isDisallowed?e["%markerMenu_disallowed_label%"]:e["%markerMenu_deprecated_label%"]})]})}function ga({localizedStrings:t,markerMenuItems:e,searchRef:r}){const[o,s]=i.useState(""),[a,l]=i.useMemo(()=>{const c=o.trim().toLowerCase();if(!c)return[e,[]];const w=e.filter(u=>{var m;return(m=u.marker)==null?void 0:m.toLowerCase().includes(c)}),d=e.filter(u=>u.title.toLowerCase().includes(c)&&!w.includes(u));return[w,d]},[o,e]);return n.jsxs(ce,{className:"tw-p-1",shouldFilter:!1,loop:!0,children:[n.jsx($e,{className:"marker-menu-search",ref:r,value:o,onValueChange:c=>s(c),placeholder:t["%markerMenu_searchPlaceholder%"]}),n.jsxs(de,{children:[n.jsx(Ye,{children:t["%markerMenu_noResults%"]}),n.jsx(te,{children:a.map(c=>{var w;return n.jsx(Uo,{item:c,localizedStrings:t},`item-${c.marker??((w=c.icon)==null?void 0:w.displayName)}-${c.title.replaceAll(" ","")}`)})}),l.length>0&&n.jsxs(n.Fragment,{children:[a.length>0&&n.jsx(ir,{alwaysRender:!0}),n.jsx(te,{children:l.map(c=>{var w;return n.jsx(Uo,{item:c,localizedStrings:t},`item-${c.marker??((w=c.icon)==null?void 0:w.displayName)}-${c.title.replaceAll(" ","")}`)})})]})]})]})}function bd(t,e,r,o){if(!o||o==="p")return[];const s=I.usfmMarkers[o];if(!(s!=null&&s.children))return[];const a=[];return Object.entries(s.children).forEach(([,l])=>{a.push(...l.map(c=>({marker:c,title:r[I.usfmMarkers[c].description]??I.usfmMarkers[c].description,action:()=>{var w;(w=t.current)==null||w.insertMarker(c),e()}})))}),a.sort((l,c)=>(l.marker??l.title).localeCompare(c.marker??c.title))}function vd(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="ft"&&(e.style="xt"),e.style==="fr"&&(e.style="xo"),e.style==="fq"&&(e.style="xq"))}function yd(t){var r;const e=(r=t.attributes)==null?void 0:r.char;e.style&&(e.style==="xt"&&(e.style="ft"),e.style==="xo"&&(e.style="fr"),e.style==="xq"&&(e.style="fq"))}const jd={type:"USJ",version:"3.1",content:[{type:"para"}]};function Nd({classNameForEditor:t,noteOps:e,onChange:r,onClose:o,scrRef:s,noteKey:a,editorOptions:l,defaultMarkerMenuTrigger:c,localizedStrings:w,parentEditorRef:d}){const u=i.useRef(null),m=i.useRef(null),h=i.useRef(null),p=i.useRef(null);i.useLayoutEffect(()=>{if(!p.current)return;const{width:F}=p.current.getBoundingClientRect();F>0&&(p.current.style.width=`${F}px`)},[]);const[g,y]=i.useState("generated"),[v,j]=i.useState("*"),[R,S]=i.useState("f"),[C,N]=i.useState(!1),[D,T]=i.useState(!0),[E,b]=i.useState(!1),M=i.useRef(!1),$=i.useRef(""),[B,P]=i.useState(!1),[L,q]=i.useState(),[H,W]=i.useState(),[Nt,At]=i.useState(),[Ot,nt]=i.useState(),wt=i.useRef(null),z=i.useMemo(()=>({...l,markerMenuTrigger:c,hasExternalUI:!0,view:{...l.view??Jt.getDefaultViewOptions(),noteMode:"expanded"}}),[l,c]),J=i.useMemo(()=>bd(u,()=>P(!1),w,Ot),[w,Ot]);i.useEffect(()=>{var F;B||(F=u.current)==null||F.focus()},[R,B]),i.useEffect(()=>{var Z,ot;let F;M.current=!1,T(!0);const U=e==null?void 0:e.at(0);if(U&&Jt.isInsertEmbedOpOfType("note",U)){const mt=(Z=U.insert.note)==null?void 0:Z.caller;let ft="custom";mt===Jt.GENERATOR_NOTE_CALLER?ft="generated":mt===Jt.HIDDEN_NOTE_CALLER?ft="hidden":mt&&j(mt),y(ft),S(((ot=U.insert.note)==null?void 0:ot.style)??"f"),F=setTimeout(()=>{var kt;(kt=u.current)==null||kt.applyUpdate([U])},0)}return()=>{F&&clearTimeout(F)}},[e,a]);const rt=i.useCallback((F,U,Z=!1)=>{var mt,ft,kt;const ot=(ft=(mt=u.current)==null?void 0:mt.getNoteOps(0))==null?void 0:ft.at(0);if(ot&&Jt.isInsertEmbedOpOfType("note",ot)){if(ot.insert.note){let ut;F==="custom"?ut=U:F==="generated"?ut=Jt.GENERATOR_NOTE_CALLER:ut=Jt.HIDDEN_NOTE_CALLER,ot.insert.note.caller=ut}r==null||r([ot]),Z&&d&&a&&((kt=d.current)==null||kt.replaceEmbedUpdate(a,[ot]))}},[a,r,d]),Q=i.useCallback(()=>{rt(g,v,!0),o()},[g,v,o,rt]),et=i.useRef(Q);i.useLayoutEffect(()=>{et.current=Q});const $t=i.useRef({book:s.book,chapterNum:s.chapterNum});i.useLayoutEffect(()=>{($t.current.book!==s.book||$t.current.chapterNum!==s.chapterNum)&&($t.current={book:s.book,chapterNum:s.chapterNum},et.current())},[s.book,s.chapterNum]);const Et=()=>{var U;const F=(U=m.current)==null?void 0:U.getElementsByClassName("editor-input")[0];F!=null&&F.textContent&&navigator.clipboard.writeText(F.textContent)},Lt=i.useCallback(F=>{y(F),rt(F,v)},[v,rt]),pe=i.useCallback(F=>{j(F),rt(g,F)},[g,rt]),A=F=>{var Z,ot,mt,ft,kt;S(F);const U=(ot=(Z=u.current)==null?void 0:Z.getNoteOps(0))==null?void 0:ot.at(0);if(U&&Jt.isInsertEmbedOpOfType("note",U)){U.insert.note&&(U.insert.note.style=F);const ut=(ft=(mt=U.insert.note)==null?void 0:mt.contents)==null?void 0:ft.ops;R!=="x"&&F==="x"?ut==null||ut.forEach(vt=>vd(vt)):R==="x"&&F!=="x"&&(ut==null||ut.forEach(vt=>yd(vt))),(kt=u.current)==null||kt.applyUpdate([U,{delete:1}])}},Ht=F=>{nt(F.contextMarker),b(F.canRedo)},me=i.useCallback(F=>{var Z,ot,mt,ft,kt;const U=(ot=(Z=u.current)==null?void 0:Z.getNoteOps(0))==null?void 0:ot.at(0);if(U&&Jt.isInsertEmbedOpOfType("note",U)){F.content.length>1&&setTimeout(()=>{var Pt;(Pt=u.current)==null||Pt.applyUpdate([{retain:2},{delete:1}])},0);const ut=(mt=U.insert.note)==null?void 0:mt.style,vt=(kt=(ft=U.insert.note)==null?void 0:ft.contents)==null?void 0:kt.ops;if(ut||N(!1),N(ut==="x"?!!(vt!=null&&vt.every(Pt=>{var ht,it;if(!((ht=Pt.attributes)!=null&&ht.char))return!0;const O=((it=Pt.attributes)==null?void 0:it.char).style;return O==="xt"||O==="xo"||O==="xq"})):!!(vt!=null&&vt.every(Pt=>{var ht,it;if(!((ht=Pt.attributes)!=null&&ht.char))return!0;const O=((it=Pt.attributes)==null?void 0:it.char).style;return O==="ft"||O==="fr"||O==="fq"}))),!M.current){M.current=!0,$.current=JSON.stringify(U),T(!0);return}T(JSON.stringify(U)===$.current),rt(g,v)}else N(!1),T(!0)},[g,v,rt]),Rt=i.useCallback(()=>{const F=window.getSelection();if(h.current&&J.length&&F&&F.rangeCount>0){const U=F.getRangeAt(0).getBoundingClientRect(),Z=h.current.getBoundingClientRect();q(U.left-Z.left),W(U.top-Z.top),At(U.height),P(!0)}},[J,h]);return i.useEffect(()=>{const F=()=>{B&&P(!1)};return window.addEventListener("click",F),()=>{window.removeEventListener("click",F)}},[B]),i.useEffect(()=>{var F;B&&((F=wt.current)==null||F.focus())},[B]),i.useEffect(()=>{var Z;const F=((Z=m.current)==null?void 0:Z.querySelector(".editor-input"))??void 0,U=ot=>{!B&&F&&document.activeElement===F&&ot.key===c?(ot.preventDefault(),Rt()):B&&ot.key==="Escape"&&(ot.preventDefault(),P(!1))};return document.addEventListener("keydown",U),()=>{document.removeEventListener("keydown",U)}},[B,Rt,c]),n.jsxs(n.Fragment,{children:[n.jsxs("div",{ref:p,className:"footnote-editor tw-grid tw-gap-[12px]",children:[n.jsxs("div",{className:"tw-flex",children:[n.jsxs("div",{className:"tw-flex tw-gap-4",children:[n.jsx(gd,{isTypeSwitchable:C,noteType:R,handleNoteTypeChange:A,localizedStrings:w}),n.jsx(md,{callerType:g,updateCallerType:Lt,customCaller:v,updateCustomCaller:pe,localizedStrings:w})]}),n.jsxs("div",{className:"tw-flex tw-w-full tw-justify-end tw-gap-4",children:[n.jsx(ma,{onUndoClick:()=>{var F;return(F=u.current)==null?void 0:F.undo()},onRedoClick:()=>{var F;return(F=u.current)==null?void 0:F.redo()},canUndo:!D,canRedo:E,localizedStrings:w}),n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(G,{onClick:Q,className:"tw-h-6 tw-w-6",size:"icon",variant:"ghost",children:n.jsx(_.Check,{})})}),n.jsx(St,{children:n.jsx("p",{children:w["%footnoteEditor_saveButton_tooltip%"]})})]})}),n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(G,{onClick:o,className:"tw-h-6 tw-w-6",size:"icon",variant:"ghost",children:n.jsx(_.X,{})})}),n.jsx(St,{children:n.jsx("p",{children:w["%footnoteEditor_cancelButton_tooltip%"]})})]})})]})]}),n.jsxs("div",{ref:m,className:"tw-relative tw-rounded-[6px] tw-border-2 tw-border-ring",children:[n.jsx("div",{className:t,children:n.jsx(fa,{editorRef:u,children:n.jsx(Jt.Editorial,{options:z,onStateChange:Ht,onUsjChange:me,defaultUsj:jd,onScrRefChange:()=>{},scrRef:s,ref:u})})}),n.jsx("div",{className:"tw-absolute tw-bottom-0 tw-right-0",children:n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsx(G,{onClick:Et,className:"tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(_.Copy,{})})}),n.jsx(St,{children:n.jsx("p",{children:w["%footnoteEditor_copyButton_tooltip%"]})})]})})})]})]}),n.jsx("div",{className:"tw-absolute",ref:h,style:{top:0,left:0,height:0,width:0}}),n.jsxs(we,{open:B,children:[n.jsx(us,{className:"tw-absolute",style:{top:H,left:L,height:Nt,width:0,pointerEvents:"none"}}),n.jsx(re,{className:"tw-w-[500px] tw-p-0",onClick:F=>{F.preventDefault(),F.stopPropagation()},children:n.jsx(ga,{markerMenuItems:J,localizedStrings:w,searchRef:wt})})]})]})}const kd=Object.freeze([...ha,...Object.entries(I.usfmMarkers).map(([,t])=>t.description).filter(t=>!!t),"%footnoteEditor_callerDropdown_item_custom%","%footnoteEditor_callerDropdown_item_generated%","%footnoteEditor_callerDropdown_item_hidden%","%footnoteEditor_callerDropdown_label%","%footnoteEditor_callerDropdown_tooltip%","%footnoteEditor_cancelButton_tooltip%","%footnoteEditor_copyButton_tooltip%","%footnoteEditor_noteType_crossReference_label%","%footnoteEditor_noteType_endNote_label%","%footnoteEditor_noteType_footnote_label%","%footnoteEditor_noteType_tooltip%","%footnoteEditor_noteTypeDropdown_label%","%footnoteEditor_saveButton_tooltip%",...pa]);function xa(t,e){if(!e||e.length===0)return t??"empty";const r=e.find(s=>typeof s=="string");if(r)return`key-${t??"unknown"}-${r.slice(0,10)}`;const o=typeof e[0]=="string"?"impossible":e[0].marker??"unknown";return`key-${t??"unknown"}-${o}`}function _d(t,e,r=!0,o=void 0){if(!e||e.length===0)return;const s=[],a=[];let l=[];return e.forEach(c=>{typeof c!="string"&&c.marker==="fp"?(l.length>0&&a.push(l),l=[c]):l.push(c)}),l.length>0&&a.push(l),a.map((c,w)=>{const d=w===a.length-1;return n.jsxs("p",{children:[ao(t,c,r,!0,s),d&&o]},xa(t,c))})}function ao(t,e,r=!0,o=!0,s=[]){if(!(!e||e.length===0))return e.map(a=>{if(typeof a=="string"){const l=`${t}-text-${a.slice(0,10)}`;if(o){const c=f(`usfm_${t}`);return n.jsx("span",{className:c,children:a},l)}return n.jsxs("span",{className:"tw-inline-flex tw-items-center tw-gap-1 tw-underline tw-decoration-destructive",children:[n.jsx(_.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"}),n.jsx("span",{children:a}),n.jsx(_.AlertCircle,{className:"tw-h-4 tw-w-4 tw-fill-destructive"})]},l)}return Cd(a,xa(`${t}\\${a.marker}`,[a]),r,[...s,t??"unknown"])})}function Cd(t,e,r,o=[]){const{marker:s}=t;return n.jsxs("span",{children:[s?r&&n.jsx("span",{className:"marker",children:`\\${s} `}):n.jsx(_.AlertCircle,{className:"tw-text-error tw-mr-1 tw-inline-block tw-h-4 tw-w-4","aria-label":"Missing marker"}),ao(s,t.content,r,!0,[...o,s??"unknown"])]},e)}function ba({footnote:t,layout:e="horizontal",formatCaller:r,showMarkers:o=!0}){const s=r?r(t.caller):t.caller,a=s!==t.caller;let l,c=t.content;Array.isArray(t.content)&&t.content.length>0&&typeof t.content[0]!="string"&&(t.content[0].marker==="fr"||t.content[0].marker==="xo")&&([l,...c]=t.content);const w=o?n.jsx("span",{className:"marker",children:`\\${t.marker} `}):void 0,d=o?n.jsx("span",{className:"marker",children:` \\${t.marker}*`}):void 0,u=s&&n.jsxs("span",{className:f("note-caller tw-inline-block",{formatted:a}),children:[s," "]}),m=l&&n.jsxs(n.Fragment,{children:[ao(t.marker,[l],o,!1)," "]}),h=e==="horizontal"?"horizontal":"vertical",p=o?"marker-visible":"",g=e==="horizontal"?"tw-col-span-1":"tw-col-span-2 tw-col-start-1 tw-row-start-2",y=f(h,p);return n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:f("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",y),children:[w,u]}),n.jsx("div",{className:f("textual-note-header tw-col-span-1 tw-w-fit tw-text-nowrap",y),children:m}),n.jsx("div",{className:f("textual-note-body tw-flex tw-flex-col tw-gap-1",g,y),children:c&&c.length>0&&n.jsx(n.Fragment,{children:_d(t.marker,c,o,d)})})]})}function Sd({className:t,classNameForItems:e,footnotes:r,layout:o="horizontal",listId:s,selectedFootnote:a,showMarkers:l=!0,suppressFormatting:c=!1,formatCaller:w,onFootnoteSelected:d}){const u=w??I.getFormatCallerFunction(r,void 0),m=(R,S)=>{d==null||d(R,S,s)},h=a?r.findIndex(R=>R===a):-1,[p,g]=i.useState(h),y=(R,S,C)=>{if(r.length)switch(R.key){case"Enter":case" ":R.preventDefault(),d==null||d(S,C,s);break}},v=R=>{if(r.length)switch(R.key){case"ArrowDown":R.preventDefault(),g(S=>Math.min(S+1,r.length-1));break;case"ArrowUp":R.preventDefault(),g(S=>Math.max(S-1,0));break}},j=i.useRef([]);return i.useEffect(()=>{var R;p>=0&&p{const C=R===a,N=`${s}-${S}`;return n.jsxs(n.Fragment,{children:[n.jsx("li",{ref:D=>{j.current[S]=D},role:"option","aria-selected":C,"data-marker":R.marker,"data-state":C?"selected":void 0,tabIndex:S===p?0:-1,className:f("tw-gap-x-3 tw-gap-y-1 tw-p-2 data-[state=selected]:tw-bg-muted",d&&"hover:tw-bg-muted/50","tw-w-full tw-rounded-sm tw-border-0 tw-bg-transparent tw-shadow-none","focus:tw-outline-none focus-visible:tw-outline-none","focus-visible:tw-ring-offset-0.5 focus-visible:tw-relative focus-visible:tw-z-10 focus-visible:tw-ring-2 focus-visible:tw-ring-ring","tw-grid tw-grid-flow-col tw-grid-cols-subgrid",o==="horizontal"?"tw-col-span-3":"tw-col-span-2 tw-row-span-2",e),onClick:()=>m(R,S),onKeyDown:D=>y(D,R,S),children:n.jsx(ba,{footnote:R,layout:o,formatCaller:()=>u(R.caller,S),showMarkers:l})},N),Sr&&e.push(t.substring(r,s.index)),e.push(n.jsx("strong",{children:s[1]},s.index)),r=o.lastIndex;return r0?e:[t]}function Rd({occurrenceData:t,setScriptureReference:e,localizedStrings:r,classNameForText:o}){const s=r["%webView_inventory_occurrences_table_header_reference%"],a=r["%webView_inventory_occurrences_table_header_occurrence%"],l=i.useMemo(()=>{const c=[],w=new Set;return t.forEach(d=>{const u=`${d.reference.book}:${d.reference.chapterNum}:${d.reference.verseNum}:${d.text}`;w.has(u)||(w.add(u),c.push(d))}),c},[t]);return n.jsxs(En,{stickyHeader:!0,children:[n.jsx(Rn,{stickyHeader:!0,children:n.jsxs(xe,{children:[n.jsx(on,{children:s}),n.jsx(on,{children:a})]})}),n.jsx(Tn,{children:l.length>0&&l.map(c=>n.jsxs(xe,{onClick:()=>{e(c.reference)},children:[n.jsx(Me,{children:I.formatScrRef(c.reference,"English")}),n.jsx(Me,{className:o,children:Ed(c.text)})]},`${c.reference.book} ${c.reference.chapterNum}:${c.reference.verseNum}-${c.text}`))})]})}const wr=i.forwardRef(({className:t,...e},r)=>n.jsx(Er.Root,{ref:r,className:f("tw-peer pr-twp tw-h-4 tw-w-4 tw-shrink-0 tw-rounded-sm tw-border tw-border-primary tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=checked]:tw-text-primary-foreground",t),...e,children:n.jsx(Er.Indicator,{className:f("tw-flex tw-items-center tw-justify-center tw-text-current"),children:n.jsx(_.Check,{className:"tw-h-4 tw-w-4"})})}));wr.displayName=Er.Root.displayName;const Td=t=>{if(t==="asc")return n.jsx(_.ArrowUpIcon,{className:"tw-h-4 tw-w-4"});if(t==="desc")return n.jsx(_.ArrowDownIcon,{className:"tw-h-4 tw-w-4"})},Dn=(t,e,r)=>n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsxs(Mt,{className:f("tw-flex tw-w-full tw-justify-start",r),variant:"ghost",onClick:()=>t.toggleSorting(void 0),children:[n.jsx("span",{className:"tw-w-6 tw-max-w-fit tw-flex-1 tw-overflow-hidden tw-text-ellipsis",children:e}),Td(t.getIsSorted())]}),n.jsx(St,{side:"bottom",children:e})]})}),Dd=t=>({accessorKey:"item",accessorFn:e=>e.items[0],header:({column:e})=>Dn(e,t)}),Id=(t,e)=>({accessorKey:`item${e}`,accessorFn:r=>r.items[e],header:({column:r})=>Dn(r,t)}),Md=t=>({accessorKey:"count",header:({column:e})=>Dn(e,t,"tw-justify-end"),cell:({row:e})=>n.jsx("div",{className:"tw-flex tw-justify-end tw-tabular-nums",children:e.getValue("count")})}),kr=(t,e,r,o,s,a)=>{let l=[...r];t.forEach(w=>{e==="approved"?l.includes(w)||l.push(w):l=l.filter(d=>d!==w)}),o(l);let c=[...s];t.forEach(w=>{e==="unapproved"?c.includes(w)||c.push(w):c=c.filter(d=>d!==w)}),a(c)},Od=(t,e,r,o,s)=>({accessorKey:"status",header:({column:a})=>Dn(a,t,"tw-justify-center"),cell:({row:a})=>{const l=a.getValue("status"),c=a.getValue("item");return n.jsxs(dr,{value:l,variant:"outline",type:"single",className:"tw-gap-0",children:[n.jsx(en,{onClick:w=>{w.stopPropagation(),kr([c],"approved",e,r,o,s)},value:"approved",className:"tw-rounded-e-none tw-border-e-0",children:n.jsx(_.CircleCheckIcon,{})}),n.jsx(en,{onClick:w=>{w.stopPropagation(),kr([c],"unapproved",e,r,o,s)},value:"unapproved",className:"tw-rounded-none",children:n.jsx(_.CircleXIcon,{})}),n.jsx(en,{onClick:w=>{w.stopPropagation(),kr([c],"unknown",e,r,o,s)},value:"unknown",className:"tw-rounded-s-none tw-border-s-0",children:n.jsx(_.CircleHelpIcon,{})})]})}}),Pd=t=>t.split(/(?:\r?\n|\r)|(?=(?:\\(?:v|c|id)))/g),Ad=t=>{const e=/^\\[vc]\s+(\d+)/,r=t.match(e);if(r)return+r[1]},$d=t=>{const e=t.match(/^\\id\s+([A-Za-z]+)/);return e?e[1]:""},va=(t,e,r)=>r.includes(t)?"unapproved":e.includes(t)?"approved":"unknown",Ld=Object.freeze(["%webView_inventory_all%","%webView_inventory_approved%","%webView_inventory_unapproved%","%webView_inventory_unknown%","%webView_inventory_scope_currentBook%","%webView_inventory_scope_chapter%","%webView_inventory_scope_verse%","%webView_inventory_filter_text%","%webView_inventory_show_additional_items%","%webView_inventory_occurrences_table_header_reference%","%webView_inventory_occurrences_table_header_occurrence%","%webView_inventory_no_results%"]),Vd=(t,e,r)=>{let o=t;return e!=="all"&&(o=o.filter(s=>e==="approved"&&s.status==="approved"||e==="unapproved"&&s.status==="unapproved"||e==="unknown"&&s.status==="unknown")),r!==""&&(o=o.filter(s=>s.items[0].includes(r))),o},Bd=(t,e,r)=>t.map(o=>{const s=I.isString(o.key)?o.key:o.key[0];return{items:I.isString(o.key)?[o.key]:o.key,count:o.count,status:o.status||va(s,e,r),occurrences:o.occurrences||[]}}),fe=(t,e)=>t[e]??e;function Fd({inventoryItems:t,setVerseRef:e,localizedStrings:r,additionalItemsLabels:o,approvedItems:s,unapprovedItems:a,scope:l,onScopeChange:c,columns:w,id:d,areInventoryItemsLoading:u=!1,classNameForVerseText:m,onItemSelected:h}){const p=fe(r,"%webView_inventory_all%"),g=fe(r,"%webView_inventory_approved%"),y=fe(r,"%webView_inventory_unapproved%"),v=fe(r,"%webView_inventory_unknown%"),j=fe(r,"%webView_inventory_scope_currentBook%"),R=fe(r,"%webView_inventory_scope_chapter%"),S=fe(r,"%webView_inventory_scope_verse%"),C=fe(r,"%webView_inventory_filter_text%"),N=fe(r,"%webView_inventory_show_additional_items%"),D=fe(r,"%webView_inventory_no_results%"),[T,E]=i.useState(!1),[b,M]=i.useState("all"),[$,B]=i.useState(""),[P,L]=i.useState([]),q=i.useMemo(()=>{const z=t??[];return z.length===0?[]:Bd(z,s,a)},[t,s,a]),H=i.useMemo(()=>{if(T)return q;const z=[];return q.forEach(J=>{const rt=J.items[0],Q=z.find(et=>et.items[0]===rt);Q?(Q.count+=J.count,Q.occurrences=Q.occurrences.concat(J.occurrences)):z.push({items:[rt],count:J.count,occurrences:J.occurrences,status:J.status})}),z},[T,q]),W=i.useMemo(()=>H.length===0?[]:Vd(H,b,$),[H,b,$]),Nt=i.useMemo(()=>{var rt,Q;if(!T)return w;const z=(rt=o==null?void 0:o.tableHeaders)==null?void 0:rt.length;if(!z)return w;const J=[];for(let et=0;et{W.length===0?L([]):W.length===1&&L(W[0].items)},[W]);const At=(z,J)=>{J.setRowSelection(()=>{const Q={};return Q[z.index]=!0,Q});const rt=z.original.items;L(rt),h&&rt.length>0&&h(rt[0])},Ot=z=>{if(z==="book"||z==="chapter"||z==="verse")c(z);else throw new Error(`Invalid scope value: ${z}`)},nt=z=>{if(z==="all"||z==="approved"||z==="unapproved"||z==="unknown")M(z);else throw new Error(`Invalid status filter value: ${z}`)},wt=i.useMemo(()=>{if(H.length===0||P.length===0)return[];const z=H.filter(J=>I.deepEqual(T?J.items:[J.items[0]],P));if(z.length>1)throw new Error("Selected item is not unique");return z.length===0?[]:z[0].occurrences},[P,T,H]);return n.jsx("div",{id:d,className:"pr-twp tw-h-full tw-overflow-auto",children:n.jsxs("div",{className:"tw-flex tw-h-full tw-w-full tw-min-w-min tw-flex-col",children:[n.jsxs("div",{className:"tw-flex tw-items-stretch",style:{contain:"inline-size"},children:[n.jsxs(He,{onValueChange:z=>nt(z),defaultValue:b,children:[n.jsx(Oe,{className:"tw-m-1 tw-w-auto tw-flex-1",children:n.jsx(Ue,{placeholder:"Select filter"})}),n.jsxs(Pe,{children:[n.jsx(Xt,{value:"all",children:p}),n.jsx(Xt,{value:"approved",children:g}),n.jsx(Xt,{value:"unapproved",children:y}),n.jsx(Xt,{value:"unknown",children:v})]})]}),n.jsxs(He,{onValueChange:z=>Ot(z),defaultValue:l,children:[n.jsx(Oe,{className:"tw-m-1 tw-w-auto tw-flex-1",children:n.jsx(Ue,{placeholder:"Select scope"})}),n.jsxs(Pe,{children:[n.jsx(Xt,{value:"book",children:j}),n.jsx(Xt,{value:"chapter",children:R}),n.jsx(Xt,{value:"verse",children:S})]})]}),n.jsx(Xe,{className:"tw-m-1 tw-flex-1 tw-rounded-md tw-border",placeholder:C,value:$,onChange:z=>{B(z.target.value)}}),o&&n.jsx(Ct,{children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:n.jsxs("div",{className:"tw-m-1 tw-flex tw-w-fit tw-min-w-[26px] tw-items-center tw-rounded-md tw-border",children:[n.jsx(wr,{className:"tw-m-1 tw-flex-shrink-0",checked:T,onCheckedChange:z=>{E(z)}}),n.jsx(xt,{className:"tw-m-1 tw-truncate",children:(o==null?void 0:o.checkboxText)??N})]})}),n.jsx(St,{children:(o==null?void 0:o.checkboxText)??N})]})})]}),n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(ia,{columns:Nt,data:W,onRowClickHandler:At,stickyHeader:!0,isLoading:u,noResultsMessage:D})}),wt.length>0&&n.jsx("div",{className:"tw-m-1 tw-flex-1 tw-overflow-auto tw-rounded-md tw-border",children:n.jsx(Rd,{classNameForText:m,occurrenceData:wt,setScriptureReference:e,localizedStrings:r})})]})})}const Gd="16rem",zd="3rem",ya=i.createContext(void 0);function In(){const t=i.useContext(ya);if(!t)throw new Error("useSidebar must be used within a SidebarProvider.");return t}const io=i.forwardRef(({defaultOpen:t=!0,open:e,onOpenChange:r,className:o,style:s,children:a,side:l="primary",...c},w)=>{const[d,u]=i.useState(t),m=e??d,h=i.useCallback(S=>{const C=typeof S=="function"?S(m):S;r?r(C):u(C)},[r,m]),p=i.useCallback(()=>h(S=>!S),[h]),g=m?"expanded":"collapsed",j=bt()==="ltr"?l:l==="primary"?"secondary":"primary",R=i.useMemo(()=>({state:g,open:m,setOpen:h,toggleSidebar:p,side:j}),[g,m,h,p,j]);return n.jsx(ya.Provider,{value:R,children:n.jsx(Ct,{delayDuration:0,children:n.jsx("div",{style:{"--sidebar-width":Gd,"--sidebar-width-icon":zd,...s},className:f("tw-group/sidebar-wrapper pr-twp tw-flex tw-w-full has-[[data-variant=inset]]:tw-bg-sidebar",o),ref:w,...c,children:a})})})});io.displayName="SidebarProvider";const lo=i.forwardRef(({variant:t="sidebar",collapsible:e="offcanvas",className:r,children:o,...s},a)=>{const l=In();return e==="none"?n.jsx("div",{className:f("tw-flex tw-h-full tw-w-[--sidebar-width] tw-flex-col tw-bg-sidebar tw-text-sidebar-foreground",r),ref:a,...s,children:o}):n.jsxs("div",{ref:a,className:"tw-group tw-peer tw-hidden tw-text-sidebar-foreground md:tw-block","data-state":l.state,"data-collapsible":l.state==="collapsed"?e:"","data-variant":t,"data-side":l.side,children:[n.jsx("div",{className:f("tw-relative tw-h-svh tw-w-[--sidebar-width] tw-bg-transparent tw-transition-[width] tw-duration-200 tw-ease-linear","group-data-[collapsible=offcanvas]:tw-w-0","group-data-[side=secondary]:tw-rotate-180",t==="floating"||t==="inset"?"group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon]")}),n.jsx("div",{className:f("tw-absolute tw-inset-y-0 tw-z-10 tw-hidden tw-h-svh tw-w-[--sidebar-width] tw-transition-[left,right,width] tw-duration-200 tw-ease-linear md:tw-flex",l.side==="primary"?"tw-left-0 group-data-[collapsible=offcanvas]:tw-left-[calc(var(--sidebar-width)*-1)]":"tw-right-0 group-data-[collapsible=offcanvas]:tw-right-[calc(var(--sidebar-width)*-1)]",t==="floating"||t==="inset"?"tw-p-2 group-data-[collapsible=icon]:tw-w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]":"group-data-[collapsible=icon]:tw-w-[--sidebar-width-icon] group-data-[side=primary]:tw-border-r group-data-[side=secondary]:tw-border-l",r),...s,children:n.jsx("div",{"data-sidebar":"sidebar",className:"tw-flex tw-h-full tw-w-full tw-flex-col tw-bg-sidebar group-data-[variant=floating]:tw-rounded-lg group-data-[variant=floating]:tw-border group-data-[variant=floating]:tw-border-sidebar-border group-data-[variant=floating]:tw-shadow",children:o})})]})});lo.displayName="Sidebar";const ja=i.forwardRef(({className:t,onClick:e,...r},o)=>{const s=In();return n.jsxs(G,{ref:o,"data-sidebar":"trigger",variant:"ghost",size:"icon",className:f("tw-h-7 tw-w-7",t),onClick:a=>{e==null||e(a),s.toggleSidebar()},...r,children:[s.side==="primary"?n.jsx(_.PanelLeft,{}):n.jsx(_.PanelRight,{}),n.jsx("span",{className:"tw-sr-only",children:"Toggle Sidebar"})]})});ja.displayName="SidebarTrigger";const Na=i.forwardRef(({className:t,...e},r)=>{const{toggleSidebar:o}=In();return n.jsx("button",{type:"button",ref:r,"data-sidebar":"rail","aria-label":"Toggle Sidebar",tabIndex:-1,onClick:o,title:"Toggle Sidebar",className:f("tw-absolute tw-inset-y-0 tw-z-20 tw-hidden tw-w-4 tw--translate-x-1/2 tw-transition-all tw-ease-linear after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-[2px] hover:after:tw-bg-sidebar-border group-data-[side=primary]:tw--right-4 group-data-[side=secondary]:tw-left-0 sm:tw-flex","[[data-side=secondary]_&]:tw-cursor-e-resize [[data-side=secondary]_&]:tw-cursor-w-resize","[[data-side=primary][data-state=collapsed]_&]:tw-cursor-e-resize [[data-side=secondary][data-state=collapsed]_&]:tw-cursor-w-resize","group-data-[collapsible=offcanvas]:tw-translate-x-0 group-data-[collapsible=offcanvas]:after:tw-left-full group-data-[collapsible=offcanvas]:hover:tw-bg-sidebar","[[data-side=primary][data-collapsible=offcanvas]_&]:tw--right-2","[[data-side=secondary][data-collapsible=offcanvas]_&]:tw--left-2",t),...e})});Na.displayName="SidebarRail";const co=i.forwardRef(({className:t,...e},r)=>n.jsx("main",{ref:r,className:f("tw-relative tw-flex tw-flex-1 tw-flex-col tw-bg-background","peer-data-[variant=inset]:tw-min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:tw-m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:tw-ml-2 md:peer-data-[variant=inset]:tw-ml-0 md:peer-data-[variant=inset]:tw-rounded-xl md:peer-data-[variant=inset]:tw-shadow",t),...e}));co.displayName="SidebarInset";const ka=i.forwardRef(({className:t,...e},r)=>n.jsx(Xe,{ref:r,"data-sidebar":"input",className:f("tw-h-8 tw-w-full tw-bg-background tw-shadow-none focus-visible:tw-ring-2 focus-visible:tw-ring-sidebar-ring",t),...e}));ka.displayName="SidebarInput";const _a=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"header",className:f("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));_a.displayName="SidebarHeader";const Ca=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"footer",className:f("tw-flex tw-flex-col tw-gap-2 tw-p-2",t),...e}));Ca.displayName="SidebarFooter";const Sa=i.forwardRef(({className:t,...e},r)=>n.jsx(Ke,{ref:r,"data-sidebar":"separator",className:f("tw-mx-2 tw-w-auto tw-bg-sidebar-border",t),...e}));Sa.displayName="SidebarSeparator";const wo=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"content",className:f("tw-flex tw-min-h-0 tw-flex-1 tw-flex-col tw-gap-2 tw-overflow-auto group-data-[collapsible=icon]:tw-overflow-hidden",t),...e}));wo.displayName="SidebarContent";const tr=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group",className:f("tw-relative tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-p-2",t),...e}));tr.displayName="SidebarGroup";const er=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?sn.Slot:"div";return n.jsx(s,{ref:o,"data-sidebar":"group-label",className:f("tw-flex tw-h-8 tw-shrink-0 tw-items-center tw-rounded-md tw-px-2 tw-text-xs tw-font-medium tw-text-sidebar-foreground/70 tw-outline-none tw-ring-sidebar-ring tw-transition-[margin,opa] tw-duration-200 tw-ease-linear focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","group-data-[collapsible=icon]:tw--mt-8 group-data-[collapsible=icon]:tw-opacity-0",t),...r})});er.displayName="SidebarGroupLabel";const Ea=i.forwardRef(({className:t,asChild:e=!1,...r},o)=>{const s=e?sn.Slot:"button";return n.jsx(s,{ref:o,"data-sidebar":"group-action",className:f("tw-absolute tw-right-3 tw-top-3.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","group-data-[collapsible=icon]:tw-hidden",t),...r})});Ea.displayName="SidebarGroupAction";const nr=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"group-content",className:f("tw-w-full tw-text-sm",t),...e}));nr.displayName="SidebarGroupContent";const uo=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu",className:f("tw-flex tw-w-full tw-min-w-0 tw-flex-col tw-gap-1",t),...e}));uo.displayName="SidebarMenu";const po=i.forwardRef(({className:t,...e},r)=>n.jsx("li",{ref:r,"data-sidebar":"menu-item",className:f("tw-group/menu-item tw-relative",t),...e}));po.displayName="SidebarMenuItem";const qd=Ae.cva("tw-peer/menu-button tw-flex tw-w-full tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-p-2 tw-text-left tw-text-sm tw-outline-none tw-ring-sidebar-ring tw-transition-[width,height,padding] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 tw-group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 data-[active=true]:tw-font-medium data-[active=true]:tw-text-sidebar-accent-foreground data-[active=true]:tw-bg-sidebar-accent data-[state=open]:hover:tw-bg-sidebar-accent data-[state=open]:hover:tw-text-sidebar-accent-foreground group-data-[collapsible=icon]:tw-!size-8 group-data-[collapsible=icon]:tw-!p-2 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0",{variants:{variant:{default:"hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground",outline:"tw-bg-background tw-shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground hover:tw-shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"},size:{default:"tw-h-8 tw-text-sm",sm:"tw-h-7 tw-text-xs",lg:"tw-h-12 tw-text-sm group-data-[collapsible=icon]:tw-!p-0"}},defaultVariants:{variant:"default",size:"default"}}),mo=i.forwardRef(({asChild:t=!1,isActive:e=!1,variant:r="default",size:o="default",tooltip:s,className:a,...l},c)=>{const w=t?sn.Slot:"button",{state:d}=In(),u=n.jsx(w,{ref:c,"data-sidebar":"menu-button","data-size":o,"data-active":e,className:f(qd({variant:r,size:o}),a),...l});return s?(typeof s=="string"&&(s={children:s}),n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:u}),n.jsx(St,{side:"right",align:"center",hidden:d!=="collapsed",...s})]})):u});mo.displayName="SidebarMenuButton";const Ra=i.forwardRef(({className:t,asChild:e=!1,showOnHover:r=!1,...o},s)=>{const a=e?sn.Slot:"button";return n.jsx(a,{ref:s,"data-sidebar":"menu-action",className:f("tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-absolute tw-right-1 tw-top-1.5 tw-flex tw-aspect-square tw-w-5 tw-items-center tw-justify-center tw-rounded-md tw-p-0 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring tw-transition-transform hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 [&>svg]:tw-size-4 [&>svg]:tw-shrink-0","after:tw-absolute after:tw--inset-2 after:md:tw-hidden","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",r&&"tw-group-focus-within/menu-item:opacity-100 tw-group-hover/menu-item:opacity-100 tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:tw-opacity-100 md:tw-opacity-0",t),...o})});Ra.displayName="SidebarMenuAction";const Ta=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,"data-sidebar":"menu-badge",className:f("tw-pointer-events-none tw-absolute tw-right-1 tw-flex tw-h-5 tw-min-w-5 tw-select-none tw-items-center tw-justify-center tw-rounded-md tw-px-1 tw-text-xs tw-font-medium tw-tabular-nums tw-text-sidebar-foreground","tw-peer-hover/menu-button:text-sidebar-accent-foreground tw-peer-data-[active=true]/menu-button:text-sidebar-accent-foreground","tw-peer-data-[size=sm]/menu-button:top-1","tw-peer-data-[size=default]/menu-button:top-1.5","tw-peer-data-[size=lg]/menu-button:top-2.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));Ta.displayName="SidebarMenuBadge";const Da=i.forwardRef(({className:t,showIcon:e=!1,...r},o)=>{const s=i.useMemo(()=>`${Math.floor(Math.random()*40)+50}%`,[]);return n.jsxs("div",{ref:o,"data-sidebar":"menu-skeleton",className:f("tw-flex tw-h-8 tw-items-center tw-gap-2 tw-rounded-md tw-px-2",t),...r,children:[e&&n.jsx(Qn,{className:"tw-size-4 tw-rounded-md","data-sidebar":"menu-skeleton-icon"}),n.jsx(Qn,{className:"tw-h-4 tw-max-w-[--skeleton-width] tw-flex-1","data-sidebar":"menu-skeleton-text",style:{"--skeleton-width":s}})]})});Da.displayName="SidebarMenuSkeleton";const Ia=i.forwardRef(({className:t,...e},r)=>n.jsx("ul",{ref:r,"data-sidebar":"menu-sub",className:f("tw-mx-3.5 tw-flex tw-min-w-0 tw-translate-x-px tw-flex-col tw-gap-1 tw-border-l tw-border-sidebar-border tw-px-2.5 tw-py-0.5","group-data-[collapsible=icon]:tw-hidden",t),...e}));Ia.displayName="SidebarMenuSub";const Ma=i.forwardRef(({...t},e)=>n.jsx("li",{ref:e,...t}));Ma.displayName="SidebarMenuSubItem";const Oa=i.forwardRef(({asChild:t=!1,size:e="md",isActive:r,className:o,...s},a)=>{const l=t?sn.Slot:"a";return n.jsx(l,{ref:a,"data-sidebar":"menu-sub-button","data-size":e,"data-active":r,className:f("tw-flex tw-h-7 tw-min-w-0 tw--translate-x-px tw-items-center tw-gap-2 tw-overflow-hidden tw-rounded-md tw-px-2 tw-text-sidebar-foreground tw-outline-none tw-ring-sidebar-ring hover:tw-bg-sidebar-accent hover:tw-text-sidebar-accent-foreground focus-visible:tw-ring-2 active:tw-bg-sidebar-accent active:tw-text-sidebar-accent-foreground disabled:tw-pointer-events-none disabled:tw-opacity-50 aria-disabled:tw-pointer-events-none aria-disabled:tw-opacity-50 [&>span:last-child]:tw-truncate [&>svg]:tw-size-4 [&>svg]:tw-shrink-0 [&>svg]:tw-text-sidebar-accent-foreground","data-[active=true]:tw-bg-sidebar-accent data-[active=true]:tw-text-sidebar-accent-foreground",e==="sm"&&"tw-text-xs",e==="md"&&"tw-text-sm","group-data-[collapsible=icon]:tw-hidden",o),...s})});Oa.displayName="SidebarMenuSubButton";function Pa({id:t,extensionLabels:e,projectInfo:r,handleSelectSidebarItem:o,selectedSidebarItem:s,extensionsSidebarGroupLabel:a,projectsSidebarGroupLabel:l,buttonPlaceholderText:c,className:w}){const d=i.useCallback((p,g)=>{o(p,g)},[o]),u=i.useCallback(p=>{const g=r.find(y=>y.projectId===p);return g?g.projectName:p},[r]),m=i.useMemo(()=>r.map(p=>({id:p.projectId,shortName:p.projectName,fullName:p.projectName})),[r]),h=i.useCallback(p=>!s.projectId&&p===s.label,[s]);return n.jsx(lo,{id:t,collapsible:"none",variant:"inset",className:f("tw-w-96 tw-gap-2 tw-overflow-y-auto",w),children:n.jsxs(wo,{children:[n.jsxs(tr,{children:[n.jsx(er,{className:"tw-text-sm",children:a}),n.jsx(nr,{children:n.jsx(uo,{children:Object.entries(e).map(([p,g])=>n.jsx(po,{children:n.jsx(mo,{onClick:()=>d(p),isActive:h(p),children:n.jsx("span",{className:"tw-pl-3",children:g})})},p))})})]}),n.jsxs(tr,{children:[n.jsx(er,{className:"tw-text-sm",children:l}),n.jsx(nr,{className:"tw-pl-3",children:n.jsxs("div",{className:f("tw-flex tw-w-full tw-items-center tw-gap-2 tw-rounded-md tw-px-2 tw-py-1",{"tw-bg-sidebar-accent tw-text-sidebar-accent-foreground":s==null?void 0:s.projectId}),children:[n.jsx(_.ScrollText,{className:"tw-h-4 tw-w-4 tw-shrink-0"}),n.jsx(la,{mode:"project",projects:m,openTabs:[],selection:{projectId:(s==null?void 0:s.projectId)??""},onChangeSelection:({projectId:p})=>{if(!p)return;const g=u(p);d(g,p)},buttonVariant:"ghost",buttonClassName:"tw-h-8 tw-w-full tw-flex-1 tw-justify-start tw-font-normal",buttonPlaceholder:c,ariaLabel:l,popoverContentStyle:{zIndex:ar}})]})})]})]})})}const ur=i.forwardRef(({value:t,onSearch:e,placeholder:r,isFullWidth:o,className:s,isDisabled:a=!1,id:l},c)=>{const w=bt();return n.jsxs("div",{id:l,className:f("tw-relative",{"tw-w-full":o},s),children:[n.jsx(_.Search,{className:f("tw-absolute tw-top-1/2 tw-h-4 tw-w-4 tw--translate-y-1/2 tw-transform tw-opacity-50",{"tw-right-3":w==="rtl"},{"tw-left-3":w==="ltr"})}),n.jsx(Xe,{ref:c,className:"tw-w-full tw-text-ellipsis tw-pe-9 tw-ps-9",placeholder:r,value:t,onChange:d=>e(d.target.value),disabled:a}),t&&n.jsxs(G,{variant:"ghost",size:"icon",className:f("tw-absolute tw-top-1/2 tw-h-7 tw--translate-y-1/2 tw-transform hover:tw-bg-transparent",{"tw-left-0":w==="rtl"},{"tw-right-0":w==="ltr"}),onClick:()=>{e("")},children:[n.jsx(_.X,{className:"tw-h-4 tw-w-4"}),n.jsx("span",{className:"tw-sr-only",children:"Clear"})]})]})});ur.displayName="SearchBar";function Kd({id:t,extensionLabels:e,projectInfo:r,children:o,handleSelectSidebarItem:s,selectedSidebarItem:a,searchValue:l,onSearch:c,extensionsSidebarGroupLabel:w,projectsSidebarGroupLabel:d,buttonPlaceholderText:u}){return n.jsxs("div",{className:"tw-box-border tw-flex tw-h-full tw-flex-col",children:[n.jsx("div",{className:"tw-box-border tw-flex tw-items-center tw-justify-center tw-py-4",children:n.jsx(ur,{className:"tw-w-9/12",value:l,onSearch:c,placeholder:"Search app settings, extension settings, and project settings"})}),n.jsxs(io,{id:t,className:"tw-h-full tw-flex-1 tw-gap-4 tw-overflow-auto tw-border-t",children:[n.jsx(Pa,{className:"tw-w-1/2 tw-min-w-[140px] tw-max-w-[220px] tw-border-e",extensionLabels:e,projectInfo:r,handleSelectSidebarItem:s,selectedSidebarItem:a,extensionsSidebarGroupLabel:w,projectsSidebarGroupLabel:d,buttonPlaceholderText:u}),n.jsx(co,{className:"tw-min-w-[215px]",children:o})]})]})}const De="scrBook",Hd="scrRef",ze="source",Ud="details",Yd="Scripture Reference",Xd="Scripture Book",Aa="Type",Wd="Details";function Zd(t,e){const r=e??!1;return[{accessorFn:o=>`${o.start.book} ${o.start.chapterNum}:${o.start.verseNum}`,id:De,header:(t==null?void 0:t.scriptureReferenceColumnName)??Yd,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?at.Canon.bookIdToEnglishName(s.start.book):o.row.groupingColumnId===De?I.formatScrRef(s.start):void 0},getGroupingValue:o=>at.Canon.bookIdToNumber(o.start.book),sortingFn:(o,s)=>I.compareScrRefs(o.original.start,s.original.start),enableGrouping:!0},{accessorFn:o=>I.formatScrRef(o.start),id:Hd,header:void 0,cell:o=>{const s=o.row.original;return o.row.getIsGrouped()?void 0:I.formatScrRef(s.start)},sortingFn:(o,s)=>I.compareScrRefs(o.original.start,s.original.start),enableGrouping:!1},{accessorFn:o=>o.source.displayName,id:ze,header:r?(t==null?void 0:t.typeColumnName)??Aa:void 0,cell:o=>r||o.row.getIsGrouped()?o.getValue():void 0,getGroupingValue:o=>o.source.id,sortingFn:(o,s)=>o.original.source.displayName.localeCompare(s.original.source.displayName),enableGrouping:!0},{accessorFn:o=>o.detail,id:Ud,header:(t==null?void 0:t.detailsColumnName)??Wd,cell:o=>o.getValue(),enableGrouping:!1}]}const Jd=t=>{if(!("offset"in t.start))throw new Error("No offset available in range start");if(t.end&&!("offset"in t.end))throw new Error("No offset available in range end");const{offset:e}=t.start;let r=0;return t.end&&({offset:r}=t.end),!t.end||I.compareScrRefs(t.start,t.end)===0?`${I.scrRefToBBBCCCVVV(t.start)}+${e}`:`${I.scrRefToBBBCCCVVV(t.start)}+${e}-${I.scrRefToBBBCCCVVV(t.end)}+${r}`},Yo=t=>`${Jd({start:t.start,end:t.end})} ${t.source.displayName} ${t.detail}`;function Qd({sources:t,showColumnHeaders:e=!1,showSourceColumn:r=!1,scriptureReferenceColumnName:o,scriptureBookGroupName:s,typeColumnName:a,detailsColumnName:l,onRowSelected:c,id:w}){const[d,u]=i.useState([]),[m,h]=i.useState([{id:De,desc:!1}]),[p,g]=i.useState({}),y=i.useMemo(()=>t.flatMap(b=>b.data.map(M=>({...M,source:b.source}))),[t]),v=i.useMemo(()=>Zd({scriptureReferenceColumnName:o,typeColumnName:a,detailsColumnName:l},r),[o,a,l,r]);i.useEffect(()=>{d.includes(ze)?h([{id:ze,desc:!1},{id:De,desc:!1}]):h([{id:De,desc:!1}])},[d]);const j=Gt.useReactTable({data:y,columns:v,state:{grouping:d,sorting:m,rowSelection:p},onGroupingChange:u,onSortingChange:h,onRowSelectionChange:g,getExpandedRowModel:Gt.getExpandedRowModel(),getGroupedRowModel:Gt.getGroupedRowModel(),getCoreRowModel:Gt.getCoreRowModel(),getSortedRowModel:Gt.getSortedRowModel(),getRowId:Yo,autoResetExpanded:!1,enableMultiRowSelection:!1,enableSubRowSelection:!1});i.useEffect(()=>{if(c){const b=j.getSelectedRowModel().rowsById,M=Object.keys(b);if(M.length===1){const $=y.find(B=>Yo(B)===M[0])||void 0;$&&c($)}}},[p,y,c,j]);const R=s??Xd,S=a??Aa,C=[{label:"No Grouping",value:[]},{label:`Group by ${R}`,value:[De]},{label:`Group by ${S}`,value:[ze]},{label:`Group by ${R} and ${S}`,value:[De,ze]},{label:`Group by ${S} and ${R}`,value:[ze,De]}],N=b=>{u(JSON.parse(b))},D=(b,M)=>{!b.getIsGrouped()&&!b.getIsSelected()&&b.getToggleSelectedHandler()(M)},T=(b,M)=>b.getIsGrouped()?"":f("banded-row",M%2===0?"even":"odd"),E=(b,M,$)=>{if(!((b==null?void 0:b.length)===0||M.depth<$.column.getGroupedIndex())){if(M.getIsGrouped())switch(M.depth){case 1:return"tw-ps-4";default:return}switch(M.depth){case 1:return"tw-ps-8";case 2:return"tw-ps-12";default:return}}};return n.jsxs("div",{id:w,className:"pr-twp tw-flex tw-h-full tw-w-full tw-flex-col",children:[!e&&n.jsxs(He,{value:JSON.stringify(d),onValueChange:b=>{N(b)},children:[n.jsx(Oe,{className:"tw-mb-1 tw-mt-2",children:n.jsx(Ue,{})}),n.jsx(Pe,{position:"item-aligned",children:n.jsx(ea,{children:C.map(b=>n.jsx(Xt,{value:JSON.stringify(b.value),children:b.label},b.label))})})]}),n.jsxs(En,{className:"tw-relative tw-flex tw-flex-col tw-overflow-y-auto tw-p-0",children:[e&&n.jsx(Rn,{children:j.getHeaderGroups().map(b=>n.jsx(xe,{children:b.headers.filter(M=>M.column.columnDef.header).map(M=>n.jsx(on,{colSpan:M.colSpan,className:"top-0 tw-sticky",children:M.isPlaceholder?void 0:n.jsxs("div",{children:[M.column.getCanGroup()?n.jsx(G,{variant:"ghost",title:`Toggle grouping by ${M.column.columnDef.header}`,onClick:M.column.getToggleGroupingHandler(),type:"button",children:M.column.getIsGrouped()?"🛑":"👊 "}):void 0," ",Gt.flexRender(M.column.columnDef.header,M.getContext())]})},M.id))},b.id))}),n.jsx(Tn,{children:j.getRowModel().rows.map((b,M)=>{const $=bt();return n.jsx(xe,{"data-state":b.getIsSelected()?"selected":"",className:f(T(b,M)),onClick:B=>D(b,B),children:b.getVisibleCells().map(B=>{if(!(B.getIsPlaceholder()||B.column.columnDef.enableGrouping&&!B.getIsGrouped()&&(B.column.columnDef.id!==ze||!r)))return n.jsx(Me,{className:f(B.column.columnDef.id,"tw-p-[1px]",E(d,b,B)),children:B.getIsGrouped()?n.jsxs(G,{variant:"link",onClick:b.getToggleExpandedHandler(),type:"button",children:[b.getIsExpanded()&&n.jsx(_.ChevronDown,{}),!b.getIsExpanded()&&($==="ltr"?n.jsx(_.ChevronRight,{}):n.jsx(_.ChevronLeft,{}))," ",Gt.flexRender(B.column.columnDef.cell,B.getContext())," (",b.subRows.length,")"]}):Gt.flexRender(B.column.columnDef.cell,B.getContext())},B.id)})},b.id)})})]})]})}const fo=(t,e)=>t.filter(r=>{try{return I.getSectionForBook(r)===e}catch{return!1}}),$a=(t,e,r)=>fo(t,e).every(o=>r.includes(o));function tw({section:t,availableBookIds:e,selectedBookIds:r,onToggle:o,localizedStrings:s}){const a=fo(e,t).length===0,l=s["%scripture_section_ot_short%"],c=s["%scripture_section_nt_short%"],w=s["%scripture_section_dc_short%"],d=s["%scripture_section_extra_short%"];return n.jsx(G,{variant:"outline",size:"sm",onClick:()=>o(t),className:f($a(e,t,r)&&!a&&"tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/70 hover:tw-text-primary-foreground"),disabled:a,children:sl(t,l,c,w,d)})}const Xo=5,_r=6;function ew({availableBookInfo:t,selectedBookIds:e,onChangeSelectedBookIds:r,localizedStrings:o,localizedBookNames:s}){const a=o["%webView_book_selector_books_selected%"],l=o["%webView_book_selector_select_books%"],c=o["%webView_book_selector_search_books%"],w=o["%webView_book_selector_select_all%"],d=o["%webView_book_selector_clear_all%"],u=o["%webView_book_selector_no_book_found%"],m=o["%webView_book_selector_more%"],{otLong:h,ntLong:p,dcLong:g,extraLong:y}={otLong:o==null?void 0:o["%scripture_section_ot_long%"],ntLong:o==null?void 0:o["%scripture_section_nt_long%"],dcLong:o==null?void 0:o["%scripture_section_dc_long%"],extraLong:o==null?void 0:o["%scripture_section_extra_long%"]},[v,j]=i.useState(!1),[R,S]=i.useState(""),C=i.useRef(void 0),N=i.useRef(!1);if(t.length!==at.Canon.allBookIds.length)throw new Error("availableBookInfo length must match Canon.allBookIds length");const D=i.useMemo(()=>at.Canon.allBookIds.filter((L,q)=>t[q]==="1"&&!at.Canon.isObsolete(at.Canon.bookIdToNumber(L))),[t]),T=i.useMemo(()=>{if(!R.trim()){const H={[I.Section.OT]:[],[I.Section.NT]:[],[I.Section.DC]:[],[I.Section.Extra]:[]};return D.forEach(W=>{const Nt=I.getSectionForBook(W);H[Nt].push(W)}),H}const L=D.filter(H=>Vr(H,R,s)),q={[I.Section.OT]:[],[I.Section.NT]:[],[I.Section.DC]:[],[I.Section.Extra]:[]};return L.forEach(H=>{const W=I.getSectionForBook(H);q[W].push(H)}),q},[D,R,s]),E=i.useCallback((L,q=!1)=>{if(!q||!C.current){r(e.includes(L)?e.filter(nt=>nt!==L):[...e,L]),C.current=L;return}const H=D.findIndex(nt=>nt===C.current),W=D.findIndex(nt=>nt===L);if(H===-1||W===-1)return;const[Nt,At]=[Math.min(H,W),Math.max(H,W)],Ot=D.slice(Nt,At+1).map(nt=>nt);r(e.includes(L)?e.filter(nt=>!Ot.includes(nt)):[...new Set([...e,...Ot])])},[e,r,D]),b=L=>{E(L,N.current),N.current=!1},M=(L,q)=>{L.preventDefault(),E(q,L.shiftKey)},$=i.useCallback(L=>{const q=fo(D,L).map(H=>H);r($a(D,L,e)?e.filter(H=>!q.includes(H)):[...new Set([...e,...q])])},[e,r,D]),B=()=>{r(D.map(L=>L))},P=()=>{r([])};return n.jsxs("div",{className:"tw-space-y-2",children:[n.jsx("div",{className:"tw-flex tw-flex-wrap tw-gap-2",children:Object.values(I.Section).map(L=>n.jsx(tw,{section:L,availableBookIds:D,selectedBookIds:e,onToggle:$,localizedStrings:o},L))}),n.jsxs(we,{open:v,onOpenChange:L=>{j(L),L||S("")},children:[n.jsx(je,{asChild:!0,children:n.jsxs(G,{variant:"outline",role:"combobox","aria-expanded":v,className:"tw-max-w-64 tw-justify-between",children:[e.length>0?`${a}: ${e.length}`:l,n.jsx(_.ChevronsUpDown,{className:"tw-ml-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(re,{className:"tw-w-[500px] tw-max-w-[calc(100vw-2rem)] tw-p-0",align:"start",children:n.jsxs(ce,{shouldFilter:!1,onKeyDown:L=>{L.key==="Enter"&&(N.current=L.shiftKey)},children:[n.jsx($e,{placeholder:c,value:R,onValueChange:S}),n.jsxs("div",{className:"tw-flex tw-justify-between tw-border-b tw-p-2",children:[n.jsx(G,{variant:"ghost",size:"sm",onClick:B,children:w}),n.jsx(G,{variant:"ghost",size:"sm",onClick:P,children:d})]}),n.jsxs(de,{children:[n.jsx(Ye,{children:u}),Object.values(I.Section).map((L,q)=>{const H=T[L];if(H.length!==0)return n.jsxs(i.Fragment,{children:[n.jsx(te,{heading:cs(L,h,p,g,y),children:H.map(W=>n.jsx(ws,{bookId:W,isSelected:e.includes(W),onSelect:()=>b(W),onMouseDown:Nt=>M(Nt,W),section:I.getSectionForBook(W),showCheck:!0,localizedBookNames:s,commandValue:ms(W,s),className:"tw-flex tw-items-center"},W))}),q0&&n.jsxs("div",{className:"tw-mt-2 tw-flex tw-flex-wrap tw-gap-1",children:[e.slice(0,e.length===_r?_r:Xo).map(L=>n.jsx(ae,{className:"hover:tw-bg-secondary",variant:"secondary",children:Ie(L,s)},L)),e.length>_r&&n.jsx(ae,{className:"hover:tw-bg-secondary",variant:"secondary",children:`+${e.length-Xo} ${m}`})]})]})}const nw=Object.freeze(["%webView_scope_selector_selected_text%","%webView_scope_selector_verse%","%webView_scope_selector_chapter%","%webView_scope_selector_book%","%webView_scope_selector_current_verse%","%webView_scope_selector_current_chapter%","%webView_scope_selector_current_book%","%webView_scope_selector_choose_books%","%webView_scope_selector_scope%","%webView_scope_selector_select_books%","%webView_scope_selector_range%","%webView_scope_selector_select_range%","%webView_scope_selector_range_start%","%webView_scope_selector_range_end%","%webView_scope_selector_ok%","%webView_scope_selector_cancel%","%webView_scope_selector_navigate%","%webView_book_selector_books_selected%","%webView_book_selector_select_books%","%webView_book_selector_search_books%","%webView_book_selector_select_all%","%webView_book_selector_clear_all%","%webView_book_selector_no_book_found%","%webView_book_selector_more%","%scripture_section_ot_long%","%scripture_section_ot_short%","%scripture_section_nt_long%","%scripture_section_nt_short%","%scripture_section_dc_long%","%scripture_section_dc_short%","%scripture_section_extra_long%","%scripture_section_extra_short%"]),Dt=(t,e)=>t[e]??e,rw=Object.freeze([" ","-"]);function ow({scope:t,availableScopes:e,onScopeChange:r,availableBookInfo:o,selectedBookIds:s,onSelectedBookIdsChange:a,localizedStrings:l,localizedBookNames:c,id:w,variant:d="radio",rangeStart:u,rangeEnd:m,onRangeStartChange:h,onRangeEndChange:p,currentScrRef:g,onCurrentScrRefChange:y,bookChapterControlLocalizedStrings:v,getEndVerse:j,hideLabel:R=!1,buttonClassName:S}){const C=Dt(l,"%webView_scope_selector_selected_text%"),N=Dt(l,"%webView_scope_selector_verse%"),D=Dt(l,"%webView_scope_selector_chapter%"),T=Dt(l,"%webView_scope_selector_book%"),E=Dt(l,"%webView_scope_selector_current_verse%"),b=Dt(l,"%webView_scope_selector_current_chapter%"),M=Dt(l,"%webView_scope_selector_current_book%"),$=Dt(l,"%webView_scope_selector_choose_books%"),B=Dt(l,"%webView_scope_selector_scope%"),P=Dt(l,"%webView_scope_selector_select_books%"),L=Dt(l,"%webView_scope_selector_range%"),q=Dt(l,"%webView_scope_selector_select_range%"),H=Dt(l,"%webView_scope_selector_range_start%"),W=Dt(l,"%webView_scope_selector_range_end%"),Nt=Dt(l,"%webView_scope_selector_ok%"),At=Dt(l,"%webView_scope_selector_cancel%"),Ot=Dt(l,"%webView_scope_selector_navigate%"),nt=V=>{if(!g)return;const Y=g.book.toUpperCase();switch(V){case"verse":return I.formatScrRef(g,"id");case"chapter":return`${Y} ${g.chapterNum}`;case"book":return Y;default:return}},wt=[{value:"selectedText",label:C,id:"scope-selected-text"},{value:"verse",label:N,dropdownLabel:E,scrRefSuffix:nt("verse"),id:"scope-verse"},{value:"chapter",label:D,dropdownLabel:b,scrRefSuffix:nt("chapter"),id:"scope-chapter"},{value:"book",label:T,dropdownLabel:M,scrRefSuffix:nt("book"),id:"scope-book"},{value:"selectedBooks",label:$,id:"scope-selected"},{value:"range",label:L,id:"scope-range"}],z=(V,Y,zt=!1)=>n.jsxs(n.Fragment,{children:[V,Y&&!zt&&n.jsxs("span",{className:"tw-text-muted-foreground",children:[": ",Y]})]}),J=e?wt.filter(V=>e.includes(V.value)):wt,rt=g??I.defaultScrRef,Q=u??rt,et=m??rt,$t=()=>{},Et=i.useRef(null),Lt=i.useRef(null),pe=i.useRef(!1),A=i.useRef(null),Ht=i.useRef(!1),[me,Rt]=i.useState(void 0),F=i.useRef(!1),U=i.useRef(!1),Z=i.useRef(null),ot=i.useCallback(V=>{if(V){Rt("start"),F.current=!1;return}Rt(Y=>Y==="start"?void 0:Y),F.current&&(F.current=!1,requestAnimationFrame(()=>{var zt;const Y=(zt=Et.current)==null?void 0:zt.querySelector("button");Y==null||Y.click()}))},[]),mt=i.useCallback(V=>{if(V){Rt("end"),U.current=!1;return}Rt(Y=>Y==="end"?void 0:Y)},[]),ft=i.useCallback(V=>{h==null||h(V),p==null||p(V),F.current=!0},[h,p]),kt=i.useCallback(V=>{p==null||p(V),U.current=!0},[p]),ut=i.useCallback(V=>{r(V),V==="selectedBooks"&&s.length===0&&(g!=null&&g.book)&&a([g.book])},[r,s,g,a]),vt=J.find(V=>V.value===t),Pt=()=>t==="selectedBooks"&&s.length>0?s.map(V=>V.toUpperCase()).join(", "):t==="range"?I.formatScrRefRange(Q,et,{optionOrLocalizedBookName:"id",endRefOptionOrLocalizedBookName:"id",repeatBookName:!0}):vt?z(vt.label,vt.scrRefSuffix):t,O=J.filter(V=>V.value!=="selectedBooks"&&V.value!=="range"),ht=J.find(V=>V.value==="selectedBooks"),it=J.find(V=>V.value==="range"),[Ee,Le]=i.useState(!1),[Ve,We]=i.useState(void 0),[Re,dn]=i.useState(void 0),[Te,wn]=i.useState(void 0),[Be,un]=i.useState(void 0),[pn,Mn]=i.useState([]),On=d==="dropdown"&&Ve==="selectedBooks",k=n.jsx(ew,{availableBookInfo:o,selectedBookIds:On?pn:s,onChangeSelectedBookIds:On?Mn:a,localizedStrings:l,localizedBookNames:c}),K=me==="end",X=me==="start",_t="tw-text-muted-foreground",Zt=d==="dropdown"&&Ve==="range",Ze=Zt?wn:ft,Vt=Zt?un:p?kt:$t,yt=n.jsxs("div",{className:"tw-flex tw-flex-wrap tw-items-end tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(xt,{htmlFor:"scope-range-start",className:f(K&&_t),children:H}),n.jsx(zn,{id:"scope-range-start",scrRef:Zt?Te??Q:Q,handleSubmit:Ze,localizedBookNames:c,localizedStrings:v,getEndVerse:j,submitKeys:rw,onOpenChange:ot,className:f(K&&_t),modal:!0})]}),n.jsxs("div",{ref:Et,className:"tw-grid tw-gap-2",children:[n.jsx(xt,{htmlFor:"scope-range-end",className:f(X&&_t),children:W}),n.jsx(zn,{id:"scope-range-end",scrRef:Zt?Be??et:et,handleSubmit:Vt,localizedBookNames:c,localizedStrings:v,getEndVerse:j,disableReferencesUpTo:Zt?Te??Q:Q,onOpenChange:mt,onCloseAutoFocus:V=>{var Y;U.current&&(U.current=!1,V.preventDefault(),(Y=Z.current)==null||Y.focus())},className:f(X&&_t),modal:!0,align:"start"})]})]}),Tt=i.useRef({}),pt=i.useCallback(V=>Y=>{Tt.current[V]=Y},[]),Ut=i.useRef(null);i.useEffect(()=>{if(!Ee)return;let V=0;const Y=requestAnimationFrame(()=>{V=requestAnimationFrame(()=>{var zt;(zt=Tt.current[t])==null||zt.focus()})});return()=>{cancelAnimationFrame(Y),V&&cancelAnimationFrame(V)}},[Ee,t]);const[Yt,Fe]=i.useState(null),[Pn,_i]=i.useState(null),[An,Ci]=i.useState(null),Si=200,[Ei,Ri]=i.useState(!1);i.useEffect(()=>{if(!An||typeof ResizeObserver>"u")return;const V=new ResizeObserver(([Y])=>{Ri(Y.contentRect.widthV.disconnect()},[An]);const vo=i.useCallback(V=>{dn(V),wn(Q),un(et),Mn(s),Le(!1),We(V)},[Q,et,s]),yo=i.useCallback(()=>{Re!==void 0&&(Re==="range"?(Te&&(h==null||h(Te)),Be&&(p==null||p(Be))):Re==="selectedBooks"&&a(pn),ut(Re),We(void 0),dn(void 0))},[Re,Te,Be,pn,h,p,a,ut]),$n=i.useCallback(V=>{V||(We(void 0),dn(void 0))},[]),jo=i.useCallback(V=>{var Y;V.preventDefault(),(Y=Ut.current)==null||Y.focus()},[]),No=V=>t===V?n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(_.Check,{className:"tw-h-4 tw-w-4"})}):void 0;return n.jsxs("div",{id:w,className:"tw-grid tw-gap-4",children:[n.jsxs("div",{className:"tw-grid tw-gap-2",children:[!R&&n.jsx(xt,{children:B}),d==="dropdown"?n.jsxs(ie,{open:Ee,onOpenChange:Le,children:[n.jsx(ve,{asChild:!0,children:n.jsxs(G,{ref:Ut,variant:"outline",role:"combobox",className:f("tw-w-full tw-justify-between tw-overflow-hidden tw-font-normal",S),children:[n.jsx("span",{className:"tw-min-w-0 tw-flex-1 tw-truncate tw-text-start",children:Pt()}),n.jsx(_.ChevronDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})}),n.jsx(ee,{ref:Ci,className:"tw-w-[var(--radix-dropdown-menu-trigger-width)] tw-min-w-[12rem]",align:"start",children:n.jsxs(Fn,{container:An,children:[O.map(({value:V,label:Y,dropdownLabel:zt,scrRefSuffix:fn,id:Ti})=>n.jsxs(ke,{ref:pt(V),className:"tw-relative tw-ps-8 data-[highlighted]:tw-bg-accent data-[highlighted]:tw-text-accent-foreground",onSelect:()=>ut(V),"data-selected":t===V?"true":void 0,children:[t===V&&n.jsx("span",{className:"tw-absolute tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center ltr:tw-left-2 rtl:tw-right-2",children:n.jsx(_.Check,{className:"tw-h-4 tw-w-4"})}),z(zt??Y,fn,Ei)]},Ti)),(ht||it)&&n.jsx(ye,{}),ht&&n.jsxs(ke,{ref:pt("selectedBooks"),className:f("tw-relative tw-ps-8","data-[highlighted]:tw-bg-accent data-[highlighted]:tw-text-accent-foreground"),onSelect:()=>vo("selectedBooks"),"data-selected":t==="selectedBooks"?"true":void 0,children:[No("selectedBooks"),`${ht.label}…`]}),it&&n.jsxs(ke,{ref:pt("range"),className:f("tw-relative tw-ps-8","data-[highlighted]:tw-bg-accent data-[highlighted]:tw-text-accent-foreground"),onSelect:()=>vo("range"),"data-selected":t==="range"?"true":void 0,children:[No("range"),`${it.label}…`]}),y&&n.jsxs(n.Fragment,{children:[n.jsx(ye,{}),n.jsx(Ce,{className:"tw-px-2 tw-py-1.5 tw-text-xs tw-font-medium tw-text-muted-foreground",children:Ot}),n.jsx(ke,{ref:A,className:"tw-p-0",onSelect:V=>{var Y,zt;if(V.preventDefault(),pe.current){pe.current=!1;return}Ht.current||(zt=(Y=Lt.current)==null?void 0:Y.querySelector("button"))==null||zt.click()},children:n.jsx("div",{ref:Lt,className:"tw-w-full tw-px-1 tw-pb-1",onPointerDownCapture:V=>{const Y=V.target instanceof HTMLElement?V.target:void 0;Y!=null&&Y.closest("button")&&(pe.current=!0,requestAnimationFrame(()=>{pe.current=!1}))},children:n.jsx(zn,{id:"scope-navigate",scrRef:g??I.defaultScrRef,handleSubmit:y,localizedBookNames:c,localizedStrings:v,getEndVerse:j,triggerVariant:"ghost",onOpenChange:V=>{Ht.current=V},onCloseAutoFocus:V=>{var Y;V.preventDefault(),(Y=A.current)==null||Y.focus()},modal:!0,className:"tw-w-full tw-min-w-0 tw-max-w-none tw-justify-between tw-px-2 tw-font-normal",triggerContent:n.jsxs(n.Fragment,{children:[n.jsx("span",{className:"tw-min-w-0 tw-flex-1 tw-truncate tw-text-start",children:I.formatScrRef(g??I.defaultScrRef,"id")}),n.jsx(_.ChevronDown,{className:"tw-ms-2 tw-h-4 tw-w-4 tw-shrink-0 tw-opacity-50"})]})})})})]})]})})]}):n.jsx(lr,{value:t,onValueChange:ut,className:"tw-flex tw-flex-col tw-space-y-1",children:J.map(({value:V,label:Y,scrRefSuffix:zt,id:fn})=>n.jsxs("div",{className:"tw-flex tw-items-center",children:[n.jsx(kn,{className:"tw-me-2",value:V,id:fn}),n.jsx(xt,{htmlFor:fn,children:z(Y,zt)})]},fn))})]}),d==="radio"&&t==="selectedBooks"&&n.jsxs("div",{className:"tw-grid tw-gap-2",children:[n.jsx(xt,{children:P}),k]}),d==="radio"&&t==="range"&&yt,d==="dropdown"&&ht&&n.jsx(Hn,{open:Ve==="selectedBooks",onOpenChange:$n,children:n.jsx(yn,{ref:_i,onCloseAutoFocus:jo,onEscapeKeyDown:V=>{Pn!=null&&Pn.querySelector('[data-state="open"]')&&V.preventDefault()},children:n.jsxs(Fn,{container:Pn,children:[n.jsx(jn,{className:"tw-pe-8",children:n.jsx(Nn,{children:$})}),k,n.jsxs(Un,{children:[n.jsx(G,{variant:"outline",onClick:()=>$n(!1),children:At}),n.jsx(G,{onClick:yo,children:Nt})]})]})})}),d==="dropdown"&&it&&n.jsx(Hn,{open:Ve==="range",onOpenChange:$n,children:n.jsx(yn,{ref:Fe,onCloseAutoFocus:jo,onEscapeKeyDown:V=>{Yt!=null&&Yt.querySelector('[data-state="open"]')&&V.preventDefault()},children:n.jsxs(Fn,{container:Yt,children:[n.jsx(jn,{className:"tw-pe-8",children:n.jsx(Nn,{children:q})}),yt,n.jsxs(Un,{children:[n.jsx(G,{variant:"outline",onClick:()=>$n(!1),children:At}),n.jsx(G,{ref:Z,onClick:yo,children:Nt})]})]})})})]})}function sw({availableScrollGroupIds:t,scrollGroupId:e,onChangeScrollGroupId:r,localizedStrings:o={},size:s="sm",className:a,id:l}){const c={...I.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS,...Object.fromEntries(Object.entries(o).map(([d,u])=>[d,d===u&&d in I.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS?I.DEFAULT_SCROLL_GROUP_LOCALIZED_STRINGS[d]:u]))},w=bt();return n.jsxs(He,{value:`${e}`,onValueChange:d=>r(d==="undefined"?void 0:parseInt(d,10)),children:[n.jsx(Oe,{size:s,className:f("pr-twp tw-w-auto",a),children:n.jsx(Ue,{placeholder:c[I.getLocalizeKeyForScrollGroupId(e)]??e})}),n.jsx(Pe,{id:l,align:w==="rtl"?"end":"start",style:{zIndex:ln},children:t.map(d=>n.jsx(Xt,{value:`${d}`,children:c[I.getLocalizeKeyForScrollGroupId(d)]},`${d}`))})]})}function aw({children:t}){return n.jsx("div",{className:"pr-twp tw-grid",children:t})}function iw({primary:t,secondary:e,children:r,isLoading:o=!1,loadingMessage:s}){return n.jsxs("div",{className:"tw-flex tw-items-center tw-justify-between tw-space-x-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("p",{className:"tw-text-sm tw-font-medium tw-leading-none",children:t}),n.jsx("p",{className:"tw-whitespace-normal tw-break-words tw-text-sm tw-text-muted-foreground",children:e})]}),o?n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:s}):n.jsx("div",{children:r})]})}function lw({primary:t,secondary:e,includeSeparator:r=!1}){return n.jsxs("div",{className:"tw-space-y-4 tw-py-2",children:[n.jsxs("div",{children:[n.jsx("h3",{className:"tw-text-lg tw-font-medium",children:t}),n.jsx("p",{className:"tw-text-sm tw-text-muted-foreground",children:e})]}),r?n.jsx(Ke,{}):""]})}function La(t,e){var r;return(r=Object.entries(t).find(([,o])=>"menuItem"in o&&o.menuItem===e))==null?void 0:r[0]}function rr({icon:t,menuLabel:e,leading:r}){return t?n.jsx("img",{className:f("tw-max-h-5 tw-max-w-5",r?"tw-me-2":"tw-ms-2"),src:t,alt:`${r?"Leading":"Trailing"} icon for ${e}`}):void 0}const Va=(t,e,r,o)=>r?Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order).flatMap(([a])=>e.filter(c=>c.group===a).sort((c,w)=>c.order-w.order).map(c=>n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:"command"in c?n.jsxs(ke,{onClick:()=>{o(c)},children:[c.iconPathBefore&&n.jsx(rr,{icon:c.iconPathBefore,menuLabel:c.label,leading:!0}),c.label,c.iconPathAfter&&n.jsx(rr,{icon:c.iconPathAfter,menuLabel:c.label})]},`dropdown-menu-item-${c.label}-${c.command}`):n.jsxs(Js,{children:[n.jsx(eo,{children:c.label}),n.jsx(Zs,{children:n.jsx(no,{children:Va(t,e,La(t,c.id),o)})})]},`dropdown-menu-sub-${c.label}-${c.id}`)}),c.tooltip&&n.jsx(St,{children:c.tooltip})]},`tooltip-${c.label}-${"command"in c?c.command:c.id}`))):void 0;function or({onSelectMenuItem:t,menuData:e,tabLabel:r,icon:o,className:s,variant:a,buttonVariant:l="ghost",id:c}){return n.jsxs(ie,{variant:a,children:[n.jsx(ve,{"aria-label":r,className:s,asChild:!0,id:c,children:n.jsx(G,{variant:l,size:"icon",children:o??n.jsx(_.MenuIcon,{})})}),n.jsx(ee,{align:"start",style:{zIndex:ln},children:Object.entries(e.columns).filter(([,w])=>typeof w=="object").sort(([,w],[,d])=>typeof w=="boolean"||typeof d=="boolean"?0:w.order-d.order).map(([w],d,u)=>n.jsxs(i.Fragment,{children:[n.jsx(to,{children:n.jsx(Ct,{children:Va(e.groups,e.items,w,t)})}),dn.jsx("div",{ref:o,className:`tw-sticky tw-top-0 tw-box-border tw-flex tw-h-14 tw-flex-row tw-items-center tw-justify-between tw-gap-2 tw-overflow-clip tw-px-4 tw-py-2 tw-text-foreground tw-@container/toolbar ${e}`,id:t,children:r}));function cw({onSelectProjectMenuItem:t,onSelectViewInfoMenuItem:e,projectMenuData:r,tabViewMenuData:o,id:s,className:a,startAreaChildren:l,centerAreaChildren:c,endAreaChildren:w,menuButtonIcon:d}){return n.jsxs(Ba,{className:`tw-w-full tw-border ${a}`,id:s,children:[r&&n.jsx(or,{onSelectMenuItem:t,menuData:r,tabLabel:"Project",icon:d??n.jsx(_.Menu,{}),buttonVariant:"ghost"}),l&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[10] tw-flex-row tw-flex-wrap tw-items-start tw-gap-x-1 tw-gap-y-2 tw-overflow-clip",children:l}),c&&n.jsx("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[1] tw-basis-0 tw-flex-row tw-flex-wrap tw-items-start tw-justify-center tw-gap-x-1 tw-gap-y-2 tw-overflow-clip @sm:tw-basis-auto",children:c}),n.jsxs("div",{className:"tw-flex tw-h-full tw-shrink tw-grow-[1] tw-flex-row-reverse tw-flex-wrap tw-items-start tw-gap-x-1 tw-gap-y-2 tw-overflow-clip",children:[o&&n.jsx(or,{onSelectMenuItem:e,menuData:o,tabLabel:"View Info",icon:n.jsx(_.EllipsisVertical,{}),className:"tw-h-full"}),w]})]})}function dw({onSelectProjectMenuItem:t,projectMenuData:e,id:r,className:o,menuButtonIcon:s}){return n.jsx(Ba,{className:"tw-pointer-events-none",id:r,children:e&&n.jsx(or,{onSelectMenuItem:t,menuData:e,tabLabel:"Project",icon:s,className:`tw-pointer-events-auto tw-shadow-lg ${o}`,buttonVariant:"outline"})})}const ho=i.forwardRef(({className:t,...e},r)=>{const o=bt();return n.jsx(Kt.Root,{orientation:"vertical",ref:r,className:f("tw-flex tw-gap-1 tw-rounded-md tw-text-muted-foreground",t),...e,dir:o})});ho.displayName=Kt.List.displayName;const go=i.forwardRef(({className:t,...e},r)=>n.jsx(Kt.List,{ref:r,className:f("tw-flex-fit tw-mlk-items-center tw-w-[124px] tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e}));go.displayName=Kt.List.displayName;const Fa=i.forwardRef(({className:t,...e},r)=>n.jsx(Kt.Trigger,{ref:r,...e,className:f("overflow-clip tw-inline-flex tw-w-[116px] tw-cursor-pointer tw-items-center tw-justify-center tw-break-words tw-rounded-sm tw-border-0 tw-bg-muted tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-text-inherit tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t)})),xo=i.forwardRef(({className:t,...e},r)=>n.jsx(Kt.Content,{ref:r,className:f("tw-ms-5 tw-flex-grow tw-text-foreground tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));xo.displayName=Kt.Content.displayName;function ww({tabList:t,searchValue:e,onSearch:r,searchPlaceholder:o,headerTitle:s,searchClassName:a,id:l}){return n.jsxs("div",{id:l,className:"pr-twp",children:[n.jsxs("div",{className:"tw-sticky tw-top-0 tw-space-y-2 tw-pb-2",children:[s?n.jsx("h1",{children:s}):"",n.jsx(ur,{className:a,value:e,onSearch:r,placeholder:o})]}),n.jsxs(ho,{children:[n.jsx(go,{children:t.map(c=>n.jsx(Fa,{value:c.value,children:c.value},c.key))}),t.map(c=>n.jsx(xo,{value:c.value,children:c.content},c.key))]})]})}function uw({...t}){return n.jsx(ct.Menu,{...t})}function pw({...t}){return n.jsx(ct.Sub,{"data-slot":"menubar-sub",...t})}const Ga=i.forwardRef(({className:t,variant:e="default",...r},o)=>{const s=i.useMemo(()=>({variant:e}),[e]);return n.jsx(Qr.Provider,{value:s,children:n.jsx(ct.Root,{ref:o,className:f("tw-flex tw-h-10 tw-items-center tw-space-x-1 tw-rounded-md tw-border tw-bg-background tw-p-1",t),...r})})});Ga.displayName=ct.Root.displayName;const za=i.forwardRef(({className:t,...e},r)=>{const o=ue();return n.jsx(ct.Trigger,{ref:r,className:f("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground","pr-twp",Se({variant:o.variant,className:t})),...e})});za.displayName=ct.Trigger.displayName;const qa=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>{const a=ue();return n.jsxs(ct.SubTrigger,{ref:s,className:f("tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",Se({variant:a.variant,className:t}),t),...o,children:[r,n.jsx(_.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]})});qa.displayName=ct.SubTrigger.displayName;const Ka=i.forwardRef(({className:t,...e},r)=>{const o=ue();return n.jsx(ct.SubContent,{ref:r,className:f("tw-z-50 tw-min-w-[8rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",{"tw-bg-popover":o.variant==="muted"},t),...e})});Ka.displayName=ct.SubContent.displayName;const Ha=i.forwardRef(({className:t,align:e="start",alignOffset:r=-4,sideOffset:o=8,...s},a)=>{const l=ue();return n.jsx(ct.Portal,{children:n.jsx(ct.Content,{ref:a,align:e,alignOffset:r,sideOffset:o,className:f("tw-z-50 tw-min-w-[12rem] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2","pr-twp",{"tw-bg-popover":l.variant==="muted"},t),...s})})});Ha.displayName=ct.Content.displayName;const Ua=i.forwardRef(({className:t,inset:e,...r},o)=>{const s=ue();return n.jsx(ct.Item,{ref:o,className:f("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",Se({variant:s.variant,className:t}),t),...r})});Ua.displayName=ct.Item.displayName;const mw=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>{const a=ue();return n.jsxs(ct.CheckboxItem,{ref:s,className:f("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Se({variant:a.variant,className:t}),t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(ct.ItemIndicator,{children:n.jsx(_.Check,{className:"tw-h-4 tw-w-4"})})}),e]})});mw.displayName=ct.CheckboxItem.displayName;const fw=i.forwardRef(({className:t,children:e,...r},o)=>{const s=ue();return n.jsxs(ct.RadioItem,{ref:o,className:f("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",Se({variant:s.variant,className:t}),t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(ct.ItemIndicator,{children:n.jsx(_.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]})});fw.displayName=ct.RadioItem.displayName;const hw=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(ct.Label,{ref:o,className:f("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold",e&&"tw-pl-8",t),...r}));hw.displayName=ct.Label.displayName;const Ya=i.forwardRef(({className:t,...e},r)=>n.jsx(ct.Separator,{ref:r,className:f("tw--mx-1 tw-my-1 tw-h-px tw-bg-muted",t),...e}));Ya.displayName=ct.Separator.displayName;const gn=(t,e)=>{setTimeout(()=>{e.forEach(r=>{var o;(o=t.current)==null||o.dispatchEvent(new KeyboardEvent("keydown",r))})},0)},Xa=(t,e,r,o)=>{if(!r)return;const s=Object.entries(t).filter(([a,l])=>"column"in l&&l.column===r||a===r).sort(([,a],[,l])=>a.order-l.order);return s.flatMap(([a],l)=>{const c=e.filter(d=>d.group===a).sort((d,u)=>d.order-u.order).map(d=>n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:"command"in d?n.jsxs(Ua,{onClick:()=>{o(d)},children:[d.iconPathBefore&&n.jsx(rr,{icon:d.iconPathBefore,menuLabel:d.label,leading:!0}),d.label,d.iconPathAfter&&n.jsx(rr,{icon:d.iconPathAfter,menuLabel:d.label})]},`menubar-item-${d.label}-${d.command}`):n.jsxs(pw,{children:[n.jsx(qa,{children:d.label}),n.jsx(Ka,{children:Xa(t,e,La(t,d.id),o)})]},`menubar-sub-${d.label}-${d.id}`)}),d.tooltip&&n.jsx(St,{children:d.tooltip})]},`tooltip-${d.label}-${"command"in d?d.command:d.id}`)),w=[...c];return c.length>0&&l{switch(u){case"platform.app":return a;case"platform.window":return l;case"platform.layout":return c;case"platform.help":return w;default:return}};if(Xi.useHotkeys(["alt","alt+p","alt+l","alt+n","alt+h"],(u,m)=>{var g,y,v,j;u.preventDefault();const h={key:"Escape",code:"Escape",keyCode:27,bubbles:!0},p={key:" ",code:"Space",keyCode:32,bubbles:!0};switch(m.hotkey){case"alt":gn(a,[h]);break;case"alt+p":(g=a.current)==null||g.focus(),gn(a,[h,p]);break;case"alt+l":(y=l.current)==null||y.focus(),gn(l,[h,p]);break;case"alt+n":(v=c.current)==null||v.focus(),gn(c,[h,p]);break;case"alt+h":(j=w.current)==null||j.focus(),gn(w,[h,p]);break}}),i.useEffect(()=>{if(!r||!s.current)return;const u=new MutationObserver(p=>{p.forEach(g=>{if(g.attributeName==="data-state"&&g.target instanceof HTMLElement){const y=g.target.getAttribute("data-state");r(y==="open")}})});return s.current.querySelectorAll("[data-state]").forEach(p=>{u.observe(p,{attributes:!0})}),()=>u.disconnect()},[r]),!!t)return n.jsx(Ga,{ref:s,className:"pr-twp tw-border-0 tw-bg-transparent",variant:o,children:Object.entries(t.columns).filter(([,u])=>typeof u=="object").sort(([,u],[,m])=>typeof u=="boolean"||typeof m=="boolean"?0:u.order-m.order).map(([u,m])=>n.jsxs(uw,{children:[n.jsx(za,{ref:d(u),children:typeof m=="object"&&"label"in m&&m.label}),n.jsx(Ha,{style:{zIndex:ln},children:n.jsx(Ct,{children:Xa(t.groups,t.items,u,e)})})]},u))})}function xw(t){switch(t){case void 0:return;case"darwin":return"tw-ps-[85px]";default:return"tw-pe-[calc(138px+1rem)]"}}function bw({menuData:t,onOpenChange:e,onSelectMenuItem:r,className:o,id:s,children:a,appMenuAreaChildren:l,configAreaChildren:c,shouldUseAsAppDragArea:w,menubarVariant:d="default"}){const u=i.useRef(void 0);return n.jsx("div",{className:f("tw-border tw-px-4 tw-text-foreground",o),ref:u,style:{position:"relative"},id:s,children:n.jsxs("div",{className:"tw-flex tw-h-full tw-w-full tw-justify-between tw-overflow-hidden",style:w?{WebkitAppRegion:"drag"}:void 0,children:[n.jsx("div",{className:"tw-flex tw-grow tw-basis-0",children:n.jsxs("div",{className:"tw-flex tw-items-center tw-gap-2",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:[l,t&&n.jsx(gw,{menuData:t,onOpenChange:e,onSelectMenuItem:r,variant:d})]})}),n.jsx("div",{className:"tw-flex tw-items-center tw-gap-2 tw-px-2",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:a}),n.jsx("div",{className:"tw-flex tw-min-w-0 tw-grow tw-basis-0 tw-justify-end",children:n.jsx("div",{className:"tw-flex tw-min-w-0 tw-items-center tw-gap-2 tw-pe-1",style:w?{WebkitAppRegion:"no-drag"}:void 0,children:c})})]})})}const vw=(t,e)=>t[e]??e;function yw({knownUiLanguages:t,primaryLanguage:e="en",fallbackLanguages:r=[],onLanguagesChange:o,onPrimaryLanguageChange:s,onFallbackLanguagesChange:a,localizedStrings:l,className:c,id:w}){const d=vw(l,"%settings_uiLanguageSelector_fallbackLanguages%"),[u,m]=i.useState(!1),h=g=>{s&&s(g),o&&o([g,...r.filter(y=>y!==g)]),a&&r.find(y=>y===g)&&a([...r.filter(y=>y!==g)]),m(!1)},p=(g,y)=>{var j,R,S,C,N,D;const v=y!==g?((R=(j=t[g])==null?void 0:j.uiNames)==null?void 0:R[y])??((C=(S=t[g])==null?void 0:S.uiNames)==null?void 0:C.en):void 0;return v?`${(N=t[g])==null?void 0:N.autonym} (${v})`:(D=t[g])==null?void 0:D.autonym};return n.jsxs("div",{id:w,className:f("pr-twp tw-max-w-sm",c),children:[n.jsxs(He,{name:"uiLanguage",value:e,onValueChange:h,open:u,onOpenChange:g=>m(g),children:[n.jsx(Oe,{children:n.jsx(Ue,{})}),n.jsx(Pe,{style:{zIndex:ln},children:Object.keys(t).map(g=>n.jsx(Xt,{value:g,children:p(g,e)},g))})]}),e!=="en"&&n.jsx("div",{className:"tw-pt-3",children:n.jsx(xt,{className:"tw-font-normal tw-text-muted-foreground",children:I.formatReplacementString(d,{fallbackLanguages:(r==null?void 0:r.length)>0?r.map(g=>p(g,e)).join(", "):t.en.autonym})})})]})}function jw({item:t,createLabel:e,createComplexLabel:r}){return e?n.jsx(xt,{children:e(t)}):r?n.jsx(xt,{children:r(t)}):n.jsx(xt,{children:t})}function Nw({id:t,className:e,listItems:r,selectedListItems:o,handleSelectListItem:s,createLabel:a,createComplexLabel:l}){return n.jsx("div",{id:t,className:e,children:r.map(c=>n.jsxs("div",{className:"tw-m-2 tw-flex tw-items-center",children:[n.jsx(wr,{className:"tw-me-2 tw-align-middle",checked:o.includes(c),onCheckedChange:w=>s(c,w)}),n.jsx(jw,{item:c,createLabel:a,createComplexLabel:l})]},c))})}function kw({scrRef:t,onClick:e,tooltipContent:r,ariaLabel:o,className:s,testId:a="linked-scr-ref-button"}){if(t==="")return;const l=n.jsx(G,{type:"button",variant:"link",onClick:e,disabled:!e,"aria-label":o,className:f("tw-h-auto tw-p-0 tw-text-start tw-font-mono tw-text-sm",s),"data-testid":a,children:t});return r?n.jsx(Ct,{delayDuration:0,children:n.jsxs(It,{children:[n.jsx(Mt,{asChild:!0,children:l}),n.jsx(St,{children:r})]})}):l}function _w({cardKey:t,isSelected:e,onSelect:r,isDenied:o,isHidden:s=!1,className:a,children:l,selectedButtons:c,hoverButtons:w,dropdownContent:d,additionalContent:u,accentColor:m,showDropdownOnHover:h=!1}){const p=g=>{(g.key==="Enter"||g.key===" ")&&(g.preventDefault(),r())};return n.jsxs("div",{hidden:s,onClick:r,onKeyDown:p,role:"button",tabIndex:0,"aria-pressed":e,className:f("tw-group tw-relative tw-min-w-36 tw-rounded-xl tw-border tw-shadow-none hover:tw-bg-muted/50",{"tw-opacity-50 hover:tw-opacity-100":o&&!e},{"tw-bg-accent":e},{"tw-bg-transparent":!e},a),children:[n.jsxs("div",{className:"tw-flex tw-flex-col tw-gap-2 tw-p-4",children:[n.jsxs("div",{className:"tw-flex tw-justify-between tw-overflow-hidden",children:[n.jsx("div",{className:"tw-min-w-0 tw-flex-1",children:l}),e&&c,!e&&w&&n.jsx("div",{className:"tw-invisible group-hover:tw-visible",children:w}),!e&&h&&d&&n.jsx("div",{className:"tw-invisible group-hover:tw-visible",children:n.jsxs(ie,{children:[n.jsx(ve,{className:f(m&&"tw-me-1"),asChild:!0,children:n.jsx(G,{className:"tw-m-1 tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(_.MoreVertical,{})})}),n.jsx(ee,{align:"end",children:d})]})}),e&&d&&n.jsxs(ie,{children:[n.jsx(ve,{className:f(m&&"tw-me-1"),asChild:!0,children:n.jsx(G,{className:"tw-m-1 tw-h-6 tw-w-6",variant:"ghost",size:"icon",children:n.jsx(_.MoreVertical,{})})}),n.jsx(ee,{align:"end",children:d})]})]}),u&&n.jsx("div",{className:"tw-w-fit tw-min-w-0 tw-max-w-full tw-overflow-hidden",children:u})]}),m&&n.jsx("div",{className:`tw-absolute tw-right-0 tw-top-0 tw-h-full tw-w-2 tw-rounded-r-xl ${m}`})]},t)}const Wa=i.forwardRef(({className:t,...e},r)=>n.jsx(_.LoaderCircle,{size:35,className:f("tw-animate-spin",t),...e,ref:r}));Wa.displayName="Spinner";function Cw({id:t,isDisabled:e=!1,hasError:r=!1,isFullWidth:o=!1,helperText:s,label:a,placeholder:l,isRequired:c=!1,className:w,defaultValue:d,value:u,onChange:m,onFocus:h,onBlur:p}){return n.jsxs("div",{className:f("tw-inline-grid tw-items-center tw-gap-1.5",{"tw-w-full":o}),children:[n.jsx(xt,{htmlFor:t,className:f({"tw-text-red-600":r,"tw-hidden":!a}),children:`${a}${c?"*":""}`}),n.jsx(Xe,{id:t,disabled:e,placeholder:l,required:c,className:f(w,{"tw-border-red-600":r}),defaultValue:d,value:u,onChange:m,onFocus:h,onBlur:p}),n.jsx("p",{className:f({"tw-hidden":!s}),children:s})]})}const Sw=Ae.cva("tw-relative tw-w-full tw-rounded-lg tw-border tw-p-4 [&>svg~*]:tw-pl-7 [&>svg+div]:tw-translate-y-[-3px] [&>svg]:tw-absolute [&>svg]:tw-left-4 [&>svg]:tw-top-4 [&>svg]:tw-text-foreground [&>img~*]:tw-pl-7 [&>img+div]:tw-translate-y-[-3px] [&>img]:tw-absolute [&>img]:tw-left-4 [&>img]:tw-top-4 [&>img]:tw-text-foreground",{variants:{variant:{default:"tw-bg-background tw-text-foreground",destructive:"tw-border-destructive/50 tw-text-destructive dark:tw-border-destructive [&>svg]:tw-text-destructive [&>img]:tw-text-destructive"}},defaultVariants:{variant:"default"}}),Za=i.forwardRef(({className:t,variant:e,...r},o)=>n.jsx("div",{ref:o,role:"alert",className:f("pr-twp",Sw({variant:e}),t),...r}));Za.displayName="Alert";const Ja=i.forwardRef(({className:t,...e},r)=>n.jsxs("h5",{ref:r,className:f("tw-mb-1 tw-font-medium tw-leading-none tw-tracking-tight",t),...e,children:[e.children," "]}));Ja.displayName="AlertTitle";const Qa=i.forwardRef(({className:t,...e},r)=>n.jsx("div",{ref:r,className:f("tw-text-sm [&_p]:tw-leading-relaxed",t),...e}));Qa.displayName="AlertDescription";const Ew=dt.Root,Rw=dt.Trigger,Tw=dt.Group,Dw=dt.Portal,Iw=dt.Sub,Mw=dt.RadioGroup,ti=i.forwardRef(({className:t,inset:e,children:r,...o},s)=>n.jsxs(dt.SubTrigger,{ref:s,className:f("pr-twp tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[state=open]:tw-bg-accent data-[state=open]:tw-text-accent-foreground",e&&"tw-pl-8",t),...o,children:[r,n.jsx(_.ChevronRight,{className:"tw-ml-auto tw-h-4 tw-w-4"})]}));ti.displayName=dt.SubTrigger.displayName;const ei=i.forwardRef(({className:t,...e},r)=>n.jsx(dt.SubContent,{ref:r,className:f("pr-twp tw-z-50 tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e}));ei.displayName=dt.SubContent.displayName;const ni=i.forwardRef(({className:t,...e},r)=>n.jsx(dt.Portal,{children:n.jsx(dt.Content,{ref:r,className:f("pr-twp tw-z-50 tw-max-h-[--radix-context-menu-content-available-height] tw-min-w-[8rem] tw-origin-[--radix-context-menu-content-transform-origin] tw-overflow-y-auto tw-overflow-x-hidden tw-rounded-md tw-border tw-bg-popover tw-p-1 tw-text-popover-foreground tw-shadow-md tw-animate-in tw-fade-in-80 data-[state=open]:tw-animate-in data-[state=closed]:tw-animate-out data-[state=closed]:tw-fade-out-0 data-[state=open]:tw-fade-in-0 data-[state=closed]:tw-zoom-out-95 data-[state=open]:tw-zoom-in-95 data-[side=bottom]:tw-slide-in-from-top-2 data-[side=left]:tw-slide-in-from-right-2 data-[side=right]:tw-slide-in-from-left-2 data-[side=top]:tw-slide-in-from-bottom-2",t),...e})}));ni.displayName=dt.Content.displayName;const ri=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(dt.Item,{ref:o,className:f("pr-twp tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-px-2 tw-py-1.5 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",e&&"tw-pl-8",t),...r}));ri.displayName=dt.Item.displayName;const oi=i.forwardRef(({className:t,children:e,checked:r,...o},s)=>n.jsxs(dt.CheckboxItem,{ref:s,className:f("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),checked:r,...o,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(dt.ItemIndicator,{children:n.jsx(_.Check,{className:"tw-h-4 tw-w-4"})})}),e]}));oi.displayName=dt.CheckboxItem.displayName;const si=i.forwardRef(({className:t,children:e,...r},o)=>n.jsxs(dt.RadioItem,{ref:o,className:f("tw-relative tw-flex tw-cursor-default tw-select-none tw-items-center tw-rounded-sm tw-py-1.5 tw-pl-8 tw-pr-2 tw-text-sm tw-outline-none focus:tw-bg-accent focus:tw-text-accent-foreground data-[disabled]:tw-pointer-events-none data-[disabled]:tw-opacity-50",t),...r,children:[n.jsx("span",{className:"tw-absolute tw-left-2 tw-flex tw-h-3.5 tw-w-3.5 tw-items-center tw-justify-center",children:n.jsx(dt.ItemIndicator,{children:n.jsx(_.Circle,{className:"tw-h-2 tw-w-2 tw-fill-current"})})}),e]}));si.displayName=dt.RadioItem.displayName;const ai=i.forwardRef(({className:t,inset:e,...r},o)=>n.jsx(dt.Label,{ref:o,className:f("tw-px-2 tw-py-1.5 tw-text-sm tw-font-semibold tw-text-foreground",e&&"tw-pl-8",t),...r}));ai.displayName=dt.Label.displayName;const ii=i.forwardRef(({className:t,...e},r)=>n.jsx(dt.Separator,{ref:r,className:f("tw--mx-1 tw-my-1 tw-h-px tw-bg-border",t),...e}));ii.displayName=dt.Separator.displayName;function li({className:t,...e}){return n.jsx("span",{className:f("tw-ml-auto tw-text-xs tw-tracking-widest tw-text-muted-foreground",t),...e})}li.displayName="ContextMenuShortcut";const ci=i.createContext({direction:"bottom"});function di({shouldScaleBackground:t=!0,direction:e="bottom",...r}){const o=i.useMemo(()=>({direction:e}),[e]);return n.jsx(ci.Provider,{value:o,children:n.jsx(le.Drawer.Root,{shouldScaleBackground:t,direction:e,...r})})}di.displayName="Drawer";const Ow=le.Drawer.Trigger,wi=le.Drawer.Portal,Pw=le.Drawer.Close,bo=i.forwardRef(({className:t,...e},r)=>n.jsx(le.Drawer.Overlay,{ref:r,className:f("tw-fixed tw-inset-0 tw-z-50 tw-bg-black/80",t),...e}));bo.displayName=le.Drawer.Overlay.displayName;const ui=i.forwardRef(({className:t,children:e,hideDrawerHandle:r=!1,...o},s)=>{const{direction:a="bottom"}=i.useContext(ci),l={bottom:"tw-inset-x-0 tw-bottom-0 tw-mt-24 tw-rounded-t-[10px]",top:"tw-inset-x-0 tw-top-0 tw-mb-24 tw-rounded-b-[10px]",left:"tw-inset-y-0 tw-left-0 tw-mr-24 tw-rounded-r-[10px] tw-w-auto tw-max-w-sm",right:"tw-inset-y-0 tw-right-0 tw-ml-24 tw-rounded-l-[10px] tw-w-auto tw-max-w-sm"},c={bottom:"tw-mx-auto tw-mt-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",top:"tw-mx-auto tw-mb-4 tw-h-2 tw-w-[100px] tw-rounded-full tw-bg-muted",left:"tw-my-auto tw-mr-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted",right:"tw-my-auto tw-ml-4 tw-w-2 tw-h-[100px] tw-rounded-full tw-bg-muted"};return n.jsxs(wi,{children:[n.jsx(bo,{}),n.jsxs(le.Drawer.Content,{ref:s,className:f("pr-twp tw-fixed tw-z-50 tw-flex tw-h-auto tw-border tw-bg-background",a==="bottom"||a==="top"?"tw-flex-col":"tw-flex-row",l[a],t),...o,children:[!r&&(a==="bottom"||a==="right")&&n.jsx("div",{className:c[a]}),n.jsx("div",{className:"tw-flex tw-flex-col",children:e}),!r&&(a==="top"||a==="left")&&n.jsx("div",{className:c[a]})]})]})});ui.displayName="DrawerContent";function pi({className:t,...e}){return n.jsx("div",{className:f("tw-grid tw-gap-1.5 tw-p-4 tw-text-center sm:tw-text-left",t),...e})}pi.displayName="DrawerHeader";function mi({className:t,...e}){return n.jsx("div",{className:f("tw-mt-auto tw-flex tw-flex-col tw-gap-2 tw-p-4",t),...e})}mi.displayName="DrawerFooter";const fi=i.forwardRef(({className:t,...e},r)=>n.jsx(le.Drawer.Title,{ref:r,className:f("tw-text-lg tw-font-semibold tw-leading-none tw-tracking-tight",t),...e}));fi.displayName=le.Drawer.Title.displayName;const hi=i.forwardRef(({className:t,...e},r)=>n.jsx(le.Drawer.Description,{ref:r,className:f("tw-text-sm tw-text-muted-foreground",t),...e}));hi.displayName=le.Drawer.Description.displayName;const gi=i.forwardRef(({className:t,value:e,...r},o)=>n.jsx(Rr.Root,{ref:o,className:f("pr-twp tw-relative tw-h-4 tw-w-full tw-overflow-hidden tw-rounded-full tw-bg-secondary",t),...r,children:n.jsx(Rr.Indicator,{className:"tw-h-full tw-w-full tw-flex-1 tw-bg-primary tw-transition-all",style:{transform:`translateX(-${100-(e||0)}%)`}})}));gi.displayName=Rr.Root.displayName;function Aw({className:t,...e}){return n.jsx(Pr.PanelGroup,{className:f("tw-flex tw-h-full tw-w-full data-[panel-group-direction=vertical]:tw-flex-col",t),...e})}const $w=Pr.Panel;function Lw({withHandle:t,className:e,...r}){return n.jsx(Pr.PanelResizeHandle,{className:f("tw-relative tw-flex tw-w-px tw-items-center tw-justify-center tw-bg-border after:tw-absolute after:tw-inset-y-0 after:tw-left-1/2 after:tw-w-1 after:tw--translate-x-1/2 focus-visible:tw-outline-none focus-visible:tw-ring-1 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-1 data-[panel-group-direction=vertical]:tw-h-px data-[panel-group-direction=vertical]:tw-w-full data-[panel-group-direction=vertical]:after:tw-left-0 data-[panel-group-direction=vertical]:after:tw-h-1 data-[panel-group-direction=vertical]:after:tw-w-full data-[panel-group-direction=vertical]:after:tw--translate-y-1/2 data-[panel-group-direction=vertical]:after:tw-translate-x-0 [&[data-panel-group-direction=vertical]>div]:tw-rotate-90",e),...r,children:t&&n.jsx("div",{className:"tw-z-10 tw-flex tw-h-4 tw-w-3 tw-items-center tw-justify-center tw-rounded-sm tw-border tw-bg-border",children:n.jsx(_.GripVertical,{className:"tw-h-2.5 tw-w-2.5"})})})}function Vw({...t}){return n.jsx(Qo.Toaster,{className:"tw-toaster tw-group",toastOptions:{classNames:{toast:"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",description:"group-[.toast]:text-muted-foreground",actionButton:"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",cancelButton:"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground"}},...t})}const xi=i.forwardRef(({className:t,...e},r)=>{const o=bt();return n.jsxs(xn.Root,{ref:r,className:f("pr-twp tw-relative tw-flex tw-w-full tw-touch-none tw-select-none tw-items-center",t),...e,dir:o,children:[n.jsx(xn.Track,{className:"tw-relative tw-h-2 tw-w-full tw-grow tw-overflow-hidden tw-rounded-full tw-bg-secondary",children:n.jsx(xn.Range,{className:"tw-absolute tw-h-full tw-bg-primary"})}),n.jsx(xn.Thumb,{className:"tw-block tw-h-5 tw-w-5 tw-rounded-full tw-border-2 tw-border-primary tw-bg-background tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50"})]})});xi.displayName=xn.Root.displayName;const bi=i.forwardRef(({className:t,...e},r)=>{const o=bt();return n.jsx(Tr.Root,{className:f("tw-peer pr-twp tw-inline-flex tw-h-6 tw-w-11 tw-shrink-0 tw-cursor-pointer tw-items-center tw-rounded-full tw-border-2 tw-border-transparent tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 focus-visible:tw-ring-offset-background disabled:tw-cursor-not-allowed disabled:tw-opacity-50 data-[state=checked]:tw-bg-primary data-[state=unchecked]:tw-bg-input",t),...e,ref:r,children:n.jsx(Tr.Thumb,{className:f("pr-twp tw-pointer-events-none tw-block tw-h-5 tw-w-5 tw-rounded-full tw-bg-background tw-shadow-lg tw-ring-0 tw-transition-transform",{"data-[state=checked]:tw-translate-x-5 data-[state=unchecked]:tw-translate-x-0":o==="ltr"},{"data-[state=checked]:tw-translate-x-[-20px] data-[state=unchecked]:tw-translate-x-0":o==="rtl"})})})});bi.displayName=Tr.Root.displayName;const Bw=Kt.Root,vi=i.forwardRef(({className:t,...e},r)=>{const o=bt();return n.jsx(Kt.List,{ref:r,className:f("pr-twp tw-inline-flex tw-h-10 tw-items-center tw-justify-center tw-rounded-md tw-bg-muted tw-p-1 tw-text-muted-foreground",t),...e,dir:o})});vi.displayName=Kt.List.displayName;const yi=i.forwardRef(({className:t,...e},r)=>n.jsx(Kt.Trigger,{ref:r,className:f("pr-twp tw-inline-flex tw-items-center tw-justify-center tw-whitespace-nowrap tw-rounded-sm tw-px-3 tw-py-1.5 tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-all hover:tw-text-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50 data-[state=active]:tw-bg-background data-[state=active]:tw-text-foreground data-[state=active]:tw-shadow-sm",t),...e}));yi.displayName=Kt.Trigger.displayName;const ji=i.forwardRef(({className:t,...e},r)=>n.jsx(Kt.Content,{ref:r,className:f("pr-twp tw-mt-2 tw-ring-offset-background focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2",t),...e}));ji.displayName=Kt.Content.displayName;const Ni=i.forwardRef(({className:t,...e},r)=>n.jsx("textarea",{className:f("pr-twp tw-flex tw-min-h-[80px] tw-w-full tw-rounded-md tw-border tw-border-input tw-bg-background tw-px-3 tw-py-2 tw-text-base tw-ring-offset-background placeholder:tw-text-muted-foreground focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-cursor-not-allowed disabled:tw-opacity-50 md:tw-text-sm",t),ref:r,...e}));Ni.displayName="Textarea";const Fw=(t,e)=>{i.useEffect(()=>{if(!t)return()=>{};const r=t(e);return()=>{r()}},[t,e])};function Gw(t){return{preserveValue:!0,...t}}const ki=(t,e,r={})=>{const o=i.useRef(e);o.current=e;const s=i.useRef(r);s.current=Gw(s.current);const[a,l]=i.useState(()=>o.current),[c,w]=i.useState(!0);return i.useEffect(()=>{let d=!0;return w(!!t),(async()=>{if(t){const u=await t();d&&(l(()=>u),w(!1))}})(),()=>{d=!1,s.current.preserveValue||l(()=>o.current)}},[t]),[a,c]},Cr=()=>!1,zw=(t,e)=>{const[r]=ki(i.useCallback(async()=>{if(!t)return Cr;const o=await Promise.resolve(t(e));return async()=>o()},[e,t]),Cr,{preserveValue:!1});i.useEffect(()=>()=>{r!==Cr&&r()},[r])};function qw(t){i.useEffect(()=>{let e;return t&&(e=document.createElement("style"),e.appendChild(document.createTextNode(t)),document.head.appendChild(e)),()=>{e&&document.head.removeChild(e)}},[t])}Object.defineProperty(exports,"sonner",{enumerable:!0,get:()=>Qo.toast});exports.Alert=Za;exports.AlertDescription=Qa;exports.AlertTitle=Ja;exports.Avatar=Zr;exports.AvatarFallback=Jr;exports.AvatarImage=Ws;exports.BOOK_CHAPTER_CONTROL_STRING_KEYS=ul;exports.BOOK_SELECTOR_STRING_KEYS=fl;exports.Badge=ae;exports.BookChapterControl=zn;exports.BookSelector=hl;exports.Button=G;exports.COMMENT_EDITOR_STRING_KEYS=Lc;exports.COMMENT_LIST_STRING_KEYS=Vc;exports.Card=Xr;exports.CardContent=Wr;exports.CardDescription=Ys;exports.CardFooter=Xs;exports.CardHeader=Hs;exports.CardTitle=Us;exports.ChapterRangeSelector=gs;exports.Checkbox=wr;exports.Checklist=Nw;exports.ComboBox=Dr;exports.Command=ce;exports.CommandEmpty=Ye;exports.CommandGroup=te;exports.CommandInput=$e;exports.CommandItem=ne;exports.CommandList=de;exports.CommentEditor=$c;exports.CommentList=zc;exports.ContextMenu=Ew;exports.ContextMenuCheckboxItem=oi;exports.ContextMenuContent=ni;exports.ContextMenuGroup=Tw;exports.ContextMenuItem=ri;exports.ContextMenuLabel=ai;exports.ContextMenuPortal=Dw;exports.ContextMenuRadioGroup=Mw;exports.ContextMenuRadioItem=si;exports.ContextMenuSeparator=ii;exports.ContextMenuShortcut=li;exports.ContextMenuSub=Iw;exports.ContextMenuSubContent=ei;exports.ContextMenuSubTrigger=ti;exports.ContextMenuTrigger=Rw;exports.DataTable=ia;exports.Dialog=Hn;exports.DialogClose=ol;exports.DialogContent=yn;exports.DialogDescription=is;exports.DialogFooter=Un;exports.DialogHeader=jn;exports.DialogOverlay=$r;exports.DialogPortal=as;exports.DialogTitle=Nn;exports.DialogTrigger=rl;exports.Drawer=di;exports.DrawerClose=Pw;exports.DrawerContent=ui;exports.DrawerDescription=hi;exports.DrawerFooter=mi;exports.DrawerHeader=pi;exports.DrawerOverlay=bo;exports.DrawerPortal=wi;exports.DrawerTitle=fi;exports.DrawerTrigger=Ow;exports.DropdownMenu=ie;exports.DropdownMenuCheckboxItem=Qt;exports.DropdownMenuContent=ee;exports.DropdownMenuGroup=to;exports.DropdownMenuItem=ke;exports.DropdownMenuItemType=wa;exports.DropdownMenuLabel=Ce;exports.DropdownMenuPortal=Zs;exports.DropdownMenuRadioGroup=Qs;exports.DropdownMenuRadioItem=ro;exports.DropdownMenuSeparator=ye;exports.DropdownMenuShortcut=ta;exports.DropdownMenuSub=Js;exports.DropdownMenuSubContent=no;exports.DropdownMenuSubTrigger=eo;exports.DropdownMenuTrigger=ve;exports.ERROR_DUMP_STRING_KEYS=ca;exports.ERROR_POPOVER_STRING_KEYS=ad;exports.EditorKeyboardShortcuts=fa;exports.ErrorDump=da;exports.ErrorPopover=id;exports.FOOTNOTE_EDITOR_STRING_KEYS=kd;exports.Filter=ud;exports.FilterDropdown=ld;exports.Footer=wd;exports.FootnoteEditor=Nd;exports.FootnoteItem=ba;exports.FootnoteList=Sd;exports.INVENTORY_STRING_KEYS=Ld;exports.Input=Xe;exports.Inventory=Fd;exports.Label=xt;exports.LinkedScrRefButton=kw;exports.MARKER_MENU_STRING_KEYS=ha;exports.MarkdownRenderer=sd;exports.MarkerMenu=ga;exports.MoreInfo=cd;exports.MultiSelectComboBox=ua;exports.NavigationContentSearch=ww;exports.Popover=we;exports.PopoverAnchor=us;exports.PopoverContent=re;exports.PopoverPortalContainerProvider=Fn;exports.PopoverTrigger=je;exports.Progress=gi;exports.ProjectSelector=la;exports.RadioGroup=lr;exports.RadioGroupItem=kn;exports.RecentSearches=fs;exports.ResizableHandle=Lw;exports.ResizablePanel=$w;exports.ResizablePanelGroup=Aw;exports.ResultsCard=_w;exports.SCOPE_SELECTOR_STRING_KEYS=nw;exports.ScopeSelector=ow;exports.ScriptureResultsViewer=Qd;exports.ScrollGroupSelector=sw;exports.SearchBar=ur;exports.Select=He;exports.SelectContent=Pe;exports.SelectGroup=ea;exports.SelectItem=Xt;exports.SelectLabel=ra;exports.SelectScrollDownButton=so;exports.SelectScrollUpButton=oo;exports.SelectSeparator=oa;exports.SelectTrigger=Oe;exports.SelectValue=Ue;exports.Separator=Ke;exports.SettingsList=aw;exports.SettingsListHeader=lw;exports.SettingsListItem=iw;exports.SettingsSidebar=Pa;exports.SettingsSidebarContentSearch=Kd;exports.Sidebar=lo;exports.SidebarContent=wo;exports.SidebarFooter=Ca;exports.SidebarGroup=tr;exports.SidebarGroupAction=Ea;exports.SidebarGroupContent=nr;exports.SidebarGroupLabel=er;exports.SidebarHeader=_a;exports.SidebarInput=ka;exports.SidebarInset=co;exports.SidebarMenu=uo;exports.SidebarMenuAction=Ra;exports.SidebarMenuBadge=Ta;exports.SidebarMenuButton=mo;exports.SidebarMenuItem=po;exports.SidebarMenuSkeleton=Da;exports.SidebarMenuSub=Ia;exports.SidebarMenuSubButton=Oa;exports.SidebarMenuSubItem=Ma;exports.SidebarProvider=io;exports.SidebarRail=Na;exports.SidebarSeparator=Sa;exports.SidebarTrigger=ja;exports.Skeleton=Qn;exports.Slider=xi;exports.Sonner=Vw;exports.Spinner=Wa;exports.Switch=bi;exports.TabDropdownMenu=or;exports.TabFloatingMenu=dw;exports.TabToolbar=cw;exports.Table=En;exports.TableBody=Tn;exports.TableCaption=aa;exports.TableCell=Me;exports.TableFooter=sa;exports.TableHead=on;exports.TableHeader=Rn;exports.TableRow=xe;exports.Tabs=Bw;exports.TabsContent=ji;exports.TabsList=vi;exports.TabsTrigger=yi;exports.TextField=Cw;exports.Textarea=Ni;exports.ToggleGroup=dr;exports.ToggleGroupItem=en;exports.Toolbar=bw;exports.Tooltip=It;exports.TooltipContent=St;exports.TooltipProvider=Ct;exports.TooltipTrigger=Mt;exports.UNDO_REDO_BUTTONS_STRING_KEYS=pa;exports.UiLanguageSelector=yw;exports.UndoRedoButtons=ma;exports.VerticalTabs=ho;exports.VerticalTabsContent=xo;exports.VerticalTabsList=go;exports.VerticalTabsTrigger=Fa;exports.Z_INDEX_ABOVE_DOCK=ln;exports.Z_INDEX_FOOTNOTE_EDITOR=Ar;exports.Z_INDEX_MODAL=os;exports.Z_INDEX_MODAL_BACKDROP=rs;exports.Z_INDEX_OVERLAY=ar;exports.Z_INDEX_TOOLTIP=ss;exports.badgeVariants=Ks;exports.buttonVariants=Br;exports.cn=f;exports.getBookIdFromUSFM=$d;exports.getInventoryHeader=Dn;exports.getLinesFromUSFM=Pd;exports.getNumberFromUSFM=Ad;exports.getStatusForItem=va;exports.getToolbarOSReservedSpaceClassName=xw;exports.inventoryCountColumn=Md;exports.inventoryItemColumn=Dd;exports.inventoryStatusColumn=Od;exports.selectTriggerVariants=na;exports.useEvent=Fw;exports.useEventAsync=zw;exports.useListbox=qs;exports.usePromise=ki;exports.useRecentSearches=al;exports.useSidebar=In;exports.useStylesheet=qw;function Kw(t,e="top"){if(!t||typeof document>"u")return;const r=document.head||document.querySelector("head"),o=r.querySelector(":first-child"),s=document.createElement("style");s.appendChild(document.createTextNode(t)),e==="top"&&o?r.insertBefore(s,o):r.appendChild(s)}Kw(`*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +}/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*:where(.pr-twp,.pr-twp *), +::before:where(.pr-twp,.pr-twp *), +::after:where(.pr-twp,.pr-twp *) { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: #e5e7eb; /* 2 */ +} + +::before:where(.pr-twp,.pr-twp *), +::after:where(.pr-twp,.pr-twp *) { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured \`sans\` font-family by default. +5. Use the user's configured \`sans\` font-feature-settings by default. +6. Use the user's configured \`sans\` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html:where(.pr-twp,.pr-twp *), +:host:where(.pr-twp,.pr-twp *) { + line-height: 1.5; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ /* 3 */ + tab-size: 4; /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ + font-feature-settings: normal; /* 5 */ + font-variation-settings: normal; /* 6 */ + -webkit-tap-highlight-color: transparent; /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from \`html\` so users can set them as a class directly on the \`html\` element. +*/ + +body:where(.pr-twp,.pr-twp *) { + margin: 0; /* 1 */ + line-height: inherit; /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr:where(.pr-twp,.pr-twp *) { + height: 0; /* 1 */ + color: inherit; /* 2 */ + border-top-width: 1px; /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]):where(.pr-twp,.pr-twp *) { + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1:where(.pr-twp,.pr-twp *), +h2:where(.pr-twp,.pr-twp *), +h3:where(.pr-twp,.pr-twp *), +h4:where(.pr-twp,.pr-twp *), +h5:where(.pr-twp,.pr-twp *), +h6:where(.pr-twp,.pr-twp *) { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a:where(.pr-twp,.pr-twp *) { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b:where(.pr-twp,.pr-twp *), +strong:where(.pr-twp,.pr-twp *) { + font-weight: bolder; +} + +/* +1. Use the user's configured \`mono\` font-family by default. +2. Use the user's configured \`mono\` font-feature-settings by default. +3. Use the user's configured \`mono\` font-variation-settings by default. +4. Correct the odd \`em\` font sizing in all browsers. +*/ + +code:where(.pr-twp,.pr-twp *), +kbd:where(.pr-twp,.pr-twp *), +samp:where(.pr-twp,.pr-twp *), +pre:where(.pr-twp,.pr-twp *) { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */ + font-feature-settings: normal; /* 2 */ + font-variation-settings: normal; /* 3 */ + font-size: 1em; /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small:where(.pr-twp,.pr-twp *) { + font-size: 80%; +} + +/* +Prevent \`sub\` and \`sup\` elements from affecting the line height in all browsers. +*/ + +sub:where(.pr-twp,.pr-twp *), +sup:where(.pr-twp,.pr-twp *) { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub:where(.pr-twp,.pr-twp *) { + bottom: -0.25em; +} + +sup:where(.pr-twp,.pr-twp *) { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table:where(.pr-twp,.pr-twp *) { + text-indent: 0; /* 1 */ + border-color: inherit; /* 2 */ + border-collapse: collapse; /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button:where(.pr-twp,.pr-twp *), +input:where(.pr-twp,.pr-twp *), +optgroup:where(.pr-twp,.pr-twp *), +select:where(.pr-twp,.pr-twp *), +textarea:where(.pr-twp,.pr-twp *) { + font-family: inherit; /* 1 */ + font-feature-settings: inherit; /* 1 */ + font-variation-settings: inherit; /* 1 */ + font-size: 100%; /* 1 */ + font-weight: inherit; /* 1 */ + line-height: inherit; /* 1 */ + letter-spacing: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button:where(.pr-twp,.pr-twp *), +select:where(.pr-twp,.pr-twp *) { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button:where(.pr-twp,.pr-twp *), +input:where([type='button']):where(.pr-twp,.pr-twp *), +input:where([type='reset']):where(.pr-twp,.pr-twp *), +input:where([type='submit']):where(.pr-twp,.pr-twp *) { + -webkit-appearance: button; /* 1 */ + background-color: transparent; /* 2 */ + background-image: none; /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring:where(.pr-twp,.pr-twp *) { + outline: auto; +} + +/* +Remove the additional \`:invalid\` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid:where(.pr-twp,.pr-twp *) { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress:where(.pr-twp,.pr-twp *) { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button:where(.pr-twp,.pr-twp *), +::-webkit-outer-spin-button:where(.pr-twp,.pr-twp *) { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search']:where(.pr-twp,.pr-twp *) { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration:where(.pr-twp,.pr-twp *) { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to \`inherit\` in Safari. +*/ + +::-webkit-file-upload-button:where(.pr-twp,.pr-twp *) { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary:where(.pr-twp,.pr-twp *) { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote:where(.pr-twp,.pr-twp *), +dl:where(.pr-twp,.pr-twp *), +dd:where(.pr-twp,.pr-twp *), +h1:where(.pr-twp,.pr-twp *), +h2:where(.pr-twp,.pr-twp *), +h3:where(.pr-twp,.pr-twp *), +h4:where(.pr-twp,.pr-twp *), +h5:where(.pr-twp,.pr-twp *), +h6:where(.pr-twp,.pr-twp *), +hr:where(.pr-twp,.pr-twp *), +figure:where(.pr-twp,.pr-twp *), +p:where(.pr-twp,.pr-twp *), +pre:where(.pr-twp,.pr-twp *) { + margin: 0; +} + +fieldset:where(.pr-twp,.pr-twp *) { + margin: 0; + padding: 0; +} + +legend:where(.pr-twp,.pr-twp *) { + padding: 0; +} + +ol:where(.pr-twp,.pr-twp *), +ul:where(.pr-twp,.pr-twp *), +menu:where(.pr-twp,.pr-twp *) { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ +dialog:where(.pr-twp,.pr-twp *) { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea:where(.pr-twp,.pr-twp *) { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::placeholder:where(.pr-twp,.pr-twp *), +textarea::placeholder:where(.pr-twp,.pr-twp *) { + opacity: 1; /* 1 */ + color: #9ca3af; /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button:where(.pr-twp,.pr-twp *), +[role="button"]:where(.pr-twp,.pr-twp *) { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ +:disabled:where(.pr-twp,.pr-twp *) { + cursor: default; +} + +/* +1. Make replaced elements \`display: block\` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add \`vertical-align: middle\` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img:where(.pr-twp,.pr-twp *), +svg:where(.pr-twp,.pr-twp *), +video:where(.pr-twp,.pr-twp *), +canvas:where(.pr-twp,.pr-twp *), +audio:where(.pr-twp,.pr-twp *), +iframe:where(.pr-twp,.pr-twp *), +embed:where(.pr-twp,.pr-twp *), +object:where(.pr-twp,.pr-twp *) { + display: block; /* 1 */ + vertical-align: middle; /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img:where(.pr-twp,.pr-twp *), +video:where(.pr-twp,.pr-twp *) { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ +[hidden]:where(:not([hidden="until-found"])):where(.pr-twp,.pr-twp *) { + display: none; +} + /* Adding the preflight selector (pr-twp) to components was not changing the font as desired. + So this piece of code adds tw-font-sans everywhere we include preflight. */ + .pr-twp { + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + @font-face { + font-family: 'Inter'; + font-display: 'swap'; + src: url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); + } + + /** + * Theme colors and other CSS variable properties in Platform.Bible. These are applied in CSS + * properties using \`hsl(var(--variableName))\` or Tailwind classes like \`tw-bg-primary\` + * + * See the wiki's [Matching Application + * Theme](https://github.com/paranext/paranext-extension-template/wiki/Extension-Anatomy#matching-application-theme) + * section for more information + */ + /* ["Slate" base theme by shadcn/ui](https://ui.shadcn.com/docs/theming#slate) */ + :root { + --background: 0 0% 100%; /* white */ + --foreground: 222.2 84% 4.9%; /* slate-950 */ + --card: 0 0% 100%; /* white */ + --card-foreground: 222.2 84% 4.9%; /* slate-950 */ + --popover: 210 20% 98%; /* popover platform */ + --popover-foreground: 222.2 84% 4.9%; /* slate-950 */ + --primary: 222.2 47.4% 11.2%; /* slate-900 */ + --primary-foreground: 210 40% 98%; /* slate-50 */ + --secondary: 210 50% 95%; + --secondary-foreground: 222.2 47.4% 11.2%; /* slate-900 */ + --muted: 210 50% 95%; + --muted-foreground: 215.4 16.3% 46.9%; /* slate-500 */ + --accent: 210 50% 95%; + --accent-foreground: 222.2 47.4% 11.2%; /* slate-900 */ + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; /* slate-50 */ + --border: 214.3 31.8% 91.4%; /* slate-200 */ + --input: 214.3 31.8% 91.4%; /* slate-200 */ + --ring: 222.2 84% 4.9%; /* slate-950 */ + + --sidebar-background: 210 20% 98%; /* popover platform */ + --sidebar-foreground: 222.2 84% 4.9%; /* slate-950 */ + --sidebar-primary: 222.2 47.4% 11.2%; /* slate-900 */ + --sidebar-primary-foreground: 210 40% 98%; /* slate-50 */ + --sidebar-accent: 210 50% 95%; + --sidebar-accent-foreground: 222.2 47.4% 11.2%; /* slate-900 */ + --sidebar-border: 214.3 31.8% 91.4%; /* slate-200 */ + --sidebar-ring: 222.2 84% 4.9%; /* slate-950 */ + + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; /* slate-950 */ + --foreground: 210 40% 98%; /* slate-50 */ + --card: 222.2 84% 4.9%; /* slate-950 */ + --card-foreground: 210 40% 98%; /* slate-50 */ + --popover: 222.2 84% 4.9%; /* slate-950 */ + --popover-foreground: 210 40% 98%; /* slate-50 */ + --primary: 210 40% 98%; /* slate-50 */ + --primary-foreground: 222.2 47.4% 11.2%; /* slate-900 */ + --secondary: 217.2 32.6% 17.5%; /* slate-800 */ + --secondary-foreground: 210 40% 98%; /* slate-50 */ + --muted: 217.2 32.6% 17.5%; /* slate-800 */ + --muted-foreground: 215 20.2% 65.1%; /* slate-400 */ + --accent: 217.2 32.6% 17.5%; /* slate-800 */ + --accent-foreground: 210 40% 98%; /* slate-50 */ + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; /* slate-50 */ + --border: 215.3 19.3% 34.5%; /* slate-600 */ + --input: 215.3 19.3% 34.5%; /* slate-600 */ + --ring: 212.7 26.8% 83.9%; /* slate-300 */ + + --sidebar-background: 222.2 84% 4.9%; /* slate-950 */ + --sidebar-foreground: 215 20.2% 65.1%; /* slate-400 */ + --sidebar-primary: 210 40% 98%; /* slate-50 */ + --sidebar-primary-foreground: 222.2 47.4% 11.2%; /* slate-900 */ + --sidebar-accent: 217.2 32.6% 17.5%; /* slate-800 */ + --sidebar-accent-foreground: 215 20.2% 65.1%; /* slate-400 */ + --sidebar-border: 217.2 32.6% 17.5%; /* slate-800 */ + --sidebar-ring: 212.7 26.8% 83.9%; /* slate-300 */ + } + + /* Palette built in https://tweakcn.com/themes/cmeukcpoj000204l45lxw5a74 based on "Caffeine" theme*/ + .pr-twp, + .pr-twp * { + border-color: hsl(var(--border)); + outline-color: hsl(var(--ring) / 0.5); +} + + /** + * disabled because tslint does not like it, but it is the selector that's needed + */ + /* stylelint-disable-next-line selector-no-qualifying-type */ + body.pr-twp { + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); +} +.tw-prose { + color: var(--tw-prose-body); + max-width: 65ch; +} +.tw-prose :where(p):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} +.tw-prose :where([class~="lead"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-lead); + font-size: 1.25em; + line-height: 1.6; + margin-top: 1.2em; + margin-bottom: 1.2em; +} +.tw-prose :where(a):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-links); + text-decoration: underline; + font-weight: 500; +} +.tw-prose :where(strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-bold); + font-weight: 600; +} +.tw-prose :where(a strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(blockquote strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(thead th strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(ol):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: decimal; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-inline-start: 1.625em; +} +.tw-prose :where(ol[type="A"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: upper-alpha; +} +.tw-prose :where(ol[type="a"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: lower-alpha; +} +.tw-prose :where(ol[type="A" s]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: upper-alpha; +} +.tw-prose :where(ol[type="a" s]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: lower-alpha; +} +.tw-prose :where(ol[type="I"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: upper-roman; +} +.tw-prose :where(ol[type="i"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: lower-roman; +} +.tw-prose :where(ol[type="I" s]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: upper-roman; +} +.tw-prose :where(ol[type="i" s]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: lower-roman; +} +.tw-prose :where(ol[type="1"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: decimal; +} +.tw-prose :where(ul):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + list-style-type: disc; + margin-top: 1.25em; + margin-bottom: 1.25em; + padding-inline-start: 1.625em; +} +.tw-prose :where(ol > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::marker { + font-weight: 400; + color: var(--tw-prose-counters); +} +.tw-prose :where(ul > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::marker { + color: var(--tw-prose-bullets); +} +.tw-prose :where(dt):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.25em; +} +.tw-prose :where(hr):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + border-color: var(--tw-prose-hr); + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} +.tw-prose :where(blockquote):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 500; + font-style: italic; + color: var(--tw-prose-quotes); + border-inline-start-width: 0.25rem; + border-inline-start-color: var(--tw-prose-quote-borders); + quotes: "“""”""‘""’"; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-inline-start: 1em; +} +.tw-prose :where(blockquote p:first-of-type):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::before { + content: open-quote; +} +.tw-prose :where(blockquote p:last-of-type):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::after { + content: close-quote; +} +.tw-prose :where(h1):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 800; + font-size: 2.25em; + margin-top: 0; + margin-bottom: 0.8888889em; + line-height: 1.1111111; +} +.tw-prose :where(h1 strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 900; + color: inherit; +} +.tw-prose :where(h2):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 1.5em; + margin-top: 2em; + margin-bottom: 1em; + line-height: 1.3333333; +} +.tw-prose :where(h2 strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 800; + color: inherit; +} +.tw-prose :where(h3):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; +} +.tw-prose :where(h3 strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 700; + color: inherit; +} +.tw-prose :where(h4):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; +} +.tw-prose :where(h4 strong):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 700; + color: inherit; +} +.tw-prose :where(img):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} +.tw-prose :where(picture):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + display: block; + margin-top: 2em; + margin-bottom: 2em; +} +.tw-prose :where(video):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} +.tw-prose :where(kbd):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-weight: 500; + font-family: inherit; + color: var(--tw-prose-kbd); + box-shadow: 0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%); + font-size: 0.875em; + border-radius: 0.3125rem; + padding-top: 0.1875em; + padding-inline-end: 0.375em; + padding-bottom: 0.1875em; + padding-inline-start: 0.375em; +} +.tw-prose :where(code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-code); + font-weight: 600; + font-size: 0.875em; +} +.tw-prose :where(code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::before { + content: "\`"; +} +.tw-prose :where(code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::after { + content: "\`"; +} +.tw-prose :where(a code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(h1 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(h2 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; + font-size: 0.875em; +} +.tw-prose :where(h3 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; + font-size: 0.9em; +} +.tw-prose :where(h4 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(blockquote code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(thead th code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: inherit; +} +.tw-prose :where(pre):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-pre-code); + background-color: var(--tw-prose-pre-bg); + overflow-x: auto; + font-weight: 400; + font-size: 0.875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: 0.375rem; + padding-top: 0.8571429em; + padding-inline-end: 1.1428571em; + padding-bottom: 0.8571429em; + padding-inline-start: 1.1428571em; +} +.tw-prose :where(pre code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + background-color: transparent; + border-width: 0; + border-radius: 0; + padding: 0; + font-weight: inherit; + color: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} +.tw-prose :where(pre code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::before { + content: none; +} +.tw-prose :where(pre code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::after { + content: none; +} +.tw-prose :where(table):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + width: 100%; + table-layout: auto; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} +.tw-prose :where(thead):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-th-borders); +} +.tw-prose :where(thead th):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + vertical-align: bottom; + padding-inline-end: 0.5714286em; + padding-bottom: 0.5714286em; + padding-inline-start: 0.5714286em; +} +.tw-prose :where(tbody tr):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-td-borders); +} +.tw-prose :where(tbody tr:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + border-bottom-width: 0; +} +.tw-prose :where(tbody td):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + vertical-align: baseline; +} +.tw-prose :where(tfoot):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + border-top-width: 1px; + border-top-color: var(--tw-prose-th-borders); +} +.tw-prose :where(tfoot td):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + vertical-align: top; +} +.tw-prose :where(th, td):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + text-align: start; +} +.tw-prose :where(figure > *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} +.tw-prose :where(figcaption):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + color: var(--tw-prose-captions); + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; +} +.tw-prose { + --tw-prose-body: hsl(var(--foreground)); + --tw-prose-headings: hsl(var(--foreground)); + --tw-prose-lead: hsl(var(--muted-foreground)); + --tw-prose-links: hsl(var(--primary)); + --tw-prose-bold: hsl(var(--foreground)); + --tw-prose-counters: hsl(var(--muted-foreground)); + --tw-prose-bullets: hsl(var(--muted-foreground)); + --tw-prose-hr: hsl(var(--border)); + --tw-prose-quotes: hsl(var(--foreground)); + --tw-prose-quote-borders: hsl(var(--border)); + --tw-prose-captions: hsl(var(--muted-foreground)); + --tw-prose-kbd: #111827; + --tw-prose-kbd-shadows: 17 24 39; + --tw-prose-code: hsl(var(--foreground)); + --tw-prose-pre-code: hsl(var(--muted-foreground)); + --tw-prose-pre-bg: hsl(var(--muted)); + --tw-prose-th-borders: hsl(var(--border)); + --tw-prose-td-borders: hsl(var(--border)); + --tw-prose-invert-body: #d1d5db; + --tw-prose-invert-headings: #fff; + --tw-prose-invert-lead: #9ca3af; + --tw-prose-invert-links: #fff; + --tw-prose-invert-bold: #fff; + --tw-prose-invert-counters: #9ca3af; + --tw-prose-invert-bullets: #4b5563; + --tw-prose-invert-hr: #374151; + --tw-prose-invert-quotes: #f3f4f6; + --tw-prose-invert-quote-borders: #374151; + --tw-prose-invert-captions: #9ca3af; + --tw-prose-invert-kbd: #fff; + --tw-prose-invert-kbd-shadows: 255 255 255; + --tw-prose-invert-code: #fff; + --tw-prose-invert-pre-code: #d1d5db; + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); + --tw-prose-invert-th-borders: #4b5563; + --tw-prose-invert-td-borders: #374151; + font-size: 1rem; + line-height: 1.75; +} +.tw-prose :where(picture > img):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} +.tw-prose :where(li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.tw-prose :where(ol > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0.375em; +} +.tw-prose :where(ul > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0.375em; +} +.tw-prose :where(.tw-prose > ul > li p):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} +.tw-prose :where(.tw-prose > ul > li > p:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.25em; +} +.tw-prose :where(.tw-prose > ul > li > p:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 1.25em; +} +.tw-prose :where(.tw-prose > ol > li > p:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.25em; +} +.tw-prose :where(.tw-prose > ol > li > p:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 1.25em; +} +.tw-prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} +.tw-prose :where(dl):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} +.tw-prose :where(dd):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.5em; + padding-inline-start: 1.625em; +} +.tw-prose :where(hr + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose :where(h2 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose :where(h3 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose :where(h4 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose :where(thead th:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0; +} +.tw-prose :where(thead th:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-end: 0; +} +.tw-prose :where(tbody td, tfoot td):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-top: 0.5714286em; + padding-inline-end: 0.5714286em; + padding-bottom: 0.5714286em; + padding-inline-start: 0.5714286em; +} +.tw-prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0; +} +.tw-prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-end: 0; +} +.tw-prose :where(figure):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} +.tw-prose :where(.tw-prose > :first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose :where(.tw-prose > :last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 0; +} +.tw-prose-sm { + font-size: 0.875rem; + line-height: 1.7142857; +} +.tw-prose-sm :where(p):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; + margin-bottom: 1.1428571em; +} +.tw-prose-sm :where([class~="lead"]):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 1.2857143em; + line-height: 1.5555556; + margin-top: 0.8888889em; + margin-bottom: 0.8888889em; +} +.tw-prose-sm :where(blockquote):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.3333333em; + margin-bottom: 1.3333333em; + padding-inline-start: 1.1111111em; +} +.tw-prose-sm :where(h1):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 2.1428571em; + margin-top: 0; + margin-bottom: 0.8em; + line-height: 1.2; +} +.tw-prose-sm :where(h2):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 1.4285714em; + margin-top: 1.6em; + margin-bottom: 0.8em; + line-height: 1.4; +} +.tw-prose-sm :where(h3):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 1.2857143em; + margin-top: 1.5555556em; + margin-bottom: 0.4444444em; + line-height: 1.5555556; +} +.tw-prose-sm :where(h4):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.4285714em; + margin-bottom: 0.5714286em; + line-height: 1.4285714; +} +.tw-prose-sm :where(img):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} +.tw-prose-sm :where(picture):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} +.tw-prose-sm :where(picture > img):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} +.tw-prose-sm :where(video):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} +.tw-prose-sm :where(kbd):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8571429em; + border-radius: 0.3125rem; + padding-top: 0.1428571em; + padding-inline-end: 0.3571429em; + padding-bottom: 0.1428571em; + padding-inline-start: 0.3571429em; +} +.tw-prose-sm :where(code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8571429em; +} +.tw-prose-sm :where(h2 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.9em; +} +.tw-prose-sm :where(h3 code):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8888889em; +} +.tw-prose-sm :where(pre):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.6666667; + margin-top: 1.6666667em; + margin-bottom: 1.6666667em; + border-radius: 0.25rem; + padding-top: 0.6666667em; + padding-inline-end: 1em; + padding-bottom: 0.6666667em; + padding-inline-start: 1em; +} +.tw-prose-sm :where(ol):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; + margin-bottom: 1.1428571em; + padding-inline-start: 1.5714286em; +} +.tw-prose-sm :where(ul):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; + margin-bottom: 1.1428571em; + padding-inline-start: 1.5714286em; +} +.tw-prose-sm :where(li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.2857143em; + margin-bottom: 0.2857143em; +} +.tw-prose-sm :where(ol > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0.4285714em; +} +.tw-prose-sm :where(ul > li):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0.4285714em; +} +.tw-prose-sm :where(.tw-prose-sm > ul > li p):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.5714286em; + margin-bottom: 0.5714286em; +} +.tw-prose-sm :where(.tw-prose-sm > ul > li > p:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; +} +.tw-prose-sm :where(.tw-prose-sm > ul > li > p:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 1.1428571em; +} +.tw-prose-sm :where(.tw-prose-sm > ol > li > p:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; +} +.tw-prose-sm :where(.tw-prose-sm > ol > li > p:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 1.1428571em; +} +.tw-prose-sm :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.5714286em; + margin-bottom: 0.5714286em; +} +.tw-prose-sm :where(dl):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; + margin-bottom: 1.1428571em; +} +.tw-prose-sm :where(dt):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.1428571em; +} +.tw-prose-sm :where(dd):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0.2857143em; + padding-inline-start: 1.5714286em; +} +.tw-prose-sm :where(hr):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 2.8571429em; + margin-bottom: 2.8571429em; +} +.tw-prose-sm :where(hr + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose-sm :where(h2 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose-sm :where(h3 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose-sm :where(h4 + *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose-sm :where(table):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.5; +} +.tw-prose-sm :where(thead th):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-end: 1em; + padding-bottom: 0.6666667em; + padding-inline-start: 1em; +} +.tw-prose-sm :where(thead th:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0; +} +.tw-prose-sm :where(thead th:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-end: 0; +} +.tw-prose-sm :where(tbody td, tfoot td):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-top: 0.6666667em; + padding-inline-end: 1em; + padding-bottom: 0.6666667em; + padding-inline-start: 1em; +} +.tw-prose-sm :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-start: 0; +} +.tw-prose-sm :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + padding-inline-end: 0; +} +.tw-prose-sm :where(figure):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; +} +.tw-prose-sm :where(figure > *):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} +.tw-prose-sm :where(figcaption):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + font-size: 0.8571429em; + line-height: 1.3333333; + margin-top: 0.6666667em; +} +.tw-prose-sm :where(.tw-prose-sm > :first-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-top: 0; +} +.tw-prose-sm :where(.tw-prose-sm > :last-child):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *)) { + margin-bottom: 0; +} +.tw-prose-quoteless :where(blockquote p:first-of-type):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::before { + content: none; +} +.tw-prose-quoteless :where(blockquote p:last-of-type):not(:where([class~="tw-not-prose"],[class~="tw-not-prose"] *))::after { + content: none; +} +.tw-sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} +.tw-pointer-events-none { + pointer-events: none; +} +.tw-pointer-events-auto { + pointer-events: auto; +} +.tw-invisible { + visibility: hidden; +} +.tw-fixed { + position: fixed; +} +.tw-absolute { + position: absolute; +} +.tw-relative { + position: relative; +} +.tw-sticky { + position: sticky; +} +.tw-inset-0 { + inset: 0px; +} +.tw-inset-x-0 { + left: 0px; + right: 0px; +} +.tw-inset-y-0 { + top: 0px; + bottom: 0px; +} +.tw--left-\\[1px\\] { + left: -1px; +} +.tw--right-1 { + right: -0.25rem; +} +.tw--top-\\[1px\\] { + top: -1px; +} +.tw-bottom-0 { + bottom: 0px; +} +.tw-left-0 { + left: 0px; +} +.tw-left-2 { + left: 0.5rem; +} +.tw-left-3 { + left: 0.75rem; +} +.tw-left-4 { + left: 1rem; +} +.tw-left-\\[50\\%\\] { + left: 50%; +} +.tw-right-0 { + right: 0px; +} +.tw-right-1 { + right: 0.25rem; +} +.tw-right-3 { + right: 0.75rem; +} +.tw-right-4 { + right: 1rem; +} +.tw-start-2 { + inset-inline-start: 0.5rem; +} +.tw-top-0 { + top: 0px; +} +.tw-top-1\\.5 { + top: 0.375rem; +} +.tw-top-1\\/2 { + top: 50%; +} +.tw-top-2\\.5 { + top: 0.625rem; +} +.tw-top-3\\.5 { + top: 0.875rem; +} +.tw-top-4 { + top: 1rem; +} +.tw-top-\\[-1px\\] { + top: -1px; +} +.tw-top-\\[50\\%\\] { + top: 50%; +} +.tw-z-10 { + z-index: 10; +} +.tw-z-20 { + z-index: 20; +} +.tw-z-50 { + z-index: 50; +} +.tw-col-span-1 { + grid-column: span 1 / span 1; +} +.tw-col-span-2 { + grid-column: span 2 / span 2; +} +.tw-col-span-3 { + grid-column: span 3 / span 3; +} +.tw-col-start-1 { + grid-column-start: 1; +} +.tw-row-span-2 { + grid-row: span 2 / span 2; +} +.tw-row-start-2 { + grid-row-start: 2; +} +.tw-m-0 { + margin: 0px; +} +.tw-m-1 { + margin: 0.25rem; +} +.tw-m-2 { + margin: 0.5rem; +} +.tw--mx-1 { + margin-left: -0.25rem; + margin-right: -0.25rem; +} +.tw-mx-0 { + margin-left: 0px; + margin-right: 0px; +} +.tw-mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; +} +.tw-mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} +.tw-mx-3\\.5 { + margin-left: 0.875rem; + margin-right: 0.875rem; +} +.tw-mx-8 { + margin-left: 2rem; + margin-right: 2rem; +} +.tw-mx-auto { + margin-left: auto; + margin-right: auto; +} +.tw-my-1 { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} +.tw-my-2\\.5 { + margin-top: 0.625rem; + margin-bottom: 0.625rem; +} +.tw-my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} +.tw-my-auto { + margin-top: auto; + margin-bottom: auto; +} +.tw-mb-1 { + margin-bottom: 0.25rem; +} +.tw-mb-2 { + margin-bottom: 0.5rem; +} +.tw-mb-24 { + margin-bottom: 6rem; +} +.tw-mb-3 { + margin-bottom: 0.75rem; +} +.tw-mb-4 { + margin-bottom: 1rem; +} +.tw-me-1 { + margin-inline-end: 0.25rem; +} +.tw-me-2 { + margin-inline-end: 0.5rem; +} +.tw-ml-2 { + margin-left: 0.5rem; +} +.tw-ml-24 { + margin-left: 6rem; +} +.tw-ml-4 { + margin-left: 1rem; +} +.tw-ml-auto { + margin-left: auto; +} +.tw-mr-1 { + margin-right: 0.25rem; +} +.tw-mr-2 { + margin-right: 0.5rem; +} +.tw-mr-24 { + margin-right: 6rem; +} +.tw-mr-3 { + margin-right: 0.75rem; +} +.tw-mr-4 { + margin-right: 1rem; +} +.tw-ms-1 { + margin-inline-start: 0.25rem; +} +.tw-ms-2 { + margin-inline-start: 0.5rem; +} +.tw-ms-5 { + margin-inline-start: 1.25rem; +} +.tw-ms-auto { + margin-inline-start: auto; +} +.tw-mt-1 { + margin-top: 0.25rem; +} +.tw-mt-2 { + margin-top: 0.5rem; +} +.tw-mt-24 { + margin-top: 6rem; +} +.tw-mt-3 { + margin-top: 0.75rem; +} +.tw-mt-4 { + margin-top: 1rem; +} +.tw-mt-6 { + margin-top: 1.5rem; +} +.tw-mt-auto { + margin-top: auto; +} +.tw-box-border { + box-sizing: border-box; +} +.tw-line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} +.tw-block { + display: block; +} +.tw-inline-block { + display: inline-block; +} +.tw-flex { + display: flex; +} +.tw-inline-flex { + display: inline-flex; +} +.tw-grid { + display: grid; +} +.tw-inline-grid { + display: inline-grid; +} +.tw-hidden { + display: none; +} +.tw-aspect-square { + aspect-ratio: 1 / 1; +} +.tw-size-4 { + width: 1rem; + height: 1rem; +} +.tw-h-1 { + height: 0.25rem; +} +.tw-h-10 { + height: 2.5rem; +} +.tw-h-11 { + height: 2.75rem; +} +.tw-h-12 { + height: 3rem; +} +.tw-h-14 { + height: 3.5rem; +} +.tw-h-2 { + height: 0.5rem; +} +.tw-h-2\\.5 { + height: 0.625rem; +} +.tw-h-20 { + height: 5rem; +} +.tw-h-24 { + height: 6rem; +} +.tw-h-3 { + height: 0.75rem; +} +.tw-h-3\\.5 { + height: 0.875rem; +} +.tw-h-32 { + height: 8rem; +} +.tw-h-4 { + height: 1rem; +} +.tw-h-40 { + height: 10rem; +} +.tw-h-5 { + height: 1.25rem; +} +.tw-h-6 { + height: 1.5rem; +} +.tw-h-64 { + height: 16rem; +} +.tw-h-7 { + height: 1.75rem; +} +.tw-h-8 { + height: 2rem; +} +.tw-h-9 { + height: 2.25rem; +} +.tw-h-96 { + height: 24rem; +} +.tw-h-\\[1\\.2rem\\] { + height: 1.2rem; +} +.tw-h-\\[100px\\] { + height: 100px; +} +.tw-h-\\[1px\\] { + height: 1px; +} +.tw-h-\\[300px\\] { + height: 300px; +} +.tw-h-\\[5px\\] { + height: 5px; +} +.tw-h-\\[calc\\(100\\%-2px\\)\\] { + height: calc(100% - 2px); +} +.tw-h-\\[var\\(--radix-select-trigger-height\\)\\] { + height: var(--radix-select-trigger-height); +} +.tw-h-auto { + height: auto; +} +.tw-h-full { + height: 100%; +} +.tw-h-px { + height: 1px; +} +.tw-h-svh { + height: 100svh; +} +.tw-max-h-10 { + max-height: 2.5rem; +} +.tw-max-h-5 { + max-height: 1.25rem; +} +.tw-max-h-80 { + max-height: 20rem; +} +.tw-max-h-96 { + max-height: 24rem; +} +.tw-max-h-\\[--radix-context-menu-content-available-height\\] { + max-height: var(--radix-context-menu-content-available-height); +} +.tw-max-h-\\[300px\\] { + max-height: 300px; +} +.tw-max-h-\\[96\\%\\] { + max-height: 96%; +} +.tw-min-h-0 { + min-height: 0px; +} +.tw-min-h-11 { + min-height: 2.75rem; +} +.tw-min-h-\\[80px\\] { + min-height: 80px; +} +.tw-min-h-svh { + min-height: 100svh; +} +.tw-w-1 { + width: 0.25rem; +} +.tw-w-1\\/2 { + width: 50%; +} +.tw-w-10 { + width: 2.5rem; +} +.tw-w-11 { + width: 2.75rem; +} +.tw-w-12 { + width: 3rem; +} +.tw-w-2 { + width: 0.5rem; +} +.tw-w-2\\.5 { + width: 0.625rem; +} +.tw-w-20 { + width: 5rem; +} +.tw-w-24 { + width: 6rem; +} +.tw-w-3 { + width: 0.75rem; +} +.tw-w-3\\.5 { + width: 0.875rem; +} +.tw-w-3\\/4 { + width: 75%; +} +.tw-w-32 { + width: 8rem; +} +.tw-w-4 { + width: 1rem; +} +.tw-w-4\\/5 { + width: 80%; +} +.tw-w-4\\/6 { + width: 66.666667%; +} +.tw-w-48 { + width: 12rem; +} +.tw-w-5 { + width: 1.25rem; +} +.tw-w-5\\/6 { + width: 83.333333%; +} +.tw-w-56 { + width: 14rem; +} +.tw-w-6 { + width: 1.5rem; +} +.tw-w-60 { + width: 15rem; +} +.tw-w-64 { + width: 16rem; +} +.tw-w-7 { + width: 1.75rem; +} +.tw-w-72 { + width: 18rem; +} +.tw-w-8 { + width: 2rem; +} +.tw-w-80 { + width: 20rem; +} +.tw-w-9 { + width: 2.25rem; +} +.tw-w-9\\/12 { + width: 75%; +} +.tw-w-96 { + width: 24rem; +} +.tw-w-\\[--sidebar-width\\] { + width: var(--sidebar-width); +} +.tw-w-\\[1\\.2rem\\] { + width: 1.2rem; +} +.tw-w-\\[100px\\] { + width: 100px; +} +.tw-w-\\[116px\\] { + width: 116px; +} +.tw-w-\\[124px\\] { + width: 124px; +} +.tw-w-\\[150px\\] { + width: 150px; +} +.tw-w-\\[180px\\] { + width: 180px; +} +.tw-w-\\[1px\\] { + width: 1px; +} +.tw-w-\\[200px\\] { + width: 200px; +} +.tw-w-\\[250px\\] { + width: 250px; +} +.tw-w-\\[300px\\] { + width: 300px; +} +.tw-w-\\[320px\\] { + width: 320px; +} +.tw-w-\\[350px\\] { + width: 350px; +} +.tw-w-\\[400px\\] { + width: 400px; +} +.tw-w-\\[500px\\] { + width: 500px; +} +.tw-w-\\[5px\\] { + width: 5px; +} +.tw-w-\\[600px\\] { + width: 600px; +} +.tw-w-\\[70px\\] { + width: 70px; +} +.tw-w-\\[calc\\(100\\%-2px\\)\\] { + width: calc(100% - 2px); +} +.tw-w-\\[var\\(--radix-dropdown-menu-trigger-width\\)\\] { + width: var(--radix-dropdown-menu-trigger-width); +} +.tw-w-\\[var\\(--radix-popper-anchor-width\\,280px\\)\\] { + width: var(--radix-popper-anchor-width,280px); +} +.tw-w-auto { + width: auto; +} +.tw-w-fit { + width: fit-content; +} +.tw-w-full { + width: 100%; +} +.tw-w-max { + width: max-content; +} +.tw-w-px { + width: 1px; +} +.tw-min-w-0 { + min-width: 0px; +} +.tw-min-w-16 { + min-width: 4rem; +} +.tw-min-w-36 { + min-width: 9rem; +} +.tw-min-w-5 { + min-width: 1.25rem; +} +.tw-min-w-8 { + min-width: 2rem; +} +.tw-min-w-80 { + min-width: 20rem; +} +.tw-min-w-\\[12rem\\] { + min-width: 12rem; +} +.tw-min-w-\\[140px\\] { + min-width: 140px; +} +.tw-min-w-\\[200px\\] { + min-width: 200px; +} +.tw-min-w-\\[215px\\] { + min-width: 215px; +} +.tw-min-w-\\[26px\\] { + min-width: 26px; +} +.tw-min-w-\\[500px\\] { + min-width: 500px; +} +.tw-min-w-\\[8rem\\] { + min-width: 8rem; +} +.tw-min-w-\\[var\\(--radix-select-trigger-width\\)\\] { + min-width: var(--radix-select-trigger-width); +} +.tw-min-w-min { + min-width: min-content; +} +.tw-max-w-2xl { + max-width: 42rem; +} +.tw-max-w-3xl { + max-width: 48rem; +} +.tw-max-w-48 { + max-width: 12rem; +} +.tw-max-w-4xl { + max-width: 56rem; +} +.tw-max-w-5 { + max-width: 1.25rem; +} +.tw-max-w-64 { + max-width: 16rem; +} +.tw-max-w-6xl { + max-width: 72rem; +} +.tw-max-w-96 { + max-width: 24rem; +} +.tw-max-w-\\[--skeleton-width\\] { + max-width: var(--skeleton-width); +} +.tw-max-w-\\[200px\\] { + max-width: 200px; +} +.tw-max-w-\\[220px\\] { + max-width: 220px; +} +.tw-max-w-\\[280px\\] { + max-width: 280px; +} +.tw-max-w-\\[calc\\(100vw-2rem\\)\\] { + max-width: calc(100vw - 2rem); +} +.tw-max-w-fit { + max-width: fit-content; +} +.tw-max-w-full { + max-width: 100%; +} +.tw-max-w-lg { + max-width: 32rem; +} +.tw-max-w-md { + max-width: 28rem; +} +.tw-max-w-none { + max-width: none; +} +.tw-max-w-sm { + max-width: 24rem; +} +.tw-max-w-xs { + max-width: 20rem; +} +.tw-flex-1 { + flex: 1 1 0%; +} +.tw-flex-shrink-0 { + flex-shrink: 0; +} +.tw-shrink { + flex-shrink: 1; +} +.tw-shrink-0 { + flex-shrink: 0; +} +.tw-flex-grow { + flex-grow: 1; +} +.tw-grow { + flex-grow: 1; +} +.tw-grow-\\[10\\] { + flex-grow: 10; +} +.tw-grow-\\[1\\] { + flex-grow: 1; +} +.tw-basis-0 { + flex-basis: 0px; +} +.tw-caption-bottom { + caption-side: bottom; +} +.tw-border-collapse { + border-collapse: collapse; +} +.tw-origin-\\[--radix-context-menu-content-transform-origin\\] { + transform-origin: var(--radix-context-menu-content-transform-origin); +} +.tw--translate-x-1\\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw--translate-x-px { + --tw-translate-x: -1px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw--translate-y-1\\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-translate-x-\\[-50\\%\\] { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-translate-x-px { + --tw-translate-x: 1px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-translate-y-\\[-50\\%\\] { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-rotate-180 { + --tw-rotate: 180deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +@keyframes tw-pulse { + + 50% { + opacity: .5; + } +} +.tw-animate-pulse { + animation: tw-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} +@keyframes tw-spin { + + to { + transform: rotate(360deg); + } +} +.tw-animate-spin { + animation: tw-spin 1s linear infinite; +} +.tw-cursor-default { + cursor: default; +} +.tw-cursor-ew-resize { + cursor: ew-resize; +} +.tw-cursor-not-allowed { + cursor: not-allowed; +} +.tw-cursor-pointer { + cursor: pointer; +} +.tw-touch-none { + touch-action: none; +} +.tw-select-none { + user-select: none; +} +.tw-resize { + resize: both; +} +.tw-scroll-m-20 { + scroll-margin: 5rem; +} +.tw-list-inside { + list-style-position: inside; +} +.tw-list-outside { + list-style-position: outside; +} +.\\!tw-list-\\[lower-alpha\\] { + list-style-type: lower-alpha !important; +} +.\\!tw-list-\\[lower-roman\\] { + list-style-type: lower-roman !important; +} +.\\!tw-list-\\[upper-alpha\\] { + list-style-type: upper-alpha !important; +} +.\\!tw-list-\\[upper-roman\\] { + list-style-type: upper-roman !important; +} +.\\!tw-list-decimal { + list-style-type: decimal !important; +} +.\\!tw-list-disc { + list-style-type: disc !important; +} +.tw-list-decimal { + list-style-type: decimal; +} +.tw-list-disc { + list-style-type: disc; +} +.tw-list-none { + list-style-type: none; +} +.tw-grid-flow-col { + grid-auto-flow: column; +} +.tw-grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} +.tw-grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} +.tw-grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} +.tw-grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} +.tw-grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); +} +.tw-grid-cols-\\[25\\%\\,25\\%\\,50\\%\\] { + grid-template-columns: 25% 25% 50%; +} +.tw-grid-cols-\\[25\\%\\,50\\%\\,25\\%\\] { + grid-template-columns: 25% 50% 25%; +} +.tw-grid-cols-\\[min-content_1fr\\] { + grid-template-columns: min-content 1fr; +} +.tw-grid-cols-\\[min-content_min-content_1fr\\] { + grid-template-columns: min-content min-content 1fr; +} +.tw-grid-cols-subgrid { + grid-template-columns: subgrid; +} +.tw-flex-row { + flex-direction: row; +} +.tw-flex-row-reverse { + flex-direction: row-reverse; +} +.tw-flex-col { + flex-direction: column; +} +.tw-flex-col-reverse { + flex-direction: column-reverse; +} +.tw-flex-wrap { + flex-wrap: wrap; +} +.tw-content-center { + align-content: center; +} +.tw-items-start { + align-items: flex-start; +} +.tw-items-end { + align-items: flex-end; +} +.tw-items-center { + align-items: center; +} +.tw-items-baseline { + align-items: baseline; +} +.tw-items-stretch { + align-items: stretch; +} +.tw-justify-start { + justify-content: flex-start; +} +.tw-justify-end { + justify-content: flex-end; +} +.tw-justify-center { + justify-content: center; +} +.tw-justify-between { + justify-content: space-between; +} +.tw-gap-0 { + gap: 0px; +} +.tw-gap-1 { + gap: 0.25rem; +} +.tw-gap-1\\.5 { + gap: 0.375rem; +} +.tw-gap-2 { + gap: 0.5rem; +} +.tw-gap-2\\.5 { + gap: 0.625rem; +} +.tw-gap-3 { + gap: 0.75rem; +} +.tw-gap-4 { + gap: 1rem; +} +.tw-gap-5 { + gap: 1.25rem; +} +.tw-gap-6 { + gap: 1.5rem; +} +.tw-gap-\\[12px\\] { + gap: 12px; +} +.tw-gap-x-1 { + column-gap: 0.25rem; +} +.tw-gap-x-2 { + column-gap: 0.5rem; +} +.tw-gap-x-3 { + column-gap: 0.75rem; +} +.tw-gap-x-4 { + column-gap: 1rem; +} +.tw-gap-y-1 { + row-gap: 0.25rem; +} +.tw-gap-y-2 { + row-gap: 0.5rem; +} +.tw-space-x-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.25rem * var(--tw-space-x-reverse)); + margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); +} +.tw-space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} +.tw-space-x-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.75rem * var(--tw-space-x-reverse)); + margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); +} +.tw-space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} +.tw-space-x-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.5rem * var(--tw-space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); +} +.tw-space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} +.tw-space-y-1\\.5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.375rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.375rem * var(--tw-space-y-reverse)); +} +.tw-space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} +.tw-space-y-3 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} +.tw-space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} +.tw-space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); +} +.tw-space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} +.tw-divide-x > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); +} +.tw-divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} +.tw-self-stretch { + align-self: stretch; +} +.tw-overflow-auto { + overflow: auto; +} +.tw-overflow-hidden { + overflow: hidden; +} +.tw-overflow-clip { + overflow: clip; +} +.tw-overflow-visible { + overflow: visible; +} +.tw-overflow-scroll { + overflow: scroll; +} +.tw-overflow-x-auto { + overflow-x: auto; +} +.tw-overflow-y-auto { + overflow-y: auto; +} +.tw-overflow-x-hidden { + overflow-x: hidden; +} +.tw-overflow-y-hidden { + overflow-y: hidden; +} +.tw-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tw-text-ellipsis { + text-overflow: ellipsis; +} +.tw-text-clip { + text-overflow: clip; +} +.tw-whitespace-normal { + white-space: normal; +} +.tw-whitespace-nowrap { + white-space: nowrap; +} +.tw-text-nowrap { + text-wrap: nowrap; +} +.tw-text-balance { + text-wrap: balance; +} +.tw-break-words { + overflow-wrap: break-word; +} +.tw-rounded { + border-radius: 0.25rem; +} +.tw-rounded-2xl { + border-radius: 1rem; +} +.tw-rounded-\\[6px\\] { + border-radius: 6px; +} +.tw-rounded-full { + border-radius: 9999px; +} +.tw-rounded-lg { + border-radius: var(--radius); +} +.tw-rounded-md { + border-radius: calc(var(--radius) - 2px); +} +.tw-rounded-none { + border-radius: 0px; +} +.tw-rounded-sm { + border-radius: calc(var(--radius) - 4px); +} +.tw-rounded-xl { + border-radius: 0.75rem; +} +.tw-rounded-b-\\[10px\\] { + border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; +} +.tw-rounded-e-none { + border-start-end-radius: 0px; + border-end-end-radius: 0px; +} +.tw-rounded-l-\\[10px\\] { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} +.tw-rounded-l-lg { + border-top-left-radius: var(--radius); + border-bottom-left-radius: var(--radius); +} +.tw-rounded-r-\\[10px\\] { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} +.tw-rounded-r-xl { + border-top-right-radius: 0.75rem; + border-bottom-right-radius: 0.75rem; +} +.tw-rounded-s-none { + border-start-start-radius: 0px; + border-end-start-radius: 0px; +} +.tw-rounded-t-\\[10px\\] { + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} +.tw-border { + border-width: 1px; +} +.tw-border-0 { + border-width: 0px; +} +.tw-border-2 { + border-width: 2px; +} +.tw-border-b { + border-bottom-width: 1px; +} +.tw-border-b-0 { + border-bottom-width: 0px; +} +.tw-border-e { + border-inline-end-width: 1px; +} +.tw-border-e-0 { + border-inline-end-width: 0px; +} +.tw-border-l { + border-left-width: 1px; +} +.tw-border-l-2 { + border-left-width: 2px; +} +.tw-border-l-4 { + border-left-width: 4px; +} +.tw-border-s-0 { + border-inline-start-width: 0px; +} +.tw-border-s-2 { + border-inline-start-width: 2px; +} +.tw-border-t { + border-top-width: 1px; +} +.tw-border-t-0 { + border-top-width: 0px; +} +.tw-border-solid { + border-style: solid; +} +.tw-border-dashed { + border-style: dashed; +} +.tw-border-none { + border-style: none; +} +.tw-border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity, 1)); +} +.tw-border-blue-400 { + --tw-border-opacity: 1; + border-color: rgb(96 165 250 / var(--tw-border-opacity, 1)); +} +.tw-border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); +} +.tw-border-border { + border-color: hsl(var(--border)); +} +.tw-border-border\\/50 { + border-color: hsl(var(--border) / 0.5); +} +.tw-border-destructive\\/50 { + border-color: hsl(var(--destructive) / 0.5); +} +.tw-border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); +} +.tw-border-input { + border-color: hsl(var(--input)); +} +.tw-border-muted-foreground { + border-color: hsl(var(--muted-foreground)); +} +.tw-border-primary { + border-color: hsl(var(--primary)); +} +.tw-border-red-300 { + --tw-border-opacity: 1; + border-color: rgb(252 165 165 / var(--tw-border-opacity, 1)); +} +.tw-border-red-400 { + --tw-border-opacity: 1; + border-color: rgb(248 113 113 / var(--tw-border-opacity, 1)); +} +.tw-border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)); +} +.tw-border-red-600 { + --tw-border-opacity: 1; + border-color: rgb(220 38 38 / var(--tw-border-opacity, 1)); +} +.tw-border-ring { + border-color: hsl(var(--ring)); +} +.tw-border-sidebar-border { + border-color: hsl(var(--sidebar-border)); +} +.tw-border-slate-300 { + --tw-border-opacity: 1; + border-color: rgb(203 213 225 / var(--tw-border-opacity, 1)); +} +.tw-border-transparent { + border-color: transparent; +} +.tw-border-yellow-400 { + --tw-border-opacity: 1; + border-color: rgb(250 204 21 / var(--tw-border-opacity, 1)); +} +.tw-border-yellow-500 { + --tw-border-opacity: 1; + border-color: rgb(234 179 8 / var(--tw-border-opacity, 1)); +} +.tw-border-s-amber-200 { + --tw-border-opacity: 1; + border-inline-start-color: rgb(253 230 138 / var(--tw-border-opacity, 1)); +} +.tw-border-s-indigo-200 { + --tw-border-opacity: 1; + border-inline-start-color: rgb(199 210 254 / var(--tw-border-opacity, 1)); +} +.tw-border-s-purple-200 { + --tw-border-opacity: 1; + border-inline-start-color: rgb(233 213 255 / var(--tw-border-opacity, 1)); +} +.tw-border-s-red-200 { + --tw-border-opacity: 1; + border-inline-start-color: rgb(254 202 202 / var(--tw-border-opacity, 1)); +} +.\\!tw-bg-destructive\\/50 { + background-color: hsl(var(--destructive) / 0.5) !important; +} +.tw-bg-accent { + background-color: hsl(var(--accent)); +} +.tw-bg-background { + background-color: hsl(var(--background)); +} +.tw-bg-background\\/50 { + background-color: hsl(var(--background) / 0.5); +} +.tw-bg-black\\/80 { + background-color: rgb(0 0 0 / 0.8); +} +.tw-bg-blue-100 { + --tw-bg-opacity: 1; + background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1)); +} +.tw-bg-blue-400 { + --tw-bg-opacity: 1; + background-color: rgb(96 165 250 / var(--tw-bg-opacity, 1)); +} +.tw-bg-blue-50 { + --tw-bg-opacity: 1; + background-color: rgb(239 246 255 / var(--tw-bg-opacity, 1)); +} +.tw-bg-blue-500 { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); +} +.tw-bg-border { + background-color: hsl(var(--border)); +} +.tw-bg-card { + background-color: hsl(var(--card)); +} +.tw-bg-destructive { + background-color: hsl(var(--destructive)); +} +.tw-bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} +.tw-bg-gray-50 { + --tw-bg-opacity: 1; + background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); +} +.tw-bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity, 1)); +} +.tw-bg-green-100 { + --tw-bg-opacity: 1; + background-color: rgb(220 252 231 / var(--tw-bg-opacity, 1)); +} +.tw-bg-green-50 { + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); +} +.tw-bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1)); +} +.tw-bg-input { + background-color: hsl(var(--input)); +} +.tw-bg-muted { + background-color: hsl(var(--muted)); +} +.tw-bg-muted\\/50 { + background-color: hsl(var(--muted) / 0.5); +} +.tw-bg-neutral-300 { + --tw-bg-opacity: 1; + background-color: rgb(212 212 212 / var(--tw-bg-opacity, 1)); +} +.tw-bg-orange-100 { + --tw-bg-opacity: 1; + background-color: rgb(255 237 213 / var(--tw-bg-opacity, 1)); +} +.tw-bg-popover { + background-color: hsl(var(--popover)); +} +.tw-bg-primary { + background-color: hsl(var(--primary)); +} +.tw-bg-primary-foreground { + background-color: hsl(var(--primary-foreground)); +} +.tw-bg-purple-50 { + --tw-bg-opacity: 1; + background-color: rgb(250 245 255 / var(--tw-bg-opacity, 1)); +} +.tw-bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1)); +} +.tw-bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); +} +.tw-bg-rose-500 { + --tw-bg-opacity: 1; + background-color: rgb(244 63 94 / var(--tw-bg-opacity, 1)); +} +.tw-bg-rose-500\\/15 { + background-color: rgb(244 63 94 / 0.15); +} +.tw-bg-rose-500\\/5 { + background-color: rgb(244 63 94 / 0.05); +} +.tw-bg-secondary { + background-color: hsl(var(--secondary)); +} +.tw-bg-sidebar { + background-color: hsl(var(--sidebar-background)); +} +.tw-bg-sidebar-accent { + background-color: hsl(var(--sidebar-accent)); +} +.tw-bg-sidebar-border { + background-color: hsl(var(--sidebar-border)); +} +.tw-bg-sky-500 { + --tw-bg-opacity: 1; + background-color: rgb(14 165 233 / var(--tw-bg-opacity, 1)); +} +.tw-bg-sky-500\\/15 { + background-color: rgb(14 165 233 / 0.15); +} +.tw-bg-sky-500\\/5 { + background-color: rgb(14 165 233 / 0.05); +} +.tw-bg-teal-500 { + --tw-bg-opacity: 1; + background-color: rgb(20 184 166 / var(--tw-bg-opacity, 1)); +} +.tw-bg-teal-500\\/15 { + background-color: rgb(20 184 166 / 0.15); +} +.tw-bg-teal-500\\/5 { + background-color: rgb(20 184 166 / 0.05); +} +.tw-bg-transparent { + background-color: transparent; +} +.tw-bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} +.tw-bg-yellow-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1)); +} +.tw-bg-yellow-50 { + --tw-bg-opacity: 1; + background-color: rgb(254 252 232 / var(--tw-bg-opacity, 1)); +} +.tw-bg-yellow-500 { + --tw-bg-opacity: 1; + background-color: rgb(234 179 8 / var(--tw-bg-opacity, 1)); +} +.tw-bg-zinc-400 { + --tw-bg-opacity: 1; + background-color: rgb(161 161 170 / var(--tw-bg-opacity, 1)); +} +.tw-fill-current { + fill: currentColor; +} +.tw-fill-destructive { + fill: hsl(var(--destructive)); +} +.tw-fill-yellow-400 { + fill: #facc15; +} +.tw-fill-yellow-400\\/50 { + fill: rgb(250 204 21 / 0.5); +} +.tw-p-0 { + padding: 0px; +} +.tw-p-0\\.5 { + padding: 0.125rem; +} +.tw-p-1 { + padding: 0.25rem; +} +.tw-p-2 { + padding: 0.5rem; +} +.tw-p-3 { + padding: 0.75rem; +} +.tw-p-4 { + padding: 1rem; +} +.tw-p-6 { + padding: 1.5rem; +} +.tw-p-8 { + padding: 2rem; +} +.tw-p-\\[10px\\] { + padding: 10px; +} +.tw-p-\\[1px\\] { + padding: 1px; +} +.tw-px-0 { + padding-left: 0px; + padding-right: 0px; +} +.tw-px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} +.tw-px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.tw-px-2\\.5 { + padding-left: 0.625rem; + padding-right: 0.625rem; +} +.tw-px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} +.tw-px-4 { + padding-left: 1rem; + padding-right: 1rem; +} +.tw-px-5 { + padding-left: 1.25rem; + padding-right: 1.25rem; +} +.tw-px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} +.tw-px-8 { + padding-left: 2rem; + padding-right: 2rem; +} +.tw-py-0 { + padding-top: 0px; + padding-bottom: 0px; +} +.tw-py-0\\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} +.tw-py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} +.tw-py-1\\.5 { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} +.tw-py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.tw-py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} +.tw-py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} +.tw-py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} +.tw-py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} +.\\!tw-pr-10 { + padding-right: 2.5rem !important; +} +.tw-pb-0 { + padding-bottom: 0px; +} +.tw-pb-1 { + padding-bottom: 0.25rem; +} +.tw-pb-16 { + padding-bottom: 4rem; +} +.tw-pb-2 { + padding-bottom: 0.5rem; +} +.tw-pb-24 { + padding-bottom: 6rem; +} +.tw-pb-3 { + padding-bottom: 0.75rem; +} +.tw-pb-4 { + padding-bottom: 1rem; +} +.tw-pb-8 { + padding-bottom: 2rem; +} +.tw-pe-1 { + padding-inline-end: 0.25rem; +} +.tw-pe-2 { + padding-inline-end: 0.5rem; +} +.tw-pe-4 { + padding-inline-end: 1rem; +} +.tw-pe-8 { + padding-inline-end: 2rem; +} +.tw-pe-9 { + padding-inline-end: 2.25rem; +} +.tw-pe-\\[calc\\(138px\\+1rem\\)\\] { + padding-inline-end: calc(138px + 1rem); +} +.tw-pl-2 { + padding-left: 0.5rem; +} +.tw-pl-3 { + padding-left: 0.75rem; +} +.tw-pl-4 { + padding-left: 1rem; +} +.tw-pl-5 { + padding-left: 1.25rem; +} +.tw-pl-6 { + padding-left: 1.5rem; +} +.tw-pl-8 { + padding-left: 2rem; +} +.tw-pr-0 { + padding-right: 0px; +} +.tw-pr-2 { + padding-right: 0.5rem; +} +.tw-pr-3 { + padding-right: 0.75rem; +} +.tw-pr-4 { + padding-right: 1rem; +} +.tw-ps-12 { + padding-inline-start: 3rem; +} +.tw-ps-2 { + padding-inline-start: 0.5rem; +} +.tw-ps-4 { + padding-inline-start: 1rem; +} +.tw-ps-8 { + padding-inline-start: 2rem; +} +.tw-ps-9 { + padding-inline-start: 2.25rem; +} +.tw-ps-\\[85px\\] { + padding-inline-start: 85px; +} +.tw-pt-0 { + padding-top: 0px; +} +.tw-pt-1 { + padding-top: 0.25rem; +} +.tw-pt-2 { + padding-top: 0.5rem; +} +.tw-pt-3 { + padding-top: 0.75rem; +} +.tw-pt-6 { + padding-top: 1.5rem; +} +.tw-text-left { + text-align: left; +} +.tw-text-center { + text-align: center; +} +.tw-text-right { + text-align: right; +} +.tw-text-start { + text-align: start; +} +.tw-text-end { + text-align: end; +} +.tw-align-middle { + vertical-align: middle; +} +.tw-font-mono { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} +.tw-font-sans { + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} +.tw-text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} +.tw-text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} +.tw-text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} +.tw-text-base { + font-size: 1rem; + line-height: 1.5rem; +} +.tw-text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} +.tw-text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.tw-text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} +.tw-text-xs { + font-size: 0.75rem; + line-height: 1rem; +} +.tw-font-bold { + font-weight: 700; +} +.tw-font-extrabold { + font-weight: 800; +} +.tw-font-medium { + font-weight: 500; +} +.tw-font-normal { + font-weight: 400; +} +.tw-font-semibold { + font-weight: 600; +} +.tw-uppercase { + text-transform: uppercase; +} +.tw-capitalize { + text-transform: capitalize; +} +.tw-italic { + font-style: italic; +} +.tw-tabular-nums { + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction); +} +.tw-leading-loose { + line-height: 2; +} +.tw-leading-none { + line-height: 1; +} +.tw-leading-relaxed { + line-height: 1.625; +} +.tw-leading-tight { + line-height: 1.25; +} +.tw-tracking-tight { + letter-spacing: -0.025em; +} +.tw-tracking-widest { + letter-spacing: 0.1em; +} +.tw-text-accent-foreground { + color: hsl(var(--accent-foreground)); +} +.tw-text-blue-400 { + --tw-text-opacity: 1; + color: rgb(96 165 250 / var(--tw-text-opacity, 1)); +} +.tw-text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity, 1)); +} +.tw-text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); +} +.tw-text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} +.tw-text-card-foreground { + color: hsl(var(--card-foreground)); +} +.tw-text-current { + color: currentColor; +} +.tw-text-destructive { + color: hsl(var(--destructive)); +} +.tw-text-destructive-foreground { + color: hsl(var(--destructive-foreground)); +} +.tw-text-foreground { + color: hsl(var(--foreground)); +} +.tw-text-foreground\\/30 { + color: hsl(var(--foreground) / 0.3); +} +.tw-text-foreground\\/50 { + color: hsl(var(--foreground) / 0.5); +} +.tw-text-foreground\\/70 { + color: hsl(var(--foreground) / 0.7); +} +.tw-text-gray-300 { + --tw-text-opacity: 1; + color: rgb(209 213 219 / var(--tw-text-opacity, 1)); +} +.tw-text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} +.tw-text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); +} +.tw-text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity, 1)); +} +.tw-text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity, 1)); +} +.tw-text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity, 1)); +} +.tw-text-green-700 { + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity, 1)); +} +.tw-text-green-800 { + --tw-text-opacity: 1; + color: rgb(22 101 52 / var(--tw-text-opacity, 1)); +} +.tw-text-inherit { + color: inherit; +} +.tw-text-muted-foreground { + color: hsl(var(--muted-foreground)); +} +.tw-text-muted-foreground\\/50 { + color: hsl(var(--muted-foreground) / 0.5); +} +.tw-text-orange-800 { + --tw-text-opacity: 1; + color: rgb(154 52 18 / var(--tw-text-opacity, 1)); +} +.tw-text-popover-foreground { + color: hsl(var(--popover-foreground)); +} +.tw-text-primary { + color: hsl(var(--primary)); +} +.tw-text-primary-foreground { + color: hsl(var(--primary-foreground)); +} +.tw-text-purple-900 { + --tw-text-opacity: 1; + color: rgb(88 28 135 / var(--tw-text-opacity, 1)); +} +.tw-text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} +.tw-text-red-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity, 1)); +} +.tw-text-red-700 { + --tw-text-opacity: 1; + color: rgb(185 28 28 / var(--tw-text-opacity, 1)); +} +.tw-text-red-800 { + --tw-text-opacity: 1; + color: rgb(153 27 27 / var(--tw-text-opacity, 1)); +} +.tw-text-rose-600 { + --tw-text-opacity: 1; + color: rgb(225 29 72 / var(--tw-text-opacity, 1)); +} +.tw-text-secondary-foreground { + color: hsl(var(--secondary-foreground)); +} +.tw-text-sidebar-accent-foreground { + color: hsl(var(--sidebar-accent-foreground)); +} +.tw-text-sidebar-foreground { + color: hsl(var(--sidebar-foreground)); +} +.tw-text-sidebar-foreground\\/70 { + color: hsl(var(--sidebar-foreground) / 0.7); +} +.tw-text-sky-600 { + --tw-text-opacity: 1; + color: rgb(2 132 199 / var(--tw-text-opacity, 1)); +} +.tw-text-slate-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity, 1)); +} +.tw-text-teal-600 { + --tw-text-opacity: 1; + color: rgb(13 148 136 / var(--tw-text-opacity, 1)); +} +.tw-text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} +.tw-text-yellow-400 { + --tw-text-opacity: 1; + color: rgb(250 204 21 / var(--tw-text-opacity, 1)); +} +.tw-text-yellow-600 { + --tw-text-opacity: 1; + color: rgb(202 138 4 / var(--tw-text-opacity, 1)); +} +.tw-text-yellow-700 { + --tw-text-opacity: 1; + color: rgb(161 98 7 / var(--tw-text-opacity, 1)); +} +.tw-underline { + text-decoration-line: underline; +} +.tw-line-through { + text-decoration-line: line-through; +} +.tw-decoration-destructive { + text-decoration-color: hsl(var(--destructive)); +} +.tw-underline-offset-4 { + text-underline-offset: 4px; +} +.tw-opacity-0 { + opacity: 0; +} +.tw-opacity-100 { + opacity: 1; +} +.tw-opacity-40 { + opacity: 0.4; +} +.tw-opacity-50 { + opacity: 0.5; +} +.tw-opacity-60 { + opacity: 0.6; +} +.tw-opacity-70 { + opacity: 0.7; +} +.tw-shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-shadow-\\[0_0_0_1px_hsl\\(var\\(--sidebar-border\\)\\)\\] { + --tw-shadow: 0 0 0 1px hsl(var(--sidebar-border)); + --tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-shadow-none { + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} +.tw-ring-0 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.tw-ring-2 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.tw-ring-primary { + --tw-ring-color: hsl(var(--primary)); +} +.tw-ring-sidebar-ring { + --tw-ring-color: hsl(var(--sidebar-ring)); +} +.tw-ring-offset-2 { + --tw-ring-offset-width: 2px; +} +.tw-ring-offset-background { + --tw-ring-offset-color: hsl(var(--background)); +} +.tw-drop-shadow-sm { + --tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} +.tw-transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-\\[left\\,right\\,width\\] { + transition-property: left,right,width; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-\\[margin\\,opa\\] { + transition-property: margin,opa; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-\\[width\\,height\\,padding\\] { + transition-property: width,height,padding; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-\\[width\\] { + transition-property: width; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-colors { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-opacity { + transition-property: opacity; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-transition-transform { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.tw-duration-200 { + transition-duration: 200ms; +} +.tw-ease-linear { + transition-timing-function: linear; +} +@keyframes enter { + + from { + opacity: var(--tw-enter-opacity, 1); + transform: translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0)); + } +} +@keyframes exit { + + to { + opacity: var(--tw-exit-opacity, 1); + transform: translate3d(var(--tw-exit-translate-x, 0), var(--tw-exit-translate-y, 0), 0) scale3d(var(--tw-exit-scale, 1), var(--tw-exit-scale, 1), var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0)); + } +} +.tw-animate-in { + animation-name: enter; + animation-duration: 150ms; + --tw-enter-opacity: initial; + --tw-enter-scale: initial; + --tw-enter-rotate: initial; + --tw-enter-translate-x: initial; + --tw-enter-translate-y: initial; +} +.tw-fade-in-0 { + --tw-enter-opacity: 0; +} +.tw-fade-in-80 { + --tw-enter-opacity: 0.8; +} +.tw-zoom-in-95 { + --tw-enter-scale: .95; +} +.tw-duration-200 { + animation-duration: 200ms; +} +.tw-ease-linear { + animation-timing-function: linear; +} +.tw-\\@container\\/toolbar { + container-type: inline-size; + container-name: toolbar; +} +.\\[--lexical-indent-base-value\\:40px\\] { + --lexical-indent-base-value: 40px; +} + +/* + * WARNING: These themes are also represented in paranext-core/src/shared/data/themes.data.json! + * Please update in both locations +*/ +/* #region shared with https://github.com/paranext/paranext-extension-template/blob/main/src/tailwind.css */ +/* #endregion */ + +/* Note that the following region is from shadcn/ui's styles + * https://ui.shadcn.com/docs/installation/manual#configure-styles but is scoped down to .pr-twp + * because this is just a component library and should not apply its styles to the entire page. + * + * There is now a section in this library's README.md that explains how to apply these styles to the + * entire page if desired. + * + * The template has the original shadcn/ui styles because it intentionally applies the styles to the + * entire page. The same is true for Platform.Bible - see \`app.component.scss\` + */ +/* #region shared with https://github.com/paranext/paranext-extension-template/blob/main/src/tailwind.css but with the difference of being scoped to .pr-twp here */ +.file\\:tw-border-0::file-selector-button { + border-width: 0px; +} +.file\\:tw-bg-transparent::file-selector-button { + background-color: transparent; +} +.file\\:tw-text-sm::file-selector-button { + font-size: 0.875rem; + line-height: 1.25rem; +} +.file\\:tw-font-medium::file-selector-button { + font-weight: 500; +} +.file\\:tw-text-foreground::file-selector-button { + color: hsl(var(--foreground)); +} +.placeholder\\:tw-text-muted-foreground::placeholder { + color: hsl(var(--muted-foreground)); +} +.before\\:tw-absolute::before { + content: var(--tw-content); + position: absolute; +} +.before\\:tw-left-0::before { + content: var(--tw-content); + left: 0px; +} +.before\\:tw-top-0\\.5::before { + content: var(--tw-content); + top: 0.125rem; +} +.before\\:tw-block::before { + content: var(--tw-content); + display: block; +} +.before\\:tw-hidden::before { + content: var(--tw-content); + display: none; +} +.before\\:tw-h-4::before { + content: var(--tw-content); + height: 1rem; +} +.before\\:tw-w-4::before { + content: var(--tw-content); + width: 1rem; +} +.before\\:tw-cursor-pointer::before { + content: var(--tw-content); + cursor: pointer; +} +.before\\:tw-rounded::before { + content: var(--tw-content); + border-radius: 0.25rem; +} +.before\\:tw-border::before { + content: var(--tw-content); + border-width: 1px; +} +.before\\:tw-border-primary::before { + content: var(--tw-content); + border-color: hsl(var(--primary)); +} +.before\\:tw-bg-primary::before { + content: var(--tw-content); + background-color: hsl(var(--primary)); +} +.before\\:tw-bg-cover::before { + content: var(--tw-content); + background-size: cover; +} +.before\\:tw-bg-no-repeat::before { + content: var(--tw-content); + background-repeat: no-repeat; +} +.before\\:tw-content-\\[\\"\\"\\]::before { + --tw-content: ""; + content: var(--tw-content); +} +.after\\:tw-absolute::after { + content: var(--tw-content); + position: absolute; +} +.after\\:tw--inset-2::after { + content: var(--tw-content); + inset: -0.5rem; +} +.after\\:tw-inset-y-0::after { + content: var(--tw-content); + top: 0px; + bottom: 0px; +} +.after\\:tw-left-1\\/2::after { + content: var(--tw-content); + left: 50%; +} +.after\\:tw-left-\\[7px\\]::after { + content: var(--tw-content); + left: 7px; +} +.after\\:tw-right-\\[7px\\]::after { + content: var(--tw-content); + right: 7px; +} +.after\\:tw-top-\\[6px\\]::after { + content: var(--tw-content); + top: 6px; +} +.after\\:tw-block::after { + content: var(--tw-content); + display: block; +} +.after\\:tw-hidden::after { + content: var(--tw-content); + display: none; +} +.after\\:tw-h-0\\.5::after { + content: var(--tw-content); + height: 0.125rem; +} +.after\\:tw-h-\\[6px\\]::after { + content: var(--tw-content); + height: 6px; +} +.after\\:tw-w-1::after { + content: var(--tw-content); + width: 0.25rem; +} +.after\\:tw-w-\\[2px\\]::after { + content: var(--tw-content); + width: 2px; +} +.after\\:tw-w-\\[3px\\]::after { + content: var(--tw-content); + width: 3px; +} +.after\\:tw--translate-x-1\\/2::after { + content: var(--tw-content); + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.after\\:tw-rotate-45::after { + content: var(--tw-content); + --tw-rotate: 45deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.after\\:tw-cursor-pointer::after { + content: var(--tw-content); + cursor: pointer; +} +.after\\:tw-border-b-2::after { + content: var(--tw-content); + border-bottom-width: 2px; +} +.after\\:tw-border-l-0::after { + content: var(--tw-content); + border-left-width: 0px; +} +.after\\:tw-border-r-2::after { + content: var(--tw-content); + border-right-width: 2px; +} +.after\\:tw-border-t-0::after { + content: var(--tw-content); + border-top-width: 0px; +} +.after\\:tw-border-solid::after { + content: var(--tw-content); + border-style: solid; +} +.after\\:tw-border-white::after { + content: var(--tw-content); + --tw-border-opacity: 1; + border-color: rgb(255 255 255 / var(--tw-border-opacity, 1)); +} +.after\\:tw-bg-muted::after { + content: var(--tw-content); + background-color: hsl(var(--muted)); +} +.after\\:tw-content-\\[\\"\\"\\]::after { + --tw-content: ""; + content: var(--tw-content); +} +.first\\:tw-mt-0:first-child { + margin-top: 0px; +} +.even\\:tw-bg-muted:nth-child(even) { + background-color: hsl(var(--muted)); +} +.hover\\:tw-cursor-pointer:hover { + cursor: pointer; +} +.hover\\:tw-bg-accent:hover { + background-color: hsl(var(--accent)); +} +.hover\\:tw-bg-accent\\/80:hover { + background-color: hsl(var(--accent) / 0.8); +} +.hover\\:tw-bg-blue-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); +} +.hover\\:tw-bg-destructive\\/80:hover { + background-color: hsl(var(--destructive) / 0.8); +} +.hover\\:tw-bg-destructive\\/90:hover { + background-color: hsl(var(--destructive) / 0.9); +} +.hover\\:tw-bg-input:hover { + background-color: hsl(var(--input)); +} +.hover\\:tw-bg-muted:hover { + background-color: hsl(var(--muted)); +} +.hover\\:tw-bg-muted\\/50:hover { + background-color: hsl(var(--muted) / 0.5); +} +.hover\\:tw-bg-muted\\/80:hover { + background-color: hsl(var(--muted) / 0.8); +} +.hover\\:tw-bg-primary\\/10:hover { + background-color: hsl(var(--primary) / 0.1); +} +.hover\\:tw-bg-primary\\/70:hover { + background-color: hsl(var(--primary) / 0.7); +} +.hover\\:tw-bg-primary\\/80:hover { + background-color: hsl(var(--primary) / 0.8); +} +.hover\\:tw-bg-primary\\/90:hover { + background-color: hsl(var(--primary) / 0.9); +} +.hover\\:tw-bg-secondary:hover { + background-color: hsl(var(--secondary)); +} +.hover\\:tw-bg-secondary\\/80:hover { + background-color: hsl(var(--secondary) / 0.8); +} +.hover\\:tw-bg-sidebar-accent:hover { + background-color: hsl(var(--sidebar-accent)); +} +.hover\\:tw-bg-transparent:hover { + background-color: transparent; +} +.hover\\:tw-text-accent-foreground:hover { + color: hsl(var(--accent-foreground)); +} +.hover\\:tw-text-foreground:hover { + color: hsl(var(--foreground)); +} +.hover\\:tw-text-muted-foreground:hover { + color: hsl(var(--muted-foreground)); +} +.hover\\:tw-text-primary-foreground:hover { + color: hsl(var(--primary-foreground)); +} +.hover\\:tw-text-sidebar-accent-foreground:hover { + color: hsl(var(--sidebar-accent-foreground)); +} +.hover\\:tw-underline:hover { + text-decoration-line: underline; +} +.hover\\:tw-opacity-100:hover { + opacity: 1; +} +.hover\\:tw-opacity-80:hover { + opacity: 0.8; +} +.hover\\:tw-shadow-\\[0_0_0_1px_hsl\\(var\\(--sidebar-accent\\)\\)\\]:hover { + --tw-shadow: 0 0 0 1px hsl(var(--sidebar-accent)); + --tw-shadow-colored: 0 0 0 1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.hover\\:tw-shadow-md:hover { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.hover\\:after\\:tw-bg-sidebar-border:hover::after { + content: var(--tw-content); + background-color: hsl(var(--sidebar-border)); +} +.focus\\:tw-relative:focus { + position: relative; +} +.focus\\:tw-z-10:focus { + z-index: 10; +} +.focus\\:tw-bg-accent:focus { + background-color: hsl(var(--accent)); +} +.focus\\:tw-bg-muted:focus { + background-color: hsl(var(--muted)); +} +.focus\\:tw-text-accent-foreground:focus { + color: hsl(var(--accent-foreground)); +} +.focus\\:tw-text-foreground:focus { + color: hsl(var(--foreground)); +} +.focus\\:tw-outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} +.focus\\:tw-ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.focus\\:tw-ring-ring:focus { + --tw-ring-color: hsl(var(--ring)); +} +.focus\\:tw-ring-offset-1:focus { + --tw-ring-offset-width: 1px; +} +.focus\\:tw-ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} +.focus\\:tw-ring-offset-background:focus { + --tw-ring-offset-color: hsl(var(--background)); +} +.focus-visible\\:tw-relative:focus-visible { + position: relative; +} +.focus-visible\\:tw-z-10:focus-visible { + z-index: 10; +} +.focus-visible\\:tw-outline-none:focus-visible { + outline: 2px solid transparent; + outline-offset: 2px; +} +.focus-visible\\:tw-ring-1:focus-visible { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.focus-visible\\:tw-ring-2:focus-visible { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} +.focus-visible\\:tw-ring-\\[color\\:hsl\\(2400o2c 5\\%0o2c 64\\.9\\%\\)\\]:focus-visible { + --tw-ring-opacity: 1; + --tw-ring-color: hsl(240 5% 64.9% / var(--tw-ring-opacity, 1)); +} +.focus-visible\\:tw-ring-ring:focus-visible { + --tw-ring-color: hsl(var(--ring)); +} +.focus-visible\\:tw-ring-sidebar-ring:focus-visible { + --tw-ring-color: hsl(var(--sidebar-ring)); +} +.focus-visible\\:tw-ring-offset-1:focus-visible { + --tw-ring-offset-width: 1px; +} +.focus-visible\\:tw-ring-offset-2:focus-visible { + --tw-ring-offset-width: 2px; +} +.focus-visible\\:tw-ring-offset-background:focus-visible { + --tw-ring-offset-color: hsl(var(--background)); +} +.active\\:tw-bg-sidebar-accent:active { + background-color: hsl(var(--sidebar-accent)); +} +.active\\:tw-text-sidebar-accent-foreground:active { + color: hsl(var(--sidebar-accent-foreground)); +} +.disabled\\:tw-pointer-events-none:disabled { + pointer-events: none; +} +.disabled\\:tw-cursor-not-allowed:disabled { + cursor: not-allowed; +} +.disabled\\:tw-opacity-50:disabled { + opacity: 0.5; +} +.tw-group:hover .group-hover\\:tw-visible { + visibility: visible; +} +.tw-group:hover .group-hover\\:tw-opacity-100 { + opacity: 1; +} +.tw-peer:disabled ~ .peer-disabled\\:tw-cursor-not-allowed { + cursor: not-allowed; +} +.tw-peer:disabled ~ .peer-disabled\\:tw-opacity-70 { + opacity: 0.7; +} +.has-\\[\\>\\[data-slot\\=button-group\\]\\]\\:tw-gap-2:has(>[data-slot=button-group]) { + gap: 0.5rem; +} +.has-\\[\\[data-variant\\=inset\\]\\]\\:tw-bg-sidebar:has([data-variant=inset]) { + background-color: hsl(var(--sidebar-background)); +} +.aria-disabled\\:tw-pointer-events-none[aria-disabled="true"] { + pointer-events: none; +} +.aria-disabled\\:tw-opacity-50[aria-disabled="true"] { + opacity: 0.5; +} +.data-\\[disabled\\=true\\]\\:tw-pointer-events-none[data-disabled="true"] { + pointer-events: none; +} +.data-\\[disabled\\]\\:tw-pointer-events-none[data-disabled] { + pointer-events: none; +} +.data-\\[orientation\\=vertical\\]\\:tw-h-auto[data-orientation="vertical"] { + height: auto; +} +.data-\\[panel-group-direction\\=vertical\\]\\:tw-h-px[data-panel-group-direction="vertical"] { + height: 1px; +} +.data-\\[panel-group-direction\\=vertical\\]\\:tw-w-full[data-panel-group-direction="vertical"] { + width: 100%; +} +.data-\\[side\\=bottom\\]\\:tw-translate-y-1[data-side="bottom"] { + --tw-translate-y: 0.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[side\\=left\\]\\:tw--translate-x-1[data-side="left"] { + --tw-translate-x: -0.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[side\\=right\\]\\:tw-translate-x-1[data-side="right"] { + --tw-translate-x: 0.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[side\\=top\\]\\:tw--translate-y-1[data-side="top"] { + --tw-translate-y: -0.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[state\\=checked\\]\\:tw-translate-x-5[data-state="checked"] { + --tw-translate-x: 1.25rem; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[state\\=checked\\]\\:tw-translate-x-\\[-20px\\][data-state="checked"] { + --tw-translate-x: -20px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[state\\=unchecked\\]\\:tw-translate-x-0[data-state="unchecked"] { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[panel-group-direction\\=vertical\\]\\:tw-flex-col[data-panel-group-direction="vertical"] { + flex-direction: column; +} +.data-\\[active\\=true\\]\\:tw-bg-sidebar-accent[data-active="true"] { + background-color: hsl(var(--sidebar-accent)); +} +.data-\\[highlighted\\]\\:tw-bg-accent[data-highlighted] { + background-color: hsl(var(--accent)); +} +.data-\\[selected\\=true\\]\\:tw-bg-accent[data-selected="true"] { + background-color: hsl(var(--accent)); +} +.data-\\[state\\=active\\]\\:tw-bg-background[data-state="active"] { + background-color: hsl(var(--background)); +} +.data-\\[state\\=checked\\]\\:tw-bg-primary[data-state="checked"] { + background-color: hsl(var(--primary)); +} +.data-\\[state\\=on\\]\\:tw-bg-accent[data-state="on"] { + background-color: hsl(var(--accent)); +} +.data-\\[state\\=open\\]\\:tw-bg-accent[data-state="open"] { + background-color: hsl(var(--accent)); +} +.data-\\[state\\=open\\]\\:tw-bg-muted[data-state="open"] { + background-color: hsl(var(--muted)); +} +.data-\\[state\\=selected\\]\\:tw-bg-muted[data-state="selected"] { + background-color: hsl(var(--muted)); +} +.data-\\[state\\=unchecked\\]\\:tw-bg-input[data-state="unchecked"] { + background-color: hsl(var(--input)); +} +.data-\\[active\\=true\\]\\:tw-font-medium[data-active="true"] { + font-weight: 500; +} +.data-\\[active\\=true\\]\\:tw-text-sidebar-accent-foreground[data-active="true"] { + color: hsl(var(--sidebar-accent-foreground)); +} +.data-\\[highlighted\\]\\:tw-text-accent-foreground[data-highlighted] { + color: hsl(var(--accent-foreground)); +} +.data-\\[selected\\=true\\]\\:tw-text-accent-foreground[data-selected="true"] { + color: hsl(var(--accent-foreground)); +} +.data-\\[state\\=active\\]\\:tw-text-foreground[data-state="active"] { + color: hsl(var(--foreground)); +} +.data-\\[state\\=checked\\]\\:tw-text-primary-foreground[data-state="checked"] { + color: hsl(var(--primary-foreground)); +} +.data-\\[state\\=on\\]\\:tw-text-accent-foreground[data-state="on"] { + color: hsl(var(--accent-foreground)); +} +.data-\\[state\\=open\\]\\:tw-text-accent-foreground[data-state="open"] { + color: hsl(var(--accent-foreground)); +} +.data-\\[state\\=open\\]\\:tw-text-foreground[data-state="open"] { + color: hsl(var(--foreground)); +} +.data-\\[state\\=open\\]\\:tw-text-muted-foreground[data-state="open"] { + color: hsl(var(--muted-foreground)); +} +.data-\\[disabled\\=true\\]\\:tw-opacity-50[data-disabled="true"] { + opacity: 0.5; +} +.data-\\[disabled\\]\\:tw-opacity-50[data-disabled] { + opacity: 0.5; +} +.data-\\[state\\=open\\]\\:tw-opacity-100[data-state="open"] { + opacity: 1; +} +.data-\\[state\\=active\\]\\:tw-shadow-sm[data-state="active"] { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.data-\\[state\\=open\\]\\:tw-animate-in[data-state="open"] { + animation-name: enter; + animation-duration: 150ms; + --tw-enter-opacity: initial; + --tw-enter-scale: initial; + --tw-enter-rotate: initial; + --tw-enter-translate-x: initial; + --tw-enter-translate-y: initial; +} +.data-\\[state\\=closed\\]\\:tw-animate-out[data-state="closed"] { + animation-name: exit; + animation-duration: 150ms; + --tw-exit-opacity: initial; + --tw-exit-scale: initial; + --tw-exit-rotate: initial; + --tw-exit-translate-x: initial; + --tw-exit-translate-y: initial; +} +.data-\\[state\\=closed\\]\\:tw-fade-out-0[data-state="closed"] { + --tw-exit-opacity: 0; +} +.data-\\[state\\=open\\]\\:tw-fade-in-0[data-state="open"] { + --tw-enter-opacity: 0; +} +.data-\\[state\\=closed\\]\\:tw-zoom-out-95[data-state="closed"] { + --tw-exit-scale: .95; +} +.data-\\[state\\=open\\]\\:tw-zoom-in-95[data-state="open"] { + --tw-enter-scale: .95; +} +.data-\\[side\\=bottom\\]\\:tw-slide-in-from-top-2[data-side="bottom"] { + --tw-enter-translate-y: -0.5rem; +} +.data-\\[side\\=left\\]\\:tw-slide-in-from-right-2[data-side="left"] { + --tw-enter-translate-x: 0.5rem; +} +.data-\\[side\\=right\\]\\:tw-slide-in-from-left-2[data-side="right"] { + --tw-enter-translate-x: -0.5rem; +} +.data-\\[side\\=top\\]\\:tw-slide-in-from-bottom-2[data-side="top"] { + --tw-enter-translate-y: 0.5rem; +} +.data-\\[state\\=closed\\]\\:tw-slide-out-to-left-1\\/2[data-state="closed"] { + --tw-exit-translate-x: -50%; +} +.data-\\[state\\=closed\\]\\:tw-slide-out-to-top-\\[48\\%\\][data-state="closed"] { + --tw-exit-translate-y: -48%; +} +.data-\\[state\\=open\\]\\:tw-slide-in-from-left-1\\/2[data-state="open"] { + --tw-enter-translate-x: -50%; +} +.data-\\[state\\=open\\]\\:tw-slide-in-from-top-\\[48\\%\\][data-state="open"] { + --tw-enter-translate-y: -48%; +} +.data-\\[panel-group-direction\\=vertical\\]\\:after\\:tw-left-0[data-panel-group-direction="vertical"]::after { + content: var(--tw-content); + left: 0px; +} +.data-\\[panel-group-direction\\=vertical\\]\\:after\\:tw-h-1[data-panel-group-direction="vertical"]::after { + content: var(--tw-content); + height: 0.25rem; +} +.data-\\[panel-group-direction\\=vertical\\]\\:after\\:tw-w-full[data-panel-group-direction="vertical"]::after { + content: var(--tw-content); + width: 100%; +} +.data-\\[panel-group-direction\\=vertical\\]\\:after\\:tw--translate-y-1\\/2[data-panel-group-direction="vertical"]::after { + content: var(--tw-content); + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[panel-group-direction\\=vertical\\]\\:after\\:tw-translate-x-0[data-panel-group-direction="vertical"]::after { + content: var(--tw-content); + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.data-\\[state\\=open\\]\\:hover\\:tw-bg-sidebar-accent:hover[data-state="open"] { + background-color: hsl(var(--sidebar-accent)); +} +.data-\\[state\\=open\\]\\:hover\\:tw-text-sidebar-accent-foreground:hover[data-state="open"] { + color: hsl(var(--sidebar-accent-foreground)); +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:tw-left-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\] { + left: calc(var(--sidebar-width) * -1); +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:tw-right-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\] { + right: calc(var(--sidebar-width) * -1); +} +.tw-group[data-side="primary"] .group-data-\\[side\\=primary\\]\\:tw--right-4 { + right: -1rem; +} +.tw-group[data-side="secondary"] .group-data-\\[side\\=secondary\\]\\:tw-left-0 { + left: 0px; +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw--mt-8 { + margin-top: -2rem; +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-hidden { + display: none; +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-w-\\[--sidebar-width-icon\\] { + width: var(--sidebar-width-icon); +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-w-\\[calc\\(var\\(--sidebar-width-icon\\)_\\+_theme\\(spacing\\.4\\)\\)\\] { + width: calc(var(--sidebar-width-icon) + 1rem); +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-w-\\[calc\\(var\\(--sidebar-width-icon\\)_\\+_theme\\(spacing\\.4\\)_\\+2px\\)\\] { + width: calc(var(--sidebar-width-icon) + 1rem + 2px); +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:tw-w-0 { + width: 0px; +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:tw-translate-x-0 { + --tw-translate-x: 0px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-group[data-side="secondary"] .group-data-\\[side\\=secondary\\]\\:tw-rotate-180 { + --tw-rotate: 180deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-overflow-hidden { + overflow: hidden; +} +.tw-group[data-variant="floating"] .group-data-\\[variant\\=floating\\]\\:tw-rounded-lg { + border-radius: var(--radius); +} +.tw-group[data-variant="floating"] .group-data-\\[variant\\=floating\\]\\:tw-border { + border-width: 1px; +} +.tw-group[data-side="primary"] .group-data-\\[side\\=primary\\]\\:tw-border-r { + border-right-width: 1px; +} +.tw-group[data-side="secondary"] .group-data-\\[side\\=secondary\\]\\:tw-border-l { + border-left-width: 1px; +} +.tw-group[data-variant="floating"] .group-data-\\[variant\\=floating\\]\\:tw-border-sidebar-border { + border-color: hsl(var(--sidebar-border)); +} +.tw-group[data-collapsible="icon"] .group-data-\\[collapsible\\=icon\\]\\:tw-opacity-0 { + opacity: 0; +} +.tw-group[data-variant="floating"] .group-data-\\[variant\\=floating\\]\\:tw-shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:after\\:tw-left-full::after { + content: var(--tw-content); + left: 100%; +} +.tw-group[data-collapsible="offcanvas"] .group-data-\\[collapsible\\=offcanvas\\]\\:hover\\:tw-bg-sidebar:hover { + background-color: hsl(var(--sidebar-background)); +} +.tw-peer[data-variant="inset"] ~ .peer-data-\\[variant\\=inset\\]\\:tw-min-h-\\[calc\\(100svh-theme\\(spacing\\.4\\)\\)\\] { + min-height: calc(100svh - 1rem); +} +@container (min-width: 24rem) { + + .\\@sm\\:tw-basis-auto { + flex-basis: auto; + } +} +@media (min-width: 640px) { + + .sm\\:tw-flex { + display: flex; + } + + .sm\\:tw-flex-row { + flex-direction: row; + } + + .sm\\:tw-justify-end { + justify-content: flex-end; + } + + .sm\\:tw-space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); + } + + .sm\\:tw-rounded-lg { + border-radius: var(--radius); + } + + .sm\\:tw-text-left { + text-align: left; + } + + .sm\\:tw-text-start { + text-align: start; + } +} +@media (min-width: 768px) { + + .md\\:tw-block { + display: block; + } + + .md\\:tw-flex { + display: flex; + } + + .md\\:tw-grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\\:tw-text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .md\\:tw-opacity-0 { + opacity: 0; + } + + .after\\:md\\:tw-hidden::after { + content: var(--tw-content); + display: none; + } + + .tw-peer[data-variant="inset"] ~ .md\\:peer-data-\\[variant\\=inset\\]\\:tw-m-2 { + margin: 0.5rem; + } + + .tw-peer[data-state="collapsed"][data-variant="inset"] ~ .md\\:peer-data-\\[state\\=collapsed\\]\\:peer-data-\\[variant\\=inset\\]\\:tw-ml-2 { + margin-left: 0.5rem; + } + + .tw-peer[data-variant="inset"] ~ .md\\:peer-data-\\[variant\\=inset\\]\\:tw-ml-0 { + margin-left: 0px; + } + + .tw-peer[data-variant="inset"] ~ .md\\:peer-data-\\[variant\\=inset\\]\\:tw-rounded-xl { + border-radius: 0.75rem; + } + + .tw-peer[data-variant="inset"] ~ .md\\:peer-data-\\[variant\\=inset\\]\\:tw-shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + } +} +@media (min-width: 1024px) { + + .lg\\:tw-flex { + display: flex; + } + + .lg\\:tw-grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lg\\:tw-space-x-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(2rem * var(--tw-space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); + } + + .lg\\:tw-text-5xl { + font-size: 3rem; + line-height: 1; + } +} +.ltr\\:tw-left-2:where([dir="ltr"], [dir="ltr"] *) { + left: 0.5rem; +} +.rtl\\:tw-right-2:where([dir="rtl"], [dir="rtl"] *) { + right: 0.5rem; +} +@media (prefers-color-scheme: dark) { + + .dark\\:tw-border-destructive { + border-color: hsl(var(--destructive)); + } + + .dark\\:tw-text-rose-400 { + --tw-text-opacity: 1; + color: rgb(251 113 133 / var(--tw-text-opacity, 1)); + } + + .dark\\:tw-text-sky-400 { + --tw-text-opacity: 1; + color: rgb(56 189 248 / var(--tw-text-opacity, 1)); + } + + .dark\\:tw-text-teal-400 { + --tw-text-opacity: 1; + color: rgb(45 212 191 / var(--tw-text-opacity, 1)); + } +} +.\\[\\&\\:has\\(\\[role\\=checkbox\\]\\)\\]\\:tw-pe-0:has([role=checkbox]) { + padding-inline-end: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:tw-rounded-l-none>*:not(:first-child) { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:tw-rounded-t-none>*:not(:first-child) { + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:tw-border-l-0>*:not(:first-child) { + border-left-width: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:tw-border-t-0>*:not(:first-child) { + border-top-width: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:tw-rounded-b-none>*:not(:last-child) { + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} +.\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:tw-rounded-r-none>*:not(:last-child) { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} +.\\[\\&\\>\\*\\]\\:focus-visible\\:tw-relative:focus-visible>* { + position: relative; +} +.\\[\\&\\>\\*\\]\\:focus-visible\\:tw-z-10:focus-visible>* { + z-index: 10; +} +.has-\\[select\\[aria-hidden\\=true\\]\\:last-child\\]\\:\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:last-of-type\\]\\:tw-rounded-r-md>[data-slot=select-trigger]:last-of-type:has(select[aria-hidden=true]:last-child) { + border-top-right-radius: calc(var(--radius) - 2px); + border-bottom-right-radius: calc(var(--radius) - 2px); +} +.\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:not\\(\\[class\\*\\=w-\\]\\)\\]\\:tw-w-fit>[data-slot=select-trigger]:not([class*=w-]) { + width: fit-content; +} +.\\[\\&\\>blockquote\\]\\:tw-border-s-0>blockquote { + border-inline-start-width: 0px; +} +.\\[\\&\\>blockquote\\]\\:tw-p-0>blockquote { + padding: 0px; +} +.\\[\\&\\>blockquote\\]\\:tw-ps-0>blockquote { + padding-inline-start: 0px; +} +.\\[\\&\\>blockquote\\]\\:tw-font-normal>blockquote { + font-weight: 400; +} +.\\[\\&\\>blockquote\\]\\:tw-not-italic>blockquote { + font-style: normal; +} +.\\[\\&\\>blockquote\\]\\:tw-text-foreground>blockquote { + color: hsl(var(--foreground)); +} +.\\[\\&\\>img\\+div\\]\\:tw-translate-y-\\[-3px\\]>img+div { + --tw-translate-y: -3px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.\\[\\&\\>img\\]\\:tw-absolute>img { + position: absolute; +} +.\\[\\&\\>img\\]\\:tw-left-4>img { + left: 1rem; +} +.\\[\\&\\>img\\]\\:tw-top-4>img { + top: 1rem; +} +.\\[\\&\\>img\\]\\:tw-text-destructive>img { + color: hsl(var(--destructive)); +} +.\\[\\&\\>img\\]\\:tw-text-foreground>img { + color: hsl(var(--foreground)); +} +.\\[\\&\\>img\\~\\*\\]\\:tw-pl-7>img~* { + padding-left: 1.75rem; +} +.\\[\\&\\>input\\]\\:tw-flex-1>input { + flex: 1 1 0%; +} +.\\[\\&\\>li\\]\\:tw-mt-2>li { + margin-top: 0.5rem; +} +.\\[\\&\\>span\\:last-child\\]\\:tw-truncate>span:last-child { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.\\[\\&\\>span\\]\\:tw-line-clamp-1>span { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; +} +.\\[\\&\\>span\\]\\:tw-flex-1>span { + flex: 1 1 0%; +} +.\\[\\&\\>span\\]\\:tw-text-start>span { + text-align: start; +} +.\\[\\&\\>svg\\+div\\]\\:tw-translate-y-\\[-3px\\]>svg+div { + --tw-translate-y: -3px; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.\\[\\&\\>svg\\]\\:tw-absolute>svg { + position: absolute; +} +.\\[\\&\\>svg\\]\\:tw-left-4>svg { + left: 1rem; +} +.\\[\\&\\>svg\\]\\:tw-top-4>svg { + top: 1rem; +} +.\\[\\&\\>svg\\]\\:tw-size-4>svg { + width: 1rem; + height: 1rem; +} +.\\[\\&\\>svg\\]\\:tw-shrink-0>svg { + flex-shrink: 0; +} +.\\[\\&\\>svg\\]\\:tw-text-destructive>svg { + color: hsl(var(--destructive)); +} +.\\[\\&\\>svg\\]\\:tw-text-foreground>svg { + color: hsl(var(--foreground)); +} +.\\[\\&\\>svg\\]\\:tw-text-sidebar-accent-foreground>svg { + color: hsl(var(--sidebar-accent-foreground)); +} +.\\[\\&\\>svg\\~\\*\\]\\:tw-pl-7>svg~* { + padding-left: 1.75rem; +} +.\\[\\&\\>tr\\]\\:last\\:tw-border-b-0:last-child>tr { + border-bottom-width: 0px; +} +.\\[\\&\\[align\\=center\\]\\]\\:tw-text-center[align=center] { + text-align: center; +} +.\\[\\&\\[align\\=right\\]\\]\\:tw-text-right[align=right] { + text-align: right; +} +.\\[\\&\\[data-panel-group-direction\\=vertical\\]\\>div\\]\\:tw-rotate-90[data-panel-group-direction=vertical]>div { + --tw-rotate: 90deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} +.\\[\\&_\\[cmdk-group-heading\\]\\]\\:tw-px-2 [cmdk-group-heading] { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.\\[\\&_\\[cmdk-group-heading\\]\\]\\:tw-py-1\\.5 [cmdk-group-heading] { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} +.\\[\\&_\\[cmdk-group-heading\\]\\]\\:tw-text-xs [cmdk-group-heading] { + font-size: 0.75rem; + line-height: 1rem; +} +.\\[\\&_\\[cmdk-group-heading\\]\\]\\:tw-font-medium [cmdk-group-heading] { + font-weight: 500; +} +.\\[\\&_\\[cmdk-group-heading\\]\\]\\:tw-text-muted-foreground [cmdk-group-heading] { + color: hsl(var(--muted-foreground)); +} +.\\[\\&_\\[cmdk-group\\]\\:not\\(\\[hidden\\]\\)_\\~\\[cmdk-group\\]\\]\\:tw-pt-0 [cmdk-group]:not([hidden]) ~[cmdk-group] { + padding-top: 0px; +} +.\\[\\&_\\[cmdk-group\\]\\]\\:tw-px-2 [cmdk-group] { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.\\[\\&_\\[cmdk-input-wrapper\\]_svg\\]\\:tw-h-5 [cmdk-input-wrapper] svg { + height: 1.25rem; +} +.\\[\\&_\\[cmdk-input-wrapper\\]_svg\\]\\:tw-w-5 [cmdk-input-wrapper] svg { + width: 1.25rem; +} +.\\[\\&_\\[cmdk-input\\]\\]\\:tw-h-12 [cmdk-input] { + height: 3rem; +} +.\\[\\&_\\[cmdk-item\\]\\]\\:tw-px-2 [cmdk-item] { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.\\[\\&_\\[cmdk-item\\]\\]\\:tw-py-3 [cmdk-item] { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} +.\\[\\&_\\[cmdk-item\\]_svg\\]\\:tw-h-5 [cmdk-item] svg { + height: 1.25rem; +} +.\\[\\&_\\[cmdk-item\\]_svg\\]\\:tw-w-5 [cmdk-item] svg { + width: 1.25rem; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-mt-0 [data-lexical-editor="true"]>blockquote { + margin-top: 0px; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-border-s-0 [data-lexical-editor="true"]>blockquote { + border-inline-start-width: 0px; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-ps-0 [data-lexical-editor="true"]>blockquote { + padding-inline-start: 0px; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-font-normal [data-lexical-editor="true"]>blockquote { + font-weight: 400; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-not-italic [data-lexical-editor="true"]>blockquote { + font-style: normal; +} +.\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:tw-text-foreground [data-lexical-editor="true"]>blockquote { + color: hsl(var(--foreground)); +} +.\\[\\&_p\\]\\:tw-leading-relaxed p { + line-height: 1.625; +} +.\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:tw-size-4 svg:not([class*=size-]) { + width: 1rem; + height: 1rem; +} +.\\[\\&_svg\\]\\:tw-pointer-events-none svg { + pointer-events: none; +} +.\\[\\&_svg\\]\\:tw-size-4 svg { + width: 1rem; + height: 1rem; +} +.\\[\\&_svg\\]\\:tw-shrink-0 svg { + flex-shrink: 0; +} +.\\[\\&_tr\\:last-child\\]\\:tw-border-0 tr:last-child { + border-width: 0px; +} +.\\[\\&_tr\\]\\:tw-border-b tr { + border-bottom-width: 1px; +} +[data-side=primary][data-collapsible=offcanvas] .\\[\\[data-side\\=primary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:tw--right-2 { + right: -0.5rem; +} +[data-side=primary][data-state=collapsed] .\\[\\[data-side\\=primary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:tw-cursor-e-resize { + cursor: e-resize; +} +[data-side=secondary][data-collapsible=offcanvas] .\\[\\[data-side\\=secondary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:tw--left-2 { + left: -0.5rem; +} +[data-side=secondary][data-state=collapsed] .\\[\\[data-side\\=secondary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:tw-cursor-w-resize { + cursor: w-resize; +} +[data-side=secondary] .\\[\\[data-side\\=secondary\\]_\\&\\]\\:tw-cursor-e-resize { + cursor: e-resize; +} +[data-side=secondary] .\\[\\[data-side\\=secondary\\]_\\&\\]\\:tw-cursor-w-resize { + cursor: w-resize; +} +/* By default the editor is too tall for the footnote editor, even while empty, so this makes it shorter. */ .footnote-editor .editor-input { min-height: 75px; @@ -153,6 +4590,6 @@ list-style-type: disclosure-open; } /*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-outline-style:solid;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--tw-font-sans:"IBM Plex Sans Variable", sans-serif;--tw-font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--tw-color-red-100:oklch(93.6% .032 17.717);--tw-color-red-200:oklch(88.5% .062 18.334);--tw-color-red-300:oklch(80.8% .114 19.571);--tw-color-red-400:oklch(70.4% .191 22.216);--tw-color-red-500:oklch(63.7% .237 25.331);--tw-color-red-600:oklch(57.7% .245 27.325);--tw-color-red-700:oklch(50.5% .213 27.518);--tw-color-red-800:oklch(44.4% .177 26.899);--tw-color-orange-100:oklch(95.4% .038 75.164);--tw-color-orange-800:oklch(47% .157 37.304);--tw-color-amber-200:oklch(92.4% .12 95.746);--tw-color-yellow-50:oklch(98.7% .026 102.212);--tw-color-yellow-100:oklch(97.3% .071 103.193);--tw-color-yellow-400:oklch(85.2% .199 91.936);--tw-color-yellow-500:oklch(79.5% .184 86.047);--tw-color-yellow-600:oklch(68.1% .162 75.834);--tw-color-yellow-700:oklch(55.4% .135 66.442);--tw-color-green-50:oklch(98.2% .018 155.826);--tw-color-green-100:oklch(96.2% .044 156.743);--tw-color-green-500:oklch(72.3% .219 149.579);--tw-color-green-600:oklch(62.7% .194 149.214);--tw-color-green-700:oklch(52.7% .154 150.069);--tw-color-green-800:oklch(44.8% .119 151.328);--tw-color-teal-400:oklch(77.7% .152 181.912);--tw-color-teal-500:oklch(70.4% .14 182.503);--tw-color-teal-600:oklch(60% .118 184.704);--tw-color-sky-400:oklch(74.6% .16 232.661);--tw-color-sky-500:oklch(68.5% .169 237.323);--tw-color-sky-600:oklch(58.8% .158 241.966);--tw-color-blue-50:oklch(97% .014 254.604);--tw-color-blue-100:oklch(93.2% .032 255.585);--tw-color-blue-400:oklch(70.7% .165 254.624);--tw-color-blue-500:oklch(62.3% .214 259.815);--tw-color-blue-600:oklch(54.6% .245 262.881);--tw-color-blue-800:oklch(42.4% .199 265.638);--tw-color-indigo-200:oklch(87% .065 274.039);--tw-color-purple-50:oklch(97.7% .014 308.299);--tw-color-purple-200:oklch(90.2% .063 306.703);--tw-color-purple-900:oklch(38.1% .176 304.987);--tw-color-rose-400:oklch(71.2% .194 13.428);--tw-color-rose-500:oklch(64.5% .246 16.439);--tw-color-rose-600:oklch(58.6% .253 17.585);--tw-color-slate-300:oklch(86.9% .022 252.894);--tw-color-slate-900:oklch(20.8% .042 265.755);--tw-color-gray-50:oklch(98.5% .002 247.839);--tw-color-gray-100:oklch(96.7% .003 264.542);--tw-color-gray-300:oklch(87.2% .01 258.338);--tw-color-gray-500:oklch(55.1% .027 264.364);--tw-color-gray-600:oklch(44.6% .03 256.802);--tw-color-gray-700:oklch(37.3% .034 259.733);--tw-color-gray-800:oklch(27.8% .033 256.848);--tw-color-zinc-400:oklch(70.5% .015 286.067);--tw-color-neutral-300:oklch(87% 0 0);--tw-color-black:#000;--tw-color-white:#fff;--tw-spacing:calc(var(--spacing));--tw-container-xs:20rem;--tw-container-sm:24rem;--tw-container-md:28rem;--tw-container-lg:32rem;--tw-container-2xl:42rem;--tw-container-3xl:48rem;--tw-container-4xl:56rem;--tw-container-6xl:72rem;--tw-text-xs:.75rem;--tw-text-xs--line-height:calc(1 / .75);--tw-text-sm:.875rem;--tw-text-sm--line-height:calc(1.25 / .875);--tw-text-base:1rem;--tw-text-base--line-height:calc(1.5 / 1);--tw-text-lg:1.125rem;--tw-text-lg--line-height:calc(1.75 / 1.125);--tw-text-xl:1.25rem;--tw-text-xl--line-height:calc(1.75 / 1.25);--tw-text-2xl:1.5rem;--tw-text-2xl--line-height:calc(2 / 1.5);--tw-text-3xl:1.875rem;--tw-text-3xl--line-height:calc(2.25 / 1.875);--tw-text-4xl:2.25rem;--tw-text-4xl--line-height:calc(2.5 / 2.25);--tw-text-5xl:3rem;--tw-text-5xl--line-height:1;--tw-font-weight-normal:400;--tw-font-weight-medium:500;--tw-font-weight-semibold:600;--tw-font-weight-bold:700;--tw-font-weight-extrabold:800;--tw-tracking-tight:-.025em;--tw-tracking-widest:.1em;--tw-leading-tight:1.25;--tw-leading-snug:1.375;--tw-leading-relaxed:1.625;--tw-leading-loose:2;--tw-radius-md:calc(var(--radius) * .8);--tw-radius-xl:calc(var(--radius) * 1.4);--tw-radius-2xl:calc(var(--radius) * 1.8);--tw-drop-shadow-sm:0 1px 2px #00000026;--tw-ease-in-out:cubic-bezier(.4, 0, .2, 1);--tw-animate-spin:spin 1s linear infinite;--tw-animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--tw-blur-xs:4px;--tw-blur-2xl:40px;--tw-default-transition-duration:.15s;--tw-default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--tw-default-font-family:"IBM Plex Sans Variable", sans-serif;--tw-default-mono-font-family:var(--tw-font-mono)}}@layer base{.pr-twp,.pr-twp *{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.pr-twp,.pr-twp *{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body.pr-twp{background-color:var(--background);color:var(--foreground)}html.pr-twp{font-family:IBM Plex Sans Variable,sans-serif}:where(.pr-twp,.pr-twp *),:where(.pr-twp,.pr-twp *):after,:where(.pr-twp,.pr-twp *):before,:where(.pr-twp,.pr-twp *) ::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}:where(.pr-twp,.pr-twp *) ::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}.pr-twp{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--tw-default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--tw-default-font-feature-settings,normal);font-variation-settings:var(--tw-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr:where(.pr-twp,.pr-twp *){height:0;color:inherit;border-top-width:1px}abbr:where([title]):where(.pr-twp,.pr-twp *){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1:where(.pr-twp,.pr-twp *),h2:where(.pr-twp,.pr-twp *),h3:where(.pr-twp,.pr-twp *),h4:where(.pr-twp,.pr-twp *),h5:where(.pr-twp,.pr-twp *),h6:where(.pr-twp,.pr-twp *){font-size:inherit;font-weight:inherit}a:where(.pr-twp,.pr-twp *){color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b:where(.pr-twp,.pr-twp *),strong:where(.pr-twp,.pr-twp *){font-weight:bolder}code:where(.pr-twp,.pr-twp *),kbd:where(.pr-twp,.pr-twp *),samp:where(.pr-twp,.pr-twp *),pre:where(.pr-twp,.pr-twp *){font-family:var(--tw-default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--tw-default-mono-font-feature-settings,normal);font-variation-settings:var(--tw-default-mono-font-variation-settings,normal);font-size:1em}small:where(.pr-twp,.pr-twp *){font-size:80%}sub:where(.pr-twp,.pr-twp *),sup:where(.pr-twp,.pr-twp *){vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub:where(.pr-twp,.pr-twp *){bottom:-.25em}sup:where(.pr-twp,.pr-twp *){top:-.5em}table:where(.pr-twp,.pr-twp *){text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(.pr-twp,.pr-twp *){outline:auto}progress:where(.pr-twp,.pr-twp *){vertical-align:baseline}summary:where(.pr-twp,.pr-twp *){display:list-item}ol:where(.pr-twp,.pr-twp *),ul:where(.pr-twp,.pr-twp *),menu:where(.pr-twp,.pr-twp *){list-style:none}img:where(.pr-twp,.pr-twp *),svg:where(.pr-twp,.pr-twp *),video:where(.pr-twp,.pr-twp *),canvas:where(.pr-twp,.pr-twp *),audio:where(.pr-twp,.pr-twp *),iframe:where(.pr-twp,.pr-twp *),embed:where(.pr-twp,.pr-twp *),object:where(.pr-twp,.pr-twp *){vertical-align:middle;display:block}img:where(.pr-twp,.pr-twp *),video:where(.pr-twp,.pr-twp *){max-width:100%;height:auto}button:where(.pr-twp,.pr-twp *),input:where(.pr-twp,.pr-twp *),select:where(.pr-twp,.pr-twp *),optgroup:where(.pr-twp,.pr-twp *),textarea:where(.pr-twp,.pr-twp *){font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(.pr-twp,.pr-twp *) ::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup:where(.pr-twp,.pr-twp *){font-weight:bolder}:where(select:is([multiple],[size])) optgroup option:where(.pr-twp,.pr-twp *){padding-inline-start:20px}:where(.pr-twp,.pr-twp *) ::file-selector-button{margin-inline-end:4px}:where(.pr-twp,.pr-twp *) ::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){:where(.pr-twp,.pr-twp *) ::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){:where(.pr-twp,.pr-twp *) ::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea:where(.pr-twp,.pr-twp *){resize:vertical}:where(.pr-twp,.pr-twp *) ::-webkit-search-decoration{-webkit-appearance:none}:where(.pr-twp,.pr-twp *) ::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit{display:inline-flex}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-fields-wrapper{padding:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-year-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-month-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-day-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-hour-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-minute-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-second-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-millisecond-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-meridiem-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid:where(.pr-twp,.pr-twp *){box-shadow:none}button:where(.pr-twp,.pr-twp *),input:where([type=button],[type=reset],[type=submit]):where(.pr-twp,.pr-twp *){appearance:button}:where(.pr-twp,.pr-twp *) ::file-selector-button{appearance:button}:where(.pr-twp,.pr-twp *) ::-webkit-inner-spin-button{height:auto}:where(.pr-twp,.pr-twp *) ::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])):where(.pr-twp,.pr-twp *){display:none!important}}@layer components;@layer utilities{.tw\\:\\@container\\/card-header{container:card-header/inline-size}.tw\\:\\@container\\/toolbar{container:toolbar/inline-size}.tw\\:pointer-events-auto{pointer-events:auto}.tw\\:pointer-events-none{pointer-events:none}.tw\\:invisible{visibility:hidden}.tw\\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.tw\\:absolute{position:absolute}.tw\\:fixed{position:fixed}.tw\\:relative{position:relative}.tw\\:sticky{position:sticky}.tw\\:inset-0{inset:calc(calc(var(--spacing)) * 0)}.tw\\:inset-y-0{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:start-1\\.5{inset-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:start-1\\/2{inset-inline-start:50%}.tw\\:end-0{inset-inline-end:calc(calc(var(--spacing)) * 0)}.tw\\:end-1{inset-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:end-2{inset-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:end-3{inset-inline-end:calc(calc(var(--spacing)) * 3)}.tw\\:-top-\\[1px\\]{top:-1px}.tw\\:top-0{top:calc(calc(var(--spacing)) * 0)}.tw\\:top-1\\.5{top:calc(calc(var(--spacing)) * 1.5)}.tw\\:top-1\\/2{top:50%}.tw\\:top-1\\/3{top:33.3333%}.tw\\:top-2{top:calc(calc(var(--spacing)) * 2)}.tw\\:top-2\\.5{top:calc(calc(var(--spacing)) * 2.5)}.tw\\:top-3\\.5{top:calc(calc(var(--spacing)) * 3.5)}.tw\\:top-\\[-1px\\]{top:-1px}.tw\\:-right-1{right:calc(calc(var(--spacing)) * -1)}.tw\\:right-0{right:calc(calc(var(--spacing)) * 0)}.tw\\:right-1{right:calc(calc(var(--spacing)) * 1)}.tw\\:right-3{right:calc(calc(var(--spacing)) * 3)}.tw\\:bottom-0{bottom:calc(calc(var(--spacing)) * 0)}.tw\\:-left-\\[1px\\]{left:-1px}.tw\\:left-0{left:calc(calc(var(--spacing)) * 0)}.tw\\:left-2{left:calc(calc(var(--spacing)) * 2)}.tw\\:left-3{left:calc(calc(var(--spacing)) * 3)}.tw\\:isolate{isolation:isolate}.tw\\:z-10{z-index:10}.tw\\:z-20{z-index:20}.tw\\:z-50{z-index:50}.tw\\:order-first{order:-9999}.tw\\:order-last{order:9999}.tw\\:col-span-1{grid-column:span 1/span 1}.tw\\:col-span-2{grid-column:span 2/span 2}.tw\\:col-span-3{grid-column:span 3/span 3}.tw\\:col-start-1{grid-column-start:1}.tw\\:col-start-2{grid-column-start:2}.tw\\:row-span-2{grid-row:span 2/span 2}.tw\\:row-start-1{grid-row-start:1}.tw\\:row-start-2{grid-row-start:2}.tw\\:m-0{margin:calc(calc(var(--spacing)) * 0)}.tw\\:m-1{margin:calc(calc(var(--spacing)) * 1)}.tw\\:m-2{margin:calc(calc(var(--spacing)) * 2)}.tw\\:-mx-1{margin-inline:calc(calc(var(--spacing)) * -1)}.tw\\:-mx-4{margin-inline:calc(calc(var(--spacing)) * -4)}.tw\\:mx-0{margin-inline:calc(calc(var(--spacing)) * 0)}.tw\\:mx-1{margin-inline:calc(calc(var(--spacing)) * 1)}.tw\\:mx-2{margin-inline:calc(calc(var(--spacing)) * 2)}.tw\\:mx-3\\.5{margin-inline:calc(calc(var(--spacing)) * 3.5)}.tw\\:mx-8{margin-inline:calc(calc(var(--spacing)) * 8)}.tw\\:my-1{margin-block:calc(calc(var(--spacing)) * 1)}.tw\\:my-2\\.5{margin-block:calc(calc(var(--spacing)) * 2.5)}.tw\\:my-4{margin-block:calc(calc(var(--spacing)) * 4)}.tw\\:ms-1{margin-inline-start:calc(calc(var(--spacing)) * 1)}.tw\\:ms-2{margin-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:ms-5{margin-inline-start:calc(calc(var(--spacing)) * 5)}.tw\\:ms-auto{margin-inline-start:auto}.tw\\:me-1{margin-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:me-2{margin-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:prose{color:var(--tw-prose-body);max-width:65ch}.tw\\:prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.tw\\:prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);margin-top:1.2em;margin-bottom:1.2em;font-size:1.25em;line-height:1.6}.tw\\:prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.tw\\:prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.tw\\:prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:decimal}.tw\\:prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.tw\\:prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.tw\\:prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.tw\\:prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.tw\\:prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.tw\\:prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.tw\\:prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.tw\\:prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.tw\\:prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.tw\\:prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:disc}.tw\\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.tw\\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.tw\\:prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.25em;font-weight:600}.tw\\:prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.tw\\:prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em;font-style:italic;font-weight:500}.tw\\:prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.tw\\:prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.tw\\:prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:0;margin-bottom:.888889em;font-size:2.25em;font-weight:800;line-height:1.11111}.tw\\:prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.tw\\:prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:2em;margin-bottom:1em;font-size:1.5em;font-weight:700;line-height:1.33333}.tw\\:prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.tw\\:prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.6em;margin-bottom:.6em;font-size:1.25em;font-weight:600;line-height:1.6}.tw\\:prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.tw\\:prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.5em;margin-bottom:.5em;font-weight:600;line-height:1.5}.tw\\:prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.tw\\:prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em;display:block}.tw\\:prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-kbd);box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%);padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;border-radius:.3125rem;padding-inline-start:.375em;font-family:inherit;font-size:.875em;font-weight:500}.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"\`"}.tw\\:prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.tw\\:prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.tw\\:prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);padding-top:.857143em;padding-inline-end:1.14286em;padding-bottom:.857143em;border-radius:.375rem;margin-top:1.71429em;margin-bottom:1.71429em;padding-inline-start:1.14286em;font-size:.875em;font-weight:400;line-height:1.71429;overflow-x:auto}.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit;background-color:#0000;border-width:0;border-radius:0;padding:0}.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.tw\\:prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){table-layout:auto;width:100%;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.71429}.tw\\:prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.tw\\:prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);vertical-align:bottom;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em;font-weight:600}.tw\\:prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.tw\\:prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.tw\\:prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.tw\\:prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.tw\\:prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.tw\\:prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.tw\\:prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);margin-top:.857143em;font-size:.875em;line-height:1.42857}.tw\\:prose{--tw-prose-body:var(--foreground);--tw-prose-headings:var(--foreground);--tw-prose-lead:var(--muted-foreground);--tw-prose-links:var(--primary);--tw-prose-bold:var(--foreground);--tw-prose-counters:var(--muted-foreground);--tw-prose-bullets:var(--muted-foreground);--tw-prose-hr:var(--border);--tw-prose-quotes:var(--foreground);--tw-prose-quote-borders:var(--border);--tw-prose-captions:var(--muted-foreground);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:var(--foreground);--tw-prose-pre-code:var(--muted-foreground);--tw-prose-pre-bg:var(--muted);--tw-prose-th-borders:var(--border);--tw-prose-td-borders:var(--border);--tw-prose-invert-body:var(--foreground);--tw-prose-invert-headings:var(--foreground);--tw-prose-invert-lead:var(--muted-foreground);--tw-prose-invert-links:var(--primary);--tw-prose-invert-bold:var(--foreground);--tw-prose-invert-counters:var(--muted-foreground);--tw-prose-invert-bullets:var(--muted-foreground);--tw-prose-invert-hr:var(--border);--tw-prose-invert-quotes:var(--foreground);--tw-prose-invert-quote-borders:var(--border);--tw-prose-invert-captions:var(--muted-foreground);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:var(--foreground);--tw-prose-invert-pre-code:var(--muted-foreground);--tw-prose-invert-pre-bg:var(--muted);--tw-prose-invert-th-borders:var(--border);--tw-prose-invert-td-borders:var(--border);font-size:1rem;line-height:1.75}.tw\\:prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.tw\\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.tw\\:prose :where(.tw\\:prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.tw\\:prose :where(.tw\\:prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.tw\\:prose :where(.tw\\:prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.tw\\:prose :where(.tw\\:prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.tw\\:prose :where(.tw\\:prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.tw\\:prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.tw\\:prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.tw\\:prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.tw\\:prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.571429em;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em}.tw\\:prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(.tw\\:prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose :where(.tw\\:prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.tw\\:prose-sm{font-size:.875rem;line-height:1.71429}.tw\\:prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.tw\\:prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.888889em;margin-bottom:.888889em;font-size:1.28571em;line-height:1.55556}.tw\\:prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.33333em;margin-bottom:1.33333em;padding-inline-start:1.11111em}.tw\\:prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:.8em;font-size:2.14286em;line-height:1.2}.tw\\:prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.6em;margin-bottom:.8em;font-size:1.42857em;line-height:1.4}.tw\\:prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.55556em;margin-bottom:.444444em;font-size:1.28571em;line-height:1.55556}.tw\\:prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.42857em;margin-bottom:.571429em;line-height:1.42857}.tw\\:prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.142857em;padding-inline-end:.357143em;padding-bottom:.142857em;border-radius:.3125rem;padding-inline-start:.357143em;font-size:.857143em}.tw\\:prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em}.tw\\:prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.tw\\:prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.888889em}.tw\\:prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;border-radius:.25rem;margin-top:1.66667em;margin-bottom:1.66667em;padding-inline-start:1em;font-size:.857143em;line-height:1.66667}.tw\\:prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em;padding-inline-start:1.57143em}.tw\\:prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;margin-bottom:.285714em}.tw\\:prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.tw\\:prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.tw\\:prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.tw\\:prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.tw\\:prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2.85714em;margin-bottom:2.85714em}.tw\\:prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em;line-height:1.5}.tw\\:prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.tw\\:prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.tw\\:prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.666667em;font-size:.857143em;line-height:1.33333}.tw\\:prose-sm :where(.tw\\:prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose-sm :where(.tw\\:prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.tw\\:-mt-4{margin-top:calc(calc(var(--spacing)) * -4)}.tw\\:mt-1{margin-top:calc(calc(var(--spacing)) * 1)}.tw\\:mt-2{margin-top:calc(calc(var(--spacing)) * 2)}.tw\\:mt-3{margin-top:calc(calc(var(--spacing)) * 3)}.tw\\:mt-4{margin-top:calc(calc(var(--spacing)) * 4)}.tw\\:mt-6{margin-top:calc(calc(var(--spacing)) * 6)}.tw\\:mt-auto{margin-top:auto}.tw\\:mr-1{margin-right:calc(calc(var(--spacing)) * 1)}.tw\\:mr-2{margin-right:calc(calc(var(--spacing)) * 2)}.tw\\:mr-3{margin-right:calc(calc(var(--spacing)) * 3)}.tw\\:-mb-4{margin-bottom:calc(calc(var(--spacing)) * -4)}.tw\\:mb-1{margin-bottom:calc(calc(var(--spacing)) * 1)}.tw\\:mb-2{margin-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:mb-3{margin-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:mb-4{margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:ml-2{margin-left:calc(calc(var(--spacing)) * 2)}.tw\\:ml-4{margin-left:calc(calc(var(--spacing)) * 4)}.tw\\:ml-auto{margin-left:auto}.tw\\:box-border{box-sizing:border-box}.tw\\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.tw\\:no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.tw\\:no-scrollbar::-webkit-scrollbar{display:none}.tw\\:block{display:block}.tw\\:flex{display:flex}.tw\\:grid{display:grid}.tw\\:hidden{display:none}.tw\\:inline-block{display:inline-block}.tw\\:inline-flex{display:inline-flex}.tw\\:inline-grid{display:inline-grid}.tw\\:field-sizing-content{field-sizing:content}.tw\\:aspect-square{aspect-ratio:1}.tw\\:size-2{width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:size-2\\.5{width:calc(calc(var(--spacing)) * 2.5);height:calc(calc(var(--spacing)) * 2.5)}.tw\\:size-3{width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:size-3\\.5{width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:size-4{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:size-6{width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:size-7{width:calc(calc(var(--spacing)) * 7);height:calc(calc(var(--spacing)) * 7)}.tw\\:size-8{width:calc(calc(var(--spacing)) * 8);height:calc(calc(var(--spacing)) * 8)}.tw\\:size-9{width:calc(calc(var(--spacing)) * 9);height:calc(calc(var(--spacing)) * 9)}.tw\\:size-full{width:100%;height:100%}.tw\\:h-1{height:calc(calc(var(--spacing)) * 1)}.tw\\:h-2{height:calc(calc(var(--spacing)) * 2)}.tw\\:h-3{height:calc(calc(var(--spacing)) * 3)}.tw\\:h-3\\.5{height:calc(calc(var(--spacing)) * 3.5)}.tw\\:h-4{height:calc(calc(var(--spacing)) * 4)}.tw\\:h-5{height:calc(calc(var(--spacing)) * 5)}.tw\\:h-6{height:calc(calc(var(--spacing)) * 6)}.tw\\:h-7{height:calc(calc(var(--spacing)) * 7)}.tw\\:h-8{height:calc(calc(var(--spacing)) * 8)}.tw\\:h-8\\!{height:calc(calc(var(--spacing)) * 8)!important}.tw\\:h-9{height:calc(calc(var(--spacing)) * 9)}.tw\\:h-10{height:calc(calc(var(--spacing)) * 10)}.tw\\:h-12{height:calc(calc(var(--spacing)) * 12)}.tw\\:h-14{height:calc(calc(var(--spacing)) * 14)}.tw\\:h-20{height:calc(calc(var(--spacing)) * 20)}.tw\\:h-24{height:calc(calc(var(--spacing)) * 24)}.tw\\:h-32{height:calc(calc(var(--spacing)) * 32)}.tw\\:h-40{height:calc(calc(var(--spacing)) * 40)}.tw\\:h-64{height:calc(calc(var(--spacing)) * 64)}.tw\\:h-96{height:calc(calc(var(--spacing)) * 96)}.tw\\:h-\\[1\\.2rem\\]{height:1.2rem}.tw\\:h-\\[5px\\]{height:5px}.tw\\:h-\\[300px\\]{height:300px}.tw\\:h-\\[calc\\(100\\%-1px\\)\\]{height:calc(100% - 1px)}.tw\\:h-\\[calc\\(100\\%-2px\\)\\]{height:calc(100% - 2px)}.tw\\:h-auto{height:auto}.tw\\:h-full{height:100%}.tw\\:h-px{height:1px}.tw\\:h-svh{height:100svh}.tw\\:max-h-\\(--radix-context-menu-content-available-height\\){max-height:var(--radix-context-menu-content-available-height)}.tw\\:max-h-\\(--radix-dropdown-menu-content-available-height\\){max-height:var(--radix-dropdown-menu-content-available-height)}.tw\\:max-h-\\(--radix-select-content-available-height\\){max-height:var(--radix-select-content-available-height)}.tw\\:max-h-5{max-height:calc(calc(var(--spacing)) * 5)}.tw\\:max-h-10{max-height:calc(calc(var(--spacing)) * 10)}.tw\\:max-h-72{max-height:calc(calc(var(--spacing)) * 72)}.tw\\:max-h-80{max-height:calc(calc(var(--spacing)) * 80)}.tw\\:max-h-\\[96\\%\\]{max-height:96%}.tw\\:max-h-\\[300px\\]{max-height:300px}.tw\\:min-h-0{min-height:calc(calc(var(--spacing)) * 0)}.tw\\:min-h-11{min-height:calc(calc(var(--spacing)) * 11)}.tw\\:min-h-16{min-height:calc(calc(var(--spacing)) * 16)}.tw\\:min-h-svh{min-height:100svh}.tw\\:w-\\(--radix-dropdown-menu-trigger-width\\){width:var(--radix-dropdown-menu-trigger-width)}.tw\\:w-\\(--sidebar-width\\){width:var(--sidebar-width)}.tw\\:w-1{width:calc(calc(var(--spacing)) * 1)}.tw\\:w-1\\/2{width:50%}.tw\\:w-2{width:calc(calc(var(--spacing)) * 2)}.tw\\:w-3{width:calc(calc(var(--spacing)) * 3)}.tw\\:w-3\\.5{width:calc(calc(var(--spacing)) * 3.5)}.tw\\:w-3\\/4{width:75%}.tw\\:w-4{width:calc(calc(var(--spacing)) * 4)}.tw\\:w-4\\/5{width:80%}.tw\\:w-4\\/6{width:66.6667%}.tw\\:w-5{width:calc(calc(var(--spacing)) * 5)}.tw\\:w-5\\/6{width:83.3333%}.tw\\:w-6{width:calc(calc(var(--spacing)) * 6)}.tw\\:w-8{width:calc(calc(var(--spacing)) * 8)}.tw\\:w-9{width:calc(calc(var(--spacing)) * 9)}.tw\\:w-9\\/12{width:75%}.tw\\:w-10{width:calc(calc(var(--spacing)) * 10)}.tw\\:w-12{width:calc(calc(var(--spacing)) * 12)}.tw\\:w-20{width:calc(calc(var(--spacing)) * 20)}.tw\\:w-24{width:calc(calc(var(--spacing)) * 24)}.tw\\:w-32{width:calc(calc(var(--spacing)) * 32)}.tw\\:w-48{width:calc(calc(var(--spacing)) * 48)}.tw\\:w-56{width:calc(calc(var(--spacing)) * 56)}.tw\\:w-60{width:calc(calc(var(--spacing)) * 60)}.tw\\:w-64{width:calc(calc(var(--spacing)) * 64)}.tw\\:w-72{width:calc(calc(var(--spacing)) * 72)}.tw\\:w-80{width:calc(calc(var(--spacing)) * 80)}.tw\\:w-96{width:calc(calc(var(--spacing)) * 96)}.tw\\:w-\\[1\\.2rem\\]{width:1.2rem}.tw\\:w-\\[1px\\]{width:1px}.tw\\:w-\\[5px\\]{width:5px}.tw\\:w-\\[70px\\]{width:70px}.tw\\:w-\\[100px\\]{width:100px}.tw\\:w-\\[116px\\]{width:116px}.tw\\:w-\\[124px\\]{width:124px}.tw\\:w-\\[150px\\]{width:150px}.tw\\:w-\\[180px\\]{width:180px}.tw\\:w-\\[200px\\]{width:200px}.tw\\:w-\\[250px\\]{width:250px}.tw\\:w-\\[300px\\]{width:300px}.tw\\:w-\\[320px\\]{width:320px}.tw\\:w-\\[350px\\]{width:350px}.tw\\:w-\\[400px\\]{width:400px}.tw\\:w-\\[500px\\]{width:500px}.tw\\:w-\\[600px\\]{width:600px}.tw\\:w-\\[calc\\(100\\%-2px\\)\\]{width:calc(100% - 2px)}.tw\\:w-\\[var\\(--radix-dropdown-menu-trigger-width\\)\\]{width:var(--radix-dropdown-menu-trigger-width)}.tw\\:w-\\[var\\(--radix-popper-anchor-width\\,280px\\)\\]{width:var(--radix-popper-anchor-width,280px)}.tw\\:w-auto{width:auto}.tw\\:w-fit{width:fit-content}.tw\\:w-full{width:100%}.tw\\:w-max{width:max-content}.tw\\:w-px{width:1px}.tw\\:max-w-\\(--skeleton-width\\){max-width:var(--skeleton-width)}.tw\\:max-w-2xl{max-width:var(--tw-container-2xl)}.tw\\:max-w-3xl{max-width:var(--tw-container-3xl)}.tw\\:max-w-4xl{max-width:var(--tw-container-4xl)}.tw\\:max-w-5{max-width:calc(calc(var(--spacing)) * 5)}.tw\\:max-w-6xl{max-width:var(--tw-container-6xl)}.tw\\:max-w-40{max-width:calc(calc(var(--spacing)) * 40)}.tw\\:max-w-48{max-width:calc(calc(var(--spacing)) * 48)}.tw\\:max-w-64{max-width:calc(calc(var(--spacing)) * 64)}.tw\\:max-w-96{max-width:calc(calc(var(--spacing)) * 96)}.tw\\:max-w-\\[200px\\]{max-width:200px}.tw\\:max-w-\\[220px\\]{max-width:220px}.tw\\:max-w-\\[280px\\]{max-width:280px}.tw\\:max-w-\\[calc\\(100\\%-2rem\\)\\]{max-width:calc(100% - 2rem)}.tw\\:max-w-\\[calc\\(100vw-2rem\\)\\]{max-width:calc(100vw - 2rem)}.tw\\:max-w-fit{max-width:fit-content}.tw\\:max-w-full{max-width:100%}.tw\\:max-w-lg{max-width:var(--tw-container-lg)}.tw\\:max-w-md{max-width:var(--tw-container-md)}.tw\\:max-w-none{max-width:none}.tw\\:max-w-sm{max-width:var(--tw-container-sm)}.tw\\:max-w-xs{max-width:var(--tw-container-xs)}.tw\\:min-w-0{min-width:calc(calc(var(--spacing)) * 0)}.tw\\:min-w-5{min-width:calc(calc(var(--spacing)) * 5)}.tw\\:min-w-7{min-width:calc(calc(var(--spacing)) * 7)}.tw\\:min-w-8{min-width:calc(calc(var(--spacing)) * 8)}.tw\\:min-w-9{min-width:calc(calc(var(--spacing)) * 9)}.tw\\:min-w-16{min-width:calc(calc(var(--spacing)) * 16)}.tw\\:min-w-32{min-width:calc(calc(var(--spacing)) * 32)}.tw\\:min-w-36{min-width:calc(calc(var(--spacing)) * 36)}.tw\\:min-w-80{min-width:calc(calc(var(--spacing)) * 80)}.tw\\:min-w-\\[12rem\\]{min-width:12rem}.tw\\:min-w-\\[26px\\]{min-width:26px}.tw\\:min-w-\\[96px\\]{min-width:96px}.tw\\:min-w-\\[140px\\]{min-width:140px}.tw\\:min-w-\\[200px\\]{min-width:200px}.tw\\:min-w-\\[215px\\]{min-width:215px}.tw\\:min-w-\\[500px\\]{min-width:500px}.tw\\:min-w-min{min-width:min-content}.tw\\:flex-1{flex:1}.tw\\:shrink{flex-shrink:1}.tw\\:shrink-0{flex-shrink:0}.tw\\:flex-grow,.tw\\:grow,.tw\\:grow-\\[1\\]{flex-grow:1}.tw\\:grow-\\[10\\]{flex-grow:10}.tw\\:basis-0{flex-basis:calc(calc(var(--spacing)) * 0)}.tw\\:caption-bottom{caption-side:bottom}.tw\\:border-collapse{border-collapse:collapse}.tw\\:origin-\\(--radix-context-menu-content-transform-origin\\){transform-origin:var(--radix-context-menu-content-transform-origin)}.tw\\:origin-\\(--radix-dropdown-menu-content-transform-origin\\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.tw\\:origin-\\(--radix-menubar-content-transform-origin\\){transform-origin:var(--radix-menubar-content-transform-origin)}.tw\\:origin-\\(--radix-popover-content-transform-origin\\){transform-origin:var(--radix-popover-content-transform-origin)}.tw\\:origin-\\(--radix-select-content-transform-origin\\){transform-origin:var(--radix-select-content-transform-origin)}.tw\\:origin-\\(--radix-tooltip-content-transform-origin\\){transform-origin:var(--radix-tooltip-content-transform-origin)}.tw\\:-translate-x-1\\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:-translate-x-px{--tw-translate-x:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-x-px{--tw-translate-x:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:-translate-y-1\\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-y-0{--tw-translate-y:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-y-\\[calc\\(-50\\%_-_2px\\)\\]{--tw-translate-y:calc(-50% - 2px);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rotate-45{rotate:45deg}.tw\\:rotate-180{rotate:180deg}.tw\\:transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.tw\\:animate-none\\!{animation:none!important}.tw\\:animate-pulse{animation:var(--tw-animate-pulse)}.tw\\:animate-spin{animation:var(--tw-animate-spin)}.tw\\:cursor-default{cursor:default}.tw\\:cursor-ew-resize{cursor:ew-resize}.tw\\:cursor-not-allowed{cursor:not-allowed}.tw\\:cursor-pointer{cursor:pointer}.tw\\:cursor-text{cursor:text}.tw\\:touch-none{touch-action:none}.tw\\:resize{resize:both}.tw\\:resize-none{resize:none}.tw\\:scroll-m-20{scroll-margin:calc(calc(var(--spacing)) * 20)}.tw\\:scroll-my-1{scroll-margin-block:calc(calc(var(--spacing)) * 1)}.tw\\:scroll-py-1{scroll-padding-block:calc(calc(var(--spacing)) * 1)}.tw\\:list-inside{list-style-position:inside}.tw\\:list-outside{list-style-position:outside}.tw\\:\\!list-\\[lower-alpha\\]{list-style-type:lower-alpha!important}.tw\\:\\!list-\\[lower-roman\\]{list-style-type:lower-roman!important}.tw\\:\\!list-\\[upper-alpha\\]{list-style-type:upper-alpha!important}.tw\\:\\!list-\\[upper-roman\\]{list-style-type:upper-roman!important}.tw\\:\\!list-decimal{list-style-type:decimal!important}.tw\\:\\!list-disc{list-style-type:disc!important}.tw\\:list-decimal{list-style-type:decimal}.tw\\:list-disc{list-style-type:disc}.tw\\:list-none{list-style-type:none}.tw\\:grid-flow-col{grid-auto-flow:column}.tw\\:auto-rows-min{grid-auto-rows:min-content}.tw\\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.tw\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.tw\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.tw\\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.tw\\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.tw\\:grid-cols-\\[25\\%_25\\%_50\\%\\]{grid-template-columns:25% 25% 50%}.tw\\:grid-cols-\\[25\\%_50\\%_25\\%\\]{grid-template-columns:25% 50% 25%}.tw\\:grid-cols-\\[min-content_1fr\\]{grid-template-columns:min-content 1fr}.tw\\:grid-cols-\\[min-content_min-content_1fr\\]{grid-template-columns:min-content min-content 1fr}.tw\\:grid-cols-subgrid{grid-template-columns:subgrid}.tw\\:flex-col{flex-direction:column}.tw\\:flex-col-reverse{flex-direction:column-reverse}.tw\\:flex-row{flex-direction:row}.tw\\:flex-row-reverse{flex-direction:row-reverse}.tw\\:flex-wrap{flex-wrap:wrap}.tw\\:place-content-center{place-content:center}.tw\\:content-center{align-content:center}.tw\\:items-baseline{align-items:baseline}.tw\\:items-center{align-items:center}.tw\\:items-end{align-items:flex-end}.tw\\:items-start{align-items:flex-start}.tw\\:items-stretch{align-items:stretch}.tw\\:justify-between{justify-content:space-between}.tw\\:justify-center{justify-content:center}.tw\\:justify-end{justify-content:flex-end}.tw\\:justify-start{justify-content:flex-start}.tw\\:gap-0{gap:calc(calc(var(--spacing)) * 0)}.tw\\:gap-0\\.5{gap:calc(calc(var(--spacing)) * .5)}.tw\\:gap-1{gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-1\\.5{gap:calc(calc(var(--spacing)) * 1.5)}.tw\\:gap-2{gap:calc(calc(var(--spacing)) * 2)}.tw\\:gap-2\\.5{gap:calc(calc(var(--spacing)) * 2.5)}.tw\\:gap-3{gap:calc(calc(var(--spacing)) * 3)}.tw\\:gap-4{gap:calc(calc(var(--spacing)) * 4)}.tw\\:gap-5{gap:calc(calc(var(--spacing)) * 5)}.tw\\:gap-6{gap:calc(calc(var(--spacing)) * 6)}.tw\\:gap-16{gap:calc(calc(var(--spacing)) * 16)}.tw\\:gap-\\[--spacing\\(var\\(--gap\\)\\)\\]{gap:calc(calc(var(--spacing)) * var(--gap))}.tw\\:gap-\\[12px\\]{gap:12px}:where(.tw\\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-1\\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 8) * calc(1 - var(--tw-space-y-reverse)))}.tw\\:gap-x-1{column-gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-x-2{column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:gap-x-3{column-gap:calc(calc(var(--spacing)) * 3)}.tw\\:gap-x-4{column-gap:calc(calc(var(--spacing)) * 4)}:where(.tw\\:-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 6) * calc(1 - var(--tw-space-x-reverse)))}.tw\\:gap-y-1{row-gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-y-2{row-gap:calc(calc(var(--spacing)) * 2)}:where(.tw\\:divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.tw\\:divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}.tw\\:self-start{align-self:flex-start}.tw\\:self-stretch{align-self:stretch}.tw\\:justify-self-end{justify-self:flex-end}.tw\\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tw\\:overflow-auto{overflow:auto}.tw\\:overflow-clip{overflow:clip}.tw\\:overflow-hidden{overflow:hidden}.tw\\:overflow-scroll{overflow:scroll}.tw\\:overflow-visible{overflow:visible}.tw\\:overflow-x-auto{overflow-x:auto}.tw\\:overflow-x-hidden{overflow-x:hidden}.tw\\:overflow-y-auto{overflow-y:auto}.tw\\:overflow-y-hidden{overflow-y:hidden}.tw\\:rounded{border-radius:.25rem}.tw\\:rounded-2xl{border-radius:calc(var(--radius) * 1.8)}.tw\\:rounded-4xl{border-radius:calc(var(--radius) * 2.6)}.tw\\:rounded-\\[2px\\]{border-radius:2px}.tw\\:rounded-\\[4px\\]{border-radius:4px}.tw\\:rounded-\\[6px\\]{border-radius:6px}.tw\\:rounded-\\[calc\\(var\\(--radius\\)-3px\\)\\]{border-radius:calc(var(--radius) - 3px)}.tw\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,10px\\)\\]{border-radius:min(var(--tw-radius-md), 10px)}.tw\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,12px\\)\\]{border-radius:min(var(--tw-radius-md), 12px)}.tw\\:rounded-full{border-radius:3.40282e38px}.tw\\:rounded-lg{border-radius:var(--radius)}.tw\\:rounded-lg\\!{border-radius:var(--radius)!important}.tw\\:rounded-md{border-radius:calc(var(--radius) * .8)}.tw\\:rounded-none{border-radius:0}.tw\\:rounded-sm{border-radius:calc(var(--radius) * .6)}.tw\\:rounded-xl{border-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-xl\\!{border-radius:calc(var(--radius) * 1.4)!important}.tw\\:rounded-s-none{border-start-start-radius:0;border-end-start-radius:0}.tw\\:rounded-e-none{border-start-end-radius:0;border-end-end-radius:0}.tw\\:rounded-t-xl{border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-l-lg{border-top-left-radius:var(--radius);border-bottom-left-radius:var(--radius)}.tw\\:rounded-r-xl{border-top-right-radius:calc(var(--radius) * 1.4);border-bottom-right-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-b-xl{border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:border{border-style:var(--tw-border-style);border-width:1px}.tw\\:border-0{border-style:var(--tw-border-style);border-width:0}.tw\\:border-2{border-style:var(--tw-border-style);border-width:2px}.tw\\:border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:border-s-0{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.tw\\:border-e{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.tw\\:border-e-0{border-inline-end-style:var(--tw-border-style);border-inline-end-width:0}.tw\\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.tw\\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.tw\\:border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.tw\\:border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.tw\\:border-dashed{--tw-border-style:dashed;border-style:dashed}.tw\\:border-none{--tw-border-style:none;border-style:none}.tw\\:border-solid{--tw-border-style:solid;border-style:solid}.tw\\:border-black{border-color:var(--tw-color-black)}.tw\\:border-blue-400{border-color:var(--tw-color-blue-400)}.tw\\:border-blue-500{border-color:var(--tw-color-blue-500)}.tw\\:border-border,.tw\\:border-border\\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.tw\\:border-border\\/50{border-color:color-mix(in oklab, var(--border) 50%, transparent)}}.tw\\:border-gray-300{border-color:var(--tw-color-gray-300)}.tw\\:border-input,.tw\\:border-input\\/30{border-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:border-input\\/30{border-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:border-muted-foreground{border-color:var(--muted-foreground)}.tw\\:border-primary{border-color:var(--primary)}.tw\\:border-red-300{border-color:var(--tw-color-red-300)}.tw\\:border-red-400{border-color:var(--tw-color-red-400)}.tw\\:border-red-500{border-color:var(--tw-color-red-500)}.tw\\:border-red-600{border-color:var(--tw-color-red-600)}.tw\\:border-ring{border-color:var(--ring)}.tw\\:border-sidebar-border{border-color:var(--sidebar-border)}.tw\\:border-slate-300{border-color:var(--tw-color-slate-300)}.tw\\:border-transparent{border-color:#0000}.tw\\:border-yellow-400{border-color:var(--tw-color-yellow-400)}.tw\\:border-yellow-500{border-color:var(--tw-color-yellow-500)}.tw\\:border-s-amber-200{border-inline-start-color:var(--tw-color-amber-200)}.tw\\:border-s-indigo-200{border-inline-start-color:var(--tw-color-indigo-200)}.tw\\:border-s-purple-200{border-inline-start-color:var(--tw-color-purple-200)}.tw\\:border-s-red-200{border-inline-start-color:var(--tw-color-red-200)}.tw\\:\\!bg-destructive\\/50{background-color:var(--destructive)!important}@supports (color:color-mix(in lab, red, red)){.tw\\:\\!bg-destructive\\/50{background-color:color-mix(in oklab, var(--destructive) 50%, transparent)!important}}.tw\\:bg-accent{background-color:var(--accent)}.tw\\:bg-accent-foreground{background-color:var(--accent-foreground)}.tw\\:bg-background,.tw\\:bg-background\\/50{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-background\\/50{background-color:color-mix(in oklab, var(--background) 50%, transparent)}}.tw\\:bg-black\\/10{background-color:var(--tw-color-black)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-black\\/10{background-color:color-mix(in oklab, var(--tw-color-black) 10%, transparent)}}.tw\\:bg-blue-50{background-color:var(--tw-color-blue-50)}.tw\\:bg-blue-100{background-color:var(--tw-color-blue-100)}.tw\\:bg-blue-400{background-color:var(--tw-color-blue-400)}.tw\\:bg-blue-500{background-color:var(--tw-color-blue-500)}.tw\\:bg-border{background-color:var(--border)}.tw\\:bg-card{background-color:var(--card)}.tw\\:bg-card-foreground{background-color:var(--card-foreground)}.tw\\:bg-destructive-foreground{background-color:var(--destructive-foreground)}.tw\\:bg-destructive\\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-destructive\\/10{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.tw\\:bg-foreground{background-color:var(--foreground)}.tw\\:bg-gray-50{background-color:var(--tw-color-gray-50)}.tw\\:bg-gray-100{background-color:var(--tw-color-gray-100)}.tw\\:bg-gray-500{background-color:var(--tw-color-gray-500)}.tw\\:bg-green-50{background-color:var(--tw-color-green-50)}.tw\\:bg-green-100{background-color:var(--tw-color-green-100)}.tw\\:bg-green-500{background-color:var(--tw-color-green-500)}.tw\\:bg-input,.tw\\:bg-input\\/30{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-input\\/30{background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:bg-muted{background-color:var(--muted)}.tw\\:bg-muted-foreground{background-color:var(--muted-foreground)}.tw\\:bg-muted\\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-muted\\/50{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:bg-neutral-300{background-color:var(--tw-color-neutral-300)}.tw\\:bg-orange-100{background-color:var(--tw-color-orange-100)}.tw\\:bg-popover{background-color:var(--popover)}.tw\\:bg-popover-foreground{background-color:var(--popover-foreground)}.tw\\:bg-popover\\/70{background-color:var(--popover)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-popover\\/70{background-color:color-mix(in oklab, var(--popover) 70%, transparent)}}.tw\\:bg-primary{background-color:var(--primary)}.tw\\:bg-primary-foreground{background-color:var(--primary-foreground)}.tw\\:bg-purple-50{background-color:var(--tw-color-purple-50)}.tw\\:bg-red-100{background-color:var(--tw-color-red-100)}.tw\\:bg-red-500{background-color:var(--tw-color-red-500)}.tw\\:bg-rose-500,.tw\\:bg-rose-500\\/5{background-color:var(--tw-color-rose-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-rose-500\\/5{background-color:color-mix(in oklab, var(--tw-color-rose-500) 5%, transparent)}}.tw\\:bg-rose-500\\/15{background-color:var(--tw-color-rose-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-rose-500\\/15{background-color:color-mix(in oklab, var(--tw-color-rose-500) 15%, transparent)}}.tw\\:bg-secondary{background-color:var(--secondary)}.tw\\:bg-secondary-foreground{background-color:var(--secondary-foreground)}.tw\\:bg-sidebar{background-color:var(--sidebar)}.tw\\:bg-sidebar-accent{background-color:var(--sidebar-accent)}.tw\\:bg-sidebar-border{background-color:var(--sidebar-border)}.tw\\:bg-sky-500,.tw\\:bg-sky-500\\/5{background-color:var(--tw-color-sky-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-sky-500\\/5{background-color:color-mix(in oklab, var(--tw-color-sky-500) 5%, transparent)}}.tw\\:bg-sky-500\\/15{background-color:var(--tw-color-sky-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-sky-500\\/15{background-color:color-mix(in oklab, var(--tw-color-sky-500) 15%, transparent)}}.tw\\:bg-teal-500,.tw\\:bg-teal-500\\/5{background-color:var(--tw-color-teal-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-teal-500\\/5{background-color:color-mix(in oklab, var(--tw-color-teal-500) 5%, transparent)}}.tw\\:bg-teal-500\\/15{background-color:var(--tw-color-teal-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-teal-500\\/15{background-color:color-mix(in oklab, var(--tw-color-teal-500) 15%, transparent)}}.tw\\:bg-transparent{background-color:#0000}.tw\\:bg-white{background-color:var(--tw-color-white)}.tw\\:bg-yellow-50{background-color:var(--tw-color-yellow-50)}.tw\\:bg-yellow-100{background-color:var(--tw-color-yellow-100)}.tw\\:bg-yellow-500{background-color:var(--tw-color-yellow-500)}.tw\\:bg-zinc-400{background-color:var(--tw-color-zinc-400)}.tw\\:bg-clip-padding{background-clip:padding-box}.tw\\:fill-destructive{fill:var(--destructive)}.tw\\:fill-foreground{fill:var(--foreground)}.tw\\:fill-yellow-400,.tw\\:fill-yellow-400\\/50{fill:var(--tw-color-yellow-400)}@supports (color:color-mix(in lab, red, red)){.tw\\:fill-yellow-400\\/50{fill:color-mix(in oklab, var(--tw-color-yellow-400) 50%, transparent)}}.tw\\:object-cover{object-fit:cover}.tw\\:\\!p-4{padding:calc(calc(var(--spacing)) * 4)!important}.tw\\:p-0{padding:calc(calc(var(--spacing)) * 0)}.tw\\:p-0\\.5{padding:calc(calc(var(--spacing)) * .5)}.tw\\:p-1{padding:calc(calc(var(--spacing)) * 1)}.tw\\:p-2{padding:calc(calc(var(--spacing)) * 2)}.tw\\:p-2\\.5{padding:calc(calc(var(--spacing)) * 2.5)}.tw\\:p-3{padding:calc(calc(var(--spacing)) * 3)}.tw\\:p-4{padding:calc(calc(var(--spacing)) * 4)}.tw\\:p-6{padding:calc(calc(var(--spacing)) * 6)}.tw\\:p-8{padding:calc(calc(var(--spacing)) * 8)}.tw\\:p-\\[1px\\]{padding:1px}.tw\\:p-\\[3px\\]{padding:3px}.tw\\:p-\\[10px\\]{padding:10px}.tw\\:p-\\[16px\\]{padding:16px}.tw\\:px-0{padding-inline:calc(calc(var(--spacing)) * 0)}.tw\\:px-1{padding-inline:calc(calc(var(--spacing)) * 1)}.tw\\:px-1\\.5{padding-inline:calc(calc(var(--spacing)) * 1.5)}.tw\\:px-2{padding-inline:calc(calc(var(--spacing)) * 2)}.tw\\:px-2\\.5{padding-inline:calc(calc(var(--spacing)) * 2.5)}.tw\\:px-3{padding-inline:calc(calc(var(--spacing)) * 3)}.tw\\:px-4{padding-inline:calc(calc(var(--spacing)) * 4)}.tw\\:px-6{padding-inline:calc(calc(var(--spacing)) * 6)}.tw\\:py-0{padding-block:calc(calc(var(--spacing)) * 0)}.tw\\:py-0\\.5{padding-block:calc(calc(var(--spacing)) * .5)}.tw\\:py-1{padding-block:calc(calc(var(--spacing)) * 1)}.tw\\:py-1\\.5{padding-block:calc(calc(var(--spacing)) * 1.5)}.tw\\:py-2{padding-block:calc(calc(var(--spacing)) * 2)}.tw\\:py-3{padding-block:calc(calc(var(--spacing)) * 3)}.tw\\:py-4{padding-block:calc(calc(var(--spacing)) * 4)}.tw\\:py-6{padding-block:calc(calc(var(--spacing)) * 6)}.tw\\:py-8{padding-block:calc(calc(var(--spacing)) * 8)}.tw\\:py-\\[2px\\]{padding-block:2px}.tw\\:ps-1\\.5{padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:ps-2{padding-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:ps-2\\.5{padding-inline-start:calc(calc(var(--spacing)) * 2.5)}.tw\\:ps-4{padding-inline-start:calc(calc(var(--spacing)) * 4)}.tw\\:ps-7{padding-inline-start:calc(calc(var(--spacing)) * 7)}.tw\\:ps-8{padding-inline-start:calc(calc(var(--spacing)) * 8)}.tw\\:ps-9{padding-inline-start:calc(calc(var(--spacing)) * 9)}.tw\\:ps-12{padding-inline-start:calc(calc(var(--spacing)) * 12)}.tw\\:ps-\\[85px\\]{padding-inline-start:85px}.tw\\:pe-1{padding-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:pe-1\\.5{padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:pe-2{padding-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:pe-4{padding-inline-end:calc(calc(var(--spacing)) * 4)}.tw\\:pe-8{padding-inline-end:calc(calc(var(--spacing)) * 8)}.tw\\:pe-9{padding-inline-end:calc(calc(var(--spacing)) * 9)}.tw\\:pe-\\[calc\\(138px\\+1rem\\)\\]{padding-inline-end:calc(138px + 1rem)}.tw\\:pt-1{padding-top:calc(calc(var(--spacing)) * 1)}.tw\\:pt-2{padding-top:calc(calc(var(--spacing)) * 2)}.tw\\:pt-3{padding-top:calc(calc(var(--spacing)) * 3)}.tw\\:pt-6{padding-top:calc(calc(var(--spacing)) * 6)}.tw\\:\\!pr-10{padding-right:calc(calc(var(--spacing)) * 10)!important}.tw\\:pr-0{padding-right:calc(calc(var(--spacing)) * 0)}.tw\\:pr-3{padding-right:calc(calc(var(--spacing)) * 3)}.tw\\:pr-4{padding-right:calc(calc(var(--spacing)) * 4)}.tw\\:pb-0{padding-bottom:calc(calc(var(--spacing)) * 0)}.tw\\:pb-1{padding-bottom:calc(calc(var(--spacing)) * 1)}.tw\\:pb-2{padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:pb-3{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:pb-4{padding-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:pb-8{padding-bottom:calc(calc(var(--spacing)) * 8)}.tw\\:pb-16{padding-bottom:calc(calc(var(--spacing)) * 16)}.tw\\:pb-24{padding-bottom:calc(calc(var(--spacing)) * 24)}.tw\\:pl-2{padding-left:calc(calc(var(--spacing)) * 2)}.tw\\:pl-3{padding-left:calc(calc(var(--spacing)) * 3)}.tw\\:pl-4{padding-left:calc(calc(var(--spacing)) * 4)}.tw\\:pl-5{padding-left:calc(calc(var(--spacing)) * 5)}.tw\\:pl-6{padding-left:calc(calc(var(--spacing)) * 6)}.tw\\:pl-8{padding-left:calc(calc(var(--spacing)) * 8)}.tw\\:text-center{text-align:center}.tw\\:text-end{text-align:end}.tw\\:text-left{text-align:left}.tw\\:text-right{text-align:right}.tw\\:text-start{text-align:start}.tw\\:align-middle{vertical-align:middle}.tw\\:font-heading{font-family:var(--font-sans)}.tw\\:font-mono{font-family:var(--tw-font-mono)}.tw\\:font-sans{font-family:IBM Plex Sans Variable,sans-serif}.tw\\:text-2xl{font-size:var(--tw-text-2xl);line-height:var(--tw-leading,var(--tw-text-2xl--line-height))}.tw\\:text-3xl{font-size:var(--tw-text-3xl);line-height:var(--tw-leading,var(--tw-text-3xl--line-height))}.tw\\:text-4xl{font-size:var(--tw-text-4xl);line-height:var(--tw-leading,var(--tw-text-4xl--line-height))}.tw\\:text-base{font-size:var(--tw-text-base);line-height:var(--tw-leading,var(--tw-text-base--line-height))}.tw\\:text-lg{font-size:var(--tw-text-lg);line-height:var(--tw-leading,var(--tw-text-lg--line-height))}.tw\\:text-sm{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:text-xl{font-size:var(--tw-text-xl);line-height:var(--tw-leading,var(--tw-text-xl--line-height))}.tw\\:text-xs{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:text-\\[0\\.8rem\\]{font-size:.8rem}.tw\\:leading-loose{--tw-leading:var(--tw-leading-loose);line-height:var(--tw-leading-loose)}.tw\\:leading-none{--tw-leading:1;line-height:1}.tw\\:leading-relaxed{--tw-leading:var(--tw-leading-relaxed);line-height:var(--tw-leading-relaxed)}.tw\\:leading-snug{--tw-leading:var(--tw-leading-snug);line-height:var(--tw-leading-snug)}.tw\\:leading-tight{--tw-leading:var(--tw-leading-tight);line-height:var(--tw-leading-tight)}.tw\\:font-bold{--tw-font-weight:var(--tw-font-weight-bold);font-weight:var(--tw-font-weight-bold)}.tw\\:font-extrabold{--tw-font-weight:var(--tw-font-weight-extrabold);font-weight:var(--tw-font-weight-extrabold)}.tw\\:font-medium{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:font-normal{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:font-semibold{--tw-font-weight:var(--tw-font-weight-semibold);font-weight:var(--tw-font-weight-semibold)}.tw\\:tracking-tight{--tw-tracking:var(--tw-tracking-tight);letter-spacing:var(--tw-tracking-tight)}.tw\\:tracking-widest{--tw-tracking:var(--tw-tracking-widest);letter-spacing:var(--tw-tracking-widest)}.tw\\:text-balance{text-wrap:balance}.tw\\:text-nowrap{text-wrap:nowrap}.tw\\:break-words{overflow-wrap:break-word}.tw\\:text-clip{text-overflow:clip}.tw\\:text-ellipsis{text-overflow:ellipsis}.tw\\:whitespace-normal{white-space:normal}.tw\\:whitespace-nowrap{white-space:nowrap}.tw\\:\\[color\\:blue\\]{color:#00f}.tw\\:text-accent{color:var(--accent)}.tw\\:text-accent-foreground{color:var(--accent-foreground)}.tw\\:text-background{color:var(--background)}.tw\\:text-blue-400{color:var(--tw-color-blue-400)}.tw\\:text-blue-500{color:var(--tw-color-blue-500)}.tw\\:text-blue-600{color:var(--tw-color-blue-600)}.tw\\:text-blue-800{color:var(--tw-color-blue-800)}.tw\\:text-card{color:var(--card)}.tw\\:text-card-foreground{color:var(--card-foreground)}.tw\\:text-current{color:currentColor}.tw\\:text-destructive{color:var(--destructive)}.tw\\:text-destructive-foreground{color:var(--destructive-foreground)}.tw\\:text-foreground,.tw\\:text-foreground\\/30{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/30{color:color-mix(in oklab, var(--foreground) 30%, transparent)}}.tw\\:text-foreground\\/50{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/50{color:color-mix(in oklab, var(--foreground) 50%, transparent)}}.tw\\:text-foreground\\/60{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/60{color:color-mix(in oklab, var(--foreground) 60%, transparent)}}.tw\\:text-foreground\\/70{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/70{color:color-mix(in oklab, var(--foreground) 70%, transparent)}}.tw\\:text-gray-300{color:var(--tw-color-gray-300)}.tw\\:text-gray-500{color:var(--tw-color-gray-500)}.tw\\:text-gray-600{color:var(--tw-color-gray-600)}.tw\\:text-gray-700{color:var(--tw-color-gray-700)}.tw\\:text-gray-800{color:var(--tw-color-gray-800)}.tw\\:text-green-600{color:var(--tw-color-green-600)}.tw\\:text-green-700{color:var(--tw-color-green-700)}.tw\\:text-green-800{color:var(--tw-color-green-800)}.tw\\:text-inherit{color:inherit}.tw\\:text-muted{color:var(--muted)}.tw\\:text-muted-foreground,.tw\\:text-muted-foreground\\/50{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-muted-foreground\\/50{color:color-mix(in oklab, var(--muted-foreground) 50%, transparent)}}.tw\\:text-orange-800{color:var(--tw-color-orange-800)}.tw\\:text-popover{color:var(--popover)}.tw\\:text-popover-foreground{color:var(--popover-foreground)}.tw\\:text-primary{color:var(--primary)}.tw\\:text-primary-foreground{color:var(--primary-foreground)}.tw\\:text-purple-900{color:var(--tw-color-purple-900)}.tw\\:text-red-500{color:var(--tw-color-red-500)}.tw\\:text-red-600{color:var(--tw-color-red-600)}.tw\\:text-red-700{color:var(--tw-color-red-700)}.tw\\:text-red-800{color:var(--tw-color-red-800)}.tw\\:text-rose-600{color:var(--tw-color-rose-600)}.tw\\:text-secondary{color:var(--secondary)}.tw\\:text-secondary-foreground{color:var(--secondary-foreground)}.tw\\:text-sidebar-accent-foreground{color:var(--sidebar-accent-foreground)}.tw\\:text-sidebar-foreground,.tw\\:text-sidebar-foreground\\/70{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-sidebar-foreground\\/70{color:color-mix(in oklab, var(--sidebar-foreground) 70%, transparent)}}.tw\\:text-sky-600{color:var(--tw-color-sky-600)}.tw\\:text-slate-900{color:var(--tw-color-slate-900)}.tw\\:text-teal-600{color:var(--tw-color-teal-600)}.tw\\:text-white{color:var(--tw-color-white)}.tw\\:text-yellow-400{color:var(--tw-color-yellow-400)}.tw\\:text-yellow-600{color:var(--tw-color-yellow-600)}.tw\\:text-yellow-700{color:var(--tw-color-yellow-700)}.tw\\:capitalize{text-transform:capitalize}.tw\\:uppercase{text-transform:uppercase}.tw\\:italic{font-style:italic}.tw\\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.tw\\:line-through{text-decoration-line:line-through}.tw\\:underline{text-decoration-line:underline}.tw\\:decoration-destructive{-webkit-text-decoration-color:var(--destructive);-webkit-text-decoration-color:var(--destructive);text-decoration-color:var(--destructive)}.tw\\:underline-offset-4{text-underline-offset:4px}.tw\\:opacity-0{opacity:0}.tw\\:opacity-40{opacity:.4}.tw\\:opacity-50{opacity:.5}.tw\\:opacity-60{opacity:.6}.tw\\:opacity-100{opacity:1}.tw\\:bg-blend-color{background-blend-mode:color}.tw\\:shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-\\[0_0_0_1px_var\\(--sidebar-border\\)\\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,var(--sidebar-border));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-none\\!{--tw-shadow:0 0 #0000!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.tw\\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-background{--tw-ring-color:var(--background)}.tw\\:ring-foreground\\/10{--tw-ring-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:ring-foreground\\/10{--tw-ring-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}.tw\\:ring-primary{--tw-ring-color:var(--primary)}.tw\\:ring-ring\\/50{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:ring-ring\\/50{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:ring-sidebar-ring{--tw-ring-color:var(--sidebar-ring)}.tw\\:ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:ring-offset-background{--tw-ring-offset-color:var(--background)}.tw\\:outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:outline-hidden{outline-offset:2px;outline:2px solid #0000}}.tw\\:drop-shadow-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--tw-drop-shadow-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.tw\\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[color\\,box-shadow\\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[left\\,right\\,width\\]{transition-property:left,right,width;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[margin\\,opacity\\]{transition-property:margin,opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[width\\,height\\,padding\\]{transition-property:width,height,padding;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[width\\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-none{transition-property:none}.tw\\:duration-100{--tw-duration:.1s;transition-duration:.1s}.tw\\:duration-200{--tw-duration:.2s;transition-duration:.2s}.tw\\:ease-linear{--tw-ease:linear;transition-timing-function:linear}.tw\\:prose-quoteless :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose-quoteless :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.tw\\:outline-none{--tw-outline-style:none;outline-style:none}.tw\\:select-none{-webkit-user-select:none;user-select:none}.tw\\:group-focus-within\\/menu-item\\:opacity-100:is(:where(.tw\\:group\\/menu-item):focus-within *){opacity:1}@media (hover:hover){.tw\\:group-hover\\:visible:is(:where(.tw\\:group):hover *){visibility:visible}.tw\\:group-hover\\:hidden:is(:where(.tw\\:group):hover *){display:none}.tw\\:group-hover\\:opacity-100:is(:where(.tw\\:group):hover *),.tw\\:group-hover\\/menu-item\\:opacity-100:is(:where(.tw\\:group\\/menu-item):hover *){opacity:1}}.tw\\:group-focus\\/context-menu-item\\:text-accent-foreground:is(:where(.tw\\:group\\/context-menu-item):focus *),.tw\\:group-focus\\/dropdown-menu-item\\:text-accent-foreground:is(:where(.tw\\:group\\/dropdown-menu-item):focus *),.tw\\:group-focus\\/menubar-item\\:text-accent-foreground:is(:where(.tw\\:group\\/menubar-item):focus *){color:var(--accent-foreground)}.tw\\:group-has-disabled\\/field\\:opacity-50:is(:where(.tw\\:group\\/field):has(:disabled) *){opacity:.5}.tw\\:group-has-data-\\[sidebar\\=menu-action\\]\\/menu-item\\:pe-8:is(:where(.tw\\:group\\/menu-item):has([data-sidebar=menu-action]) *){padding-inline-end:calc(calc(var(--spacing)) * 8)}.tw\\:group-has-data-\\[size\\=lg\\]\\/avatar-group\\:size-10:is(:where(.tw\\:group\\/avatar-group):has([data-size=lg]) *){width:calc(calc(var(--spacing)) * 10);height:calc(calc(var(--spacing)) * 10)}.tw\\:group-has-data-\\[size\\=sm\\]\\/avatar-group\\:size-6:is(:where(.tw\\:group\\/avatar-group):has([data-size=sm]) *){width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:group-has-data-\\[slot\\=command-shortcut\\]\\/command-item\\:hidden:is(:where(.tw\\:group\\/command-item):has([data-slot=command-shortcut]) *){display:none}.tw\\:group-has-\\[\\>input\\]\\/input-group\\:pt-2:is(:where(.tw\\:group\\/input-group):has(>input) *){padding-top:calc(calc(var(--spacing)) * 2)}.tw\\:group-has-\\[\\>input\\]\\/input-group\\:pb-2:is(:where(.tw\\:group\\/input-group):has(>input) *){padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:group-has-\\[\\>svg\\]\\/alert\\:col-start-2:is(:where(.tw\\:group\\/alert):has(>svg) *){grid-column-start:2}.tw\\:group-data-\\[checked\\=true\\]\\/command-item\\:opacity-100:is(:where(.tw\\:group\\/command-item)[data-checked=true] *){opacity:1}.tw\\:group-data-\\[collapsible\\=icon\\]\\:-mt-8:is(:where(.tw\\:group)[data-collapsible=icon] *){margin-top:calc(calc(var(--spacing)) * -8)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:hidden:is(:where(.tw\\:group)[data-collapsible=icon] *){display:none}.tw\\:group-data-\\[collapsible\\=icon\\]\\:size-8\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(calc(var(--spacing)) * 8)!important;height:calc(calc(var(--spacing)) * 8)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\(--sidebar-width-icon\\):is(:where(.tw\\:group)[data-collapsible=icon] *){width:var(--sidebar-width-icon)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\[calc\\(var\\(--sidebar-width-icon\\)\\+\\(--spacing\\(4\\)\\)\\)\\]:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(calc(var(--spacing)) * 4)))}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\[calc\\(var\\(--sidebar-width-icon\\)\\+\\(--spacing\\(4\\)\\)\\+2px\\)\\]:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(calc(var(--spacing)) * 4)) + 2px)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:overflow-hidden:is(:where(.tw\\:group)[data-collapsible=icon] *){overflow:hidden}.tw\\:group-data-\\[collapsible\\=icon\\]\\:p-0\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){padding:calc(calc(var(--spacing)) * 0)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:p-2\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){padding:calc(calc(var(--spacing)) * 2)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:opacity-0:is(:where(.tw\\:group)[data-collapsible=icon] *){opacity:0}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:right-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\]:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){right:calc(var(--sidebar-width) * -1)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:left-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\]:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){left:calc(var(--sidebar-width) * -1)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:w-0:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){width:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:translate-x-0:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:group-data-\\[disabled\\=true\\]\\:pointer-events-none:is(:where(.tw\\:group)[data-disabled=true] *){pointer-events:none}.tw\\:group-data-\\[disabled\\=true\\]\\:opacity-50:is(:where(.tw\\:group)[data-disabled=true] *),.tw\\:group-data-\\[disabled\\=true\\]\\/input-group\\:opacity-50:is(:where(.tw\\:group\\/input-group)[data-disabled=true] *){opacity:.5}.tw\\:group-data-\\[side\\=primary\\]\\:-right-4:is(:where(.tw\\:group)[data-side=primary] *){right:calc(calc(var(--spacing)) * -4)}.tw\\:group-data-\\[side\\=primary\\]\\:border-e:is(:where(.tw\\:group)[data-side=primary] *){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.tw\\:group-data-\\[side\\=secondary\\]\\:left-0:is(:where(.tw\\:group)[data-side=secondary] *){left:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-\\[side\\=secondary\\]\\:rotate-180:is(:where(.tw\\:group)[data-side=secondary] *){rotate:180deg}.tw\\:group-data-\\[side\\=secondary\\]\\:border-s:is(:where(.tw\\:group)[data-side=secondary] *){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:group-data-\\[size\\=default\\]\\/avatar\\:size-2\\.5:is(:where(.tw\\:group\\/avatar)[data-size=default] *){width:calc(calc(var(--spacing)) * 2.5);height:calc(calc(var(--spacing)) * 2.5)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:size-4:is(:where(.tw\\:group\\/switch)[data-size=default] *){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[size\\=lg\\]\\/avatar\\:size-3:is(:where(.tw\\:group\\/avatar)[data-size=lg] *){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=sm] *){width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:text-xs:is(:where(.tw\\:group\\/avatar)[data-size=sm] *){font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:p-3:is(:where(.tw\\:group\\/card)[data-size=sm] *){padding:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:px-3:is(:where(.tw\\:group\\/card)[data-size=sm] *){padding-inline:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:text-sm:is(:where(.tw\\:group\\/card)[data-size=sm] *){font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:size-3:is(:where(.tw\\:group\\/switch)[data-size=sm] *){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:rounded-none:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *){border-radius:0}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:px-2:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *){padding-inline:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[variant\\=floating\\]\\:rounded-lg:is(:where(.tw\\:group)[data-variant=floating] *){border-radius:var(--radius)}.tw\\:group-data-\\[variant\\=floating\\]\\:shadow-sm:is(:where(.tw\\:group)[data-variant=floating] *){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=floating\\]\\:ring-1:is(:where(.tw\\:group)[data-variant=floating] *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=floating\\]\\:ring-sidebar-border:is(:where(.tw\\:group)[data-variant=floating] *){--tw-ring-color:var(--sidebar-border)}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *){background-color:#0000}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:mx-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){margin-inline:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:mt-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){margin-top:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:h-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){height:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:w-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){width:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:text-center:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){text-align:center}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:my-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){margin-block:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:me-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){margin-inline-end:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:h-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){height:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:w-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){width:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:my-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){margin-block:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:ms-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){margin-inline-start:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:h-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){height:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:w-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){width:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:mx-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){margin-inline:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:mb-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:h-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){height:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:w-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){width:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:text-center:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){text-align:center}.tw\\:group-data-selected\\/command-item\\:text-foreground:is(:where(.tw\\:group\\/command-item):where([data-selected=true]) *){color:var(--foreground)}.tw\\:group-data-horizontal\\/tabs\\:h-8:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *){height:calc(calc(var(--spacing)) * 8)}.tw\\:group-data-vertical\\/tabs\\:h-fit:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){height:fit-content}.tw\\:group-data-vertical\\/tabs\\:w-full:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){width:100%}.tw\\:group-data-vertical\\/tabs\\:flex-col:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){flex-direction:column}.tw\\:group-data-vertical\\/tabs\\:justify-start:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){justify-content:flex-start}@media (hover:hover){.tw\\:peer-hover\\/menu-button\\:text-sidebar-accent-foreground:is(:where(.tw\\:peer\\/menu-button):hover~*){color:var(--sidebar-accent-foreground)}.tw\\:peer-focus\\:group-hover\\:text-blue-500:is(:where(.tw\\:peer):focus~*):is(:where(.tw\\:group):hover *){color:var(--tw-color-blue-500)}}.tw\\:peer-disabled\\:cursor-not-allowed:is(:where(.tw\\:peer):disabled~*){cursor:not-allowed}.tw\\:peer-disabled\\:opacity-50:is(:where(.tw\\:peer):disabled~*){opacity:.5}.tw\\:peer-data-\\[size\\=default\\]\\/menu-button\\:top-1\\.5:is(:where(.tw\\:peer\\/menu-button)[data-size=default]~*){top:calc(calc(var(--spacing)) * 1.5)}.tw\\:peer-data-\\[size\\=lg\\]\\/menu-button\\:top-2\\.5:is(:where(.tw\\:peer\\/menu-button)[data-size=lg]~*){top:calc(calc(var(--spacing)) * 2.5)}.tw\\:peer-data-\\[size\\=sm\\]\\/menu-button\\:top-1:is(:where(.tw\\:peer\\/menu-button)[data-size=sm]~*){top:calc(calc(var(--spacing)) * 1)}.tw\\:peer-data-active\\/menu-button\\:text-sidebar-accent-foreground:is(:is(:where(.tw\\:peer\\/menu-button):where([data-state=active]),:where(.tw\\:peer\\/menu-button):where([data-active]:not([data-active=false])))~*){color:var(--sidebar-accent-foreground)}.tw\\:file\\:inline-flex::file-selector-button{display:inline-flex}.tw\\:file\\:h-6::file-selector-button{height:calc(calc(var(--spacing)) * 6)}.tw\\:file\\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.tw\\:file\\:bg-transparent::file-selector-button{background-color:#0000}.tw\\:file\\:text-sm::file-selector-button{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:file\\:font-medium::file-selector-button{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:file\\:text-foreground::file-selector-button{color:var(--foreground)}.tw\\:placeholder\\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.tw\\:before\\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.tw\\:before\\:absolute:before{content:var(--tw-content);position:absolute}.tw\\:before\\:inset-0:before{content:var(--tw-content);inset:calc(calc(var(--spacing)) * 0)}.tw\\:before\\:top-0\\.5:before{content:var(--tw-content);top:calc(calc(var(--spacing)) * .5)}.tw\\:before\\:left-0:before{content:var(--tw-content);left:calc(calc(var(--spacing)) * 0)}.tw\\:before\\:-z-1:before{content:var(--tw-content);z-index:calc(1 * -1)}.tw\\:before\\:block:before{content:var(--tw-content);display:block}.tw\\:before\\:hidden:before{content:var(--tw-content);display:none}.tw\\:before\\:h-4:before{content:var(--tw-content);height:calc(calc(var(--spacing)) * 4)}.tw\\:before\\:w-4:before{content:var(--tw-content);width:calc(calc(var(--spacing)) * 4)}.tw\\:before\\:cursor-pointer:before{content:var(--tw-content);cursor:pointer}.tw\\:before\\:rounded:before{content:var(--tw-content);border-radius:.25rem}.tw\\:before\\:rounded-\\[inherit\\]:before{content:var(--tw-content);border-radius:inherit}.tw\\:before\\:border:before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.tw\\:before\\:border-primary:before{content:var(--tw-content);border-color:var(--primary)}.tw\\:before\\:bg-primary:before{content:var(--tw-content);background-color:var(--primary)}.tw\\:before\\:bg-cover:before{content:var(--tw-content);background-size:cover}.tw\\:before\\:bg-no-repeat:before{content:var(--tw-content);background-repeat:no-repeat}.tw\\:before\\:backdrop-blur-2xl:before{content:var(--tw-content);--tw-backdrop-blur:blur(var(--tw-blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.tw\\:before\\:backdrop-saturate-150:before{content:var(--tw-content);--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.tw\\:before\\:content-\\[\\"\\"\\]:before{--tw-content:"";content:var(--tw-content)}.tw\\:before\\:content-\\[\\\\\\"\\\\\\"\\]:before{--tw-content:\\"\\";content:var(--tw-content)}.tw\\:after\\:absolute:after{content:var(--tw-content);position:absolute}.tw\\:after\\:-inset-2:after{content:var(--tw-content);inset:calc(calc(var(--spacing)) * -2)}.tw\\:after\\:inset-0:after{content:var(--tw-content);inset:calc(calc(var(--spacing)) * 0)}.tw\\:after\\:-inset-x-3:after{content:var(--tw-content);inset-inline:calc(calc(var(--spacing)) * -3)}.tw\\:after\\:-inset-y-2:after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * -2)}.tw\\:after\\:inset-y-0:after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:after\\:start-1\\/2:after{content:var(--tw-content);inset-inline-start:50%}.tw\\:after\\:top-\\[6px\\]:after{content:var(--tw-content);top:6px}.tw\\:after\\:right-\\[7px\\]:after{content:var(--tw-content);right:7px}.tw\\:after\\:left-\\[7px\\]:after{content:var(--tw-content);left:7px}.tw\\:after\\:block:after{content:var(--tw-content);display:block}.tw\\:after\\:hidden:after{content:var(--tw-content);display:none}.tw\\:after\\:h-0\\.5:after{content:var(--tw-content);height:calc(calc(var(--spacing)) * .5)}.tw\\:after\\:h-\\[6px\\]:after{content:var(--tw-content);height:6px}.tw\\:after\\:w-1:after{content:var(--tw-content);width:calc(calc(var(--spacing)) * 1)}.tw\\:after\\:w-\\[2px\\]:after{content:var(--tw-content);width:2px}.tw\\:after\\:w-\\[3px\\]:after{content:var(--tw-content);width:3px}.tw\\:after\\:-translate-x-1\\/2:after{content:var(--tw-content);--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:after\\:rotate-45:after{content:var(--tw-content);rotate:45deg}.tw\\:after\\:cursor-pointer:after{content:var(--tw-content);cursor:pointer}.tw\\:after\\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.tw\\:after\\:border:after{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.tw\\:after\\:border-t-0:after{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:after\\:border-r-2:after{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:2px}.tw\\:after\\:border-b-2:after{content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.tw\\:after\\:border-l-0:after{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:0}.tw\\:after\\:border-solid:after{content:var(--tw-content);--tw-border-style:solid;border-style:solid}.tw\\:after\\:border-border:after{content:var(--tw-content);border-color:var(--border)}.tw\\:after\\:border-white:after{content:var(--tw-content);border-color:var(--tw-color-white)}.tw\\:after\\:bg-foreground:after{content:var(--tw-content);background-color:var(--foreground)}.tw\\:after\\:bg-muted:after{content:var(--tw-content);background-color:var(--muted)}.tw\\:after\\:opacity-0:after{content:var(--tw-content);opacity:0}.tw\\:after\\:mix-blend-darken:after{content:var(--tw-content);mix-blend-mode:darken}.tw\\:after\\:transition-opacity:after{content:var(--tw-content);transition-property:opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:after\\:content-\\[\\"\\"\\]:after{--tw-content:"";content:var(--tw-content)}.tw\\:after\\:content-\\[\\\\\\"\\\\\\"\\]:after{--tw-content:\\"\\";content:var(--tw-content)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:after\\:start-full:is(:where(.tw\\:group)[data-collapsible=offcanvas] *):after{content:var(--tw-content);inset-inline-start:100%}.tw\\:group-data-horizontal\\/tabs\\:after\\:inset-x-0:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-horizontal\\/tabs\\:after\\:bottom-\\[-5px\\]:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);bottom:-5px}.tw\\:group-data-horizontal\\/tabs\\:after\\:h-0\\.5:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);height:calc(calc(var(--spacing)) * .5)}.tw\\:group-data-vertical\\/tabs\\:after\\:inset-y-0:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-vertical\\/tabs\\:after\\:-end-1:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);inset-inline-end:calc(calc(var(--spacing)) * -1)}.tw\\:group-data-vertical\\/tabs\\:after\\:w-0\\.5:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);width:calc(calc(var(--spacing)) * .5)}.tw\\:first\\:mt-0:first-child{margin-top:calc(calc(var(--spacing)) * 0)}.tw\\:even\\:bg-muted:nth-child(2n){background-color:var(--muted)}@media (hover:hover){.tw\\:hover\\:-mt-4:hover{margin-top:calc(calc(var(--spacing)) * -4)}.tw\\:hover\\:cursor-pointer:hover{cursor:pointer}.tw\\:hover\\:bg-accent:hover,.tw\\:hover\\:bg-accent\\/80:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-accent\\/80:hover{background-color:color-mix(in oklab, var(--accent) 80%, transparent)}}.tw\\:hover\\:bg-blue-600:hover{background-color:var(--tw-color-blue-600)}.tw\\:hover\\:bg-destructive\\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-destructive\\/20:hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:hover\\:bg-gray-50:hover{background-color:var(--tw-color-gray-50)}.tw\\:hover\\:bg-input:hover{background-color:var(--input)}.tw\\:hover\\:bg-muted:hover,.tw\\:hover\\:bg-muted\\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-muted\\/50:hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:hover\\:bg-muted\\/80:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-muted\\/80:hover{background-color:color-mix(in oklab, var(--muted) 80%, transparent)}}.tw\\:hover\\:bg-primary\\/10:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-primary\\/10:hover{background-color:color-mix(in oklab, var(--primary) 10%, transparent)}}.tw\\:hover\\:bg-primary\\/70:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-primary\\/70:hover{background-color:color-mix(in oklab, var(--primary) 70%, transparent)}}.tw\\:hover\\:bg-red-500:hover{background-color:var(--tw-color-red-500)}.tw\\:hover\\:bg-secondary:hover,.tw\\:hover\\:bg-secondary\\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-secondary\\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.tw\\:hover\\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.tw\\:hover\\:bg-transparent:hover{background-color:#0000}.tw\\:hover\\:text-foreground:hover{color:var(--foreground)}.tw\\:hover\\:text-muted-foreground:hover{color:var(--muted-foreground)}.tw\\:hover\\:text-primary-foreground:hover{color:var(--primary-foreground)}.tw\\:hover\\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}.tw\\:hover\\:underline:hover{text-decoration-line:underline}.tw\\:hover\\:opacity-80:hover{opacity:.8}.tw\\:hover\\:opacity-100:hover{opacity:1}.tw\\:hover\\:shadow-\\[0_0_0_1px_var\\(--sidebar-accent\\)\\]:hover{--tw-shadow:0 0 0 1px var(--tw-shadow-color,var(--sidebar-accent));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:ring-3:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:group-data-\\[collapsible\\=offcanvas\\]\\:bg-sidebar:hover:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){background-color:var(--sidebar)}.tw\\:hover\\:after\\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:var(--sidebar-border)}}.tw\\:focus\\:relative:focus{position:relative}.tw\\:focus\\:z-10:focus{z-index:10}.tw\\:focus\\:bg-accent:focus{background-color:var(--accent)}.tw\\:focus\\:bg-muted:focus{background-color:var(--muted)}.tw\\:focus\\:text-accent-foreground:focus{color:var(--accent-foreground)}.tw\\:focus\\:text-foreground:focus{color:var(--foreground)}.tw\\:focus\\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus\\:ring-ring:focus{--tw-ring-color:var(--ring)}.tw\\:focus\\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:focus\\:ring-offset-background:focus{--tw-ring-offset-color:var(--background)}.tw\\:focus\\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:focus\\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}:is(.tw\\:focus\\:\\*\\*\\:text-accent-foreground:focus *),:is(.tw\\:not-data-\\[variant\\=destructive\\]\\:focus\\:\\*\\*\\:text-accent-foreground:not([data-variant=destructive]):focus *){color:var(--accent-foreground)}.tw\\:focus-visible\\:relative:focus-visible{position:relative}.tw\\:focus-visible\\:z-10:focus-visible{z-index:10}.tw\\:focus-visible\\:border-destructive\\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:border-destructive\\/40:focus-visible{border-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:focus-visible\\:border-ring:focus-visible{border-color:var(--ring)}.tw\\:focus-visible\\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-3:focus-visible,.tw\\:focus-visible\\:ring-\\[3px\\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-\\[color\\:hsl\\(240\\,5\\%\\,64\\.9\\%\\)\\]:focus-visible{--tw-ring-color:#a1a1aa}.tw\\:focus-visible\\:ring-destructive\\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:ring-destructive\\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:focus-visible\\:ring-ring:focus-visible,.tw\\:focus-visible\\:ring-ring\\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:ring-ring\\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:focus-visible\\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:focus-visible\\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:focus-visible\\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.tw\\:focus-visible\\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.tw\\:focus-visible\\:outline-ring:focus-visible{outline-color:var(--ring)}:is(.tw\\:\\*\\:focus-visible\\:relative>*):focus-visible{position:relative}:is(.tw\\:\\*\\:focus-visible\\:z-10>*):focus-visible{z-index:10}.tw\\:active\\:bg-sidebar-accent:active{background-color:var(--sidebar-accent)}.tw\\:active\\:text-sidebar-accent-foreground:active{color:var(--sidebar-accent-foreground)}.tw\\:active\\:ring-3:active{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:active\\:not-aria-\\[haspopup\\]\\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:disabled\\:pointer-events-none:disabled{pointer-events:none}.tw\\:disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.tw\\:disabled\\:bg-input\\/50:disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:disabled\\:bg-input\\/50:disabled{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:disabled\\:bg-transparent:disabled{background-color:#0000}.tw\\:disabled\\:opacity-50:disabled{opacity:.5}:where([data-side=primary]) .tw\\:in-data-\\[side\\=primary\\]\\:cursor-w-resize{cursor:w-resize}:where([data-side=secondary]) .tw\\:in-data-\\[side\\=secondary\\]\\:cursor-e-resize{cursor:e-resize}:where([data-slot=button-group]) .tw\\:in-data-\\[slot\\=button-group\\]\\:rounded-lg{border-radius:var(--radius)}:where([data-slot=combobox-content]) .tw\\:in-data-\\[slot\\=combobox-content\\]\\:focus-within\\:border-inherit:focus-within{border-color:inherit}:where([data-slot=combobox-content]) .tw\\:in-data-\\[slot\\=combobox-content\\]\\:focus-within\\:ring-0:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:where([data-slot=dialog-content]) .tw\\:in-data-\\[slot\\=dialog-content\\]\\:rounded-lg\\!{border-radius:var(--radius)!important}:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/20{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/20{background-color:color-mix(in oklab, var(--background) 20%, transparent)}}:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:text-background{color:var(--background)}.tw\\:has-disabled\\:bg-input\\/50:has(:disabled){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-disabled\\:bg-input\\/50:has(:disabled){background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:has-disabled\\:opacity-50:has(:disabled){opacity:.5}.tw\\:has-aria-expanded\\:bg-muted\\/50:has([aria-expanded=true]){background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-aria-expanded\\:bg-muted\\/50:has([aria-expanded=true]){background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-1:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-1\\.5:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-2:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:has-data-\\[icon\\=inline-end\\]\\:pe-1\\.5:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-1:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-1\\.5:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-2:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:has-data-\\[icon\\=inline-start\\]\\:ps-1\\.5:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[slot\\=alert-action\\]\\:relative:has([data-slot=alert-action]){position:relative}.tw\\:has-data-\\[slot\\=alert-action\\]\\:pe-18:has([data-slot=alert-action]){padding-inline-end:calc(calc(var(--spacing)) * 18)}.tw\\:has-data-\\[slot\\=card-action\\]\\:grid-cols-\\[1fr_auto\\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.tw\\:has-data-\\[slot\\=card-description\\]\\:grid-rows-\\[auto_auto\\]:has([data-slot=card-description]){grid-template-rows:auto auto}.tw\\:has-data-\\[slot\\=card-footer\\]\\:pb-0:has([data-slot=card-footer]){padding-bottom:calc(calc(var(--spacing)) * 0)}.tw\\:has-data-\\[slot\\=kbd\\]\\:pe-1\\.5:has([data-slot=kbd]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[variant\\=inset\\]\\:bg-sidebar:has([data-variant=inset]){background-color:var(--sidebar)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:border-ring:has([data-slot=input-group-control]:focus-visible){border-color:var(--ring)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-3:has([data-slot=input-group-control]:focus-visible){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-ring\\/50:has([data-slot=input-group-control]:focus-visible){--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-ring\\/50:has([data-slot=input-group-control]:focus-visible){--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:border-destructive:has([data-slot][aria-invalid=true]){border-color:var(--destructive)}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-3:has([data-slot][aria-invalid=true]){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/20:has([data-slot][aria-invalid=true]){--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/20:has([data-slot][aria-invalid=true]){--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:h-auto:has(>[data-align=block-end]){height:auto}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:flex-col:has(>[data-align=block-end]){flex-direction:column}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:h-auto:has(>[data-align=block-start]){height:auto}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:flex-col:has(>[data-align=block-start]){flex-direction:column}.tw\\:has-\\[\\>\\[data-slot\\=button-group\\]\\]\\:gap-2:has(>[data-slot=button-group]){gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>button\\]\\:ms-\\[-0\\.3rem\\]:has(>button){margin-inline-start:-.3rem}.tw\\:has-\\[\\>button\\]\\:me-\\[-0\\.3rem\\]:has(>button){margin-inline-end:-.3rem}.tw\\:has-\\[\\>img\\]\\:grid-cols-\\[auto_1fr\\]:has(>img){grid-template-columns:auto 1fr}.tw\\:has-\\[\\>img\\]\\:gap-x-2:has(>img){column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>img\\:first-child\\]\\:pt-0:has(>img:first-child){padding-top:calc(calc(var(--spacing)) * 0)}.tw\\:has-\\[\\>kbd\\]\\:ms-\\[-0\\.15rem\\]:has(>kbd){margin-inline-start:-.15rem}.tw\\:has-\\[\\>kbd\\]\\:me-\\[-0\\.15rem\\]:has(>kbd){margin-inline-end:-.15rem}.tw\\:has-\\[\\>svg\\]\\:grid-cols-\\[auto_1fr\\]:has(>svg){grid-template-columns:auto 1fr}.tw\\:has-\\[\\>svg\\]\\:gap-x-2:has(>svg){column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>svg\\]\\:p-0:has(>svg){padding:calc(calc(var(--spacing)) * 0)}.tw\\:has-\\[\\>textarea\\]\\:h-auto:has(>textarea){height:auto}.tw\\:aria-disabled\\:pointer-events-none[aria-disabled=true]{pointer-events:none}.tw\\:aria-disabled\\:opacity-50[aria-disabled=true]{opacity:.5}.tw\\:aria-expanded\\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.tw\\:aria-expanded\\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.tw\\:aria-expanded\\:text-foreground[aria-expanded=true]{color:var(--foreground)}.tw\\:aria-expanded\\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.tw\\:aria-expanded\\:opacity-100[aria-expanded=true]{opacity:1}.tw\\:aria-invalid\\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.tw\\:aria-invalid\\:ring-0[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:aria-invalid\\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:aria-invalid\\:ring-destructive\\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:aria-invalid\\:ring-destructive\\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:aria-invalid\\:aria-checked\\:border-primary[aria-invalid=true][aria-checked=true]{border-color:var(--primary)}.tw\\:aria-pressed\\:bg-muted[aria-pressed=true]{background-color:var(--muted)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:h-px[aria-orientation=horizontal]{height:1px}.tw\\:aria-\\[orientation\\=horizontal\\]\\:w-full[aria-orientation=horizontal]{width:100%}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:start-0[aria-orientation=horizontal]:after{content:var(--tw-content);inset-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:h-1[aria-orientation=horizontal]:after{content:var(--tw-content);height:calc(calc(var(--spacing)) * 1)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:w-full[aria-orientation=horizontal]:after{content:var(--tw-content);width:100%}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:translate-x-0[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:-translate-y-1\\/2[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:aria-\\[orientation\\=vertical\\]\\:flex-col[aria-orientation=vertical]{flex-direction:column}.tw\\:data-inset\\:ps-7[data-inset]{padding-inline-start:calc(calc(var(--spacing)) * 7)}.tw\\:data-placeholder\\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.tw\\:data-\\[align-trigger\\=false\\]\\:min-w-36[data-align-trigger=false]{min-width:calc(calc(var(--spacing)) * 36)}.tw\\:data-\\[align-trigger\\=true\\]\\:min-w-\\(--radix-select-trigger-width\\)[data-align-trigger=true]{min-width:var(--radix-select-trigger-width)}.tw\\:data-\\[align-trigger\\=true\\]\\:animate-none[data-align-trigger=true]{animation:none}.tw\\:data-\\[disabled\\=true\\]\\:pointer-events-none[data-disabled=true]{pointer-events:none}.tw\\:data-\\[disabled\\=true\\]\\:opacity-50[data-disabled=true]{opacity:.5}.tw\\:data-\\[position\\=popper\\]\\:h-\\(--radix-select-trigger-height\\)[data-position=popper]{height:var(--radix-select-trigger-height)}.tw\\:data-\\[position\\=popper\\]\\:w-full[data-position=popper]{width:100%}.tw\\:data-\\[position\\=popper\\]\\:min-w-\\(--radix-select-trigger-width\\)[data-position=popper]{min-width:var(--radix-select-trigger-width)}.tw\\:data-\\[side\\=bottom\\]\\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=bottom\\]\\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.tw\\:data-\\[side\\=left\\]\\:-translate-x-1[data-side=left]{--tw-translate-x:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=left\\]\\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.tw\\:data-\\[side\\=right\\]\\:translate-x-1[data-side=right]{--tw-translate-x:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=right\\]\\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.tw\\:data-\\[side\\=top\\]\\:-translate-y-1[data-side=top]{--tw-translate-y:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=top\\]\\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.tw\\:data-\\[size\\=default\\]\\:h-8[data-size=default]{height:calc(calc(var(--spacing)) * 8)}.tw\\:data-\\[size\\=default\\]\\:h-\\[18\\.4px\\][data-size=default]{height:18.4px}.tw\\:data-\\[size\\=default\\]\\:w-\\[32px\\][data-size=default]{width:32px}.tw\\:data-\\[size\\=lg\\]\\:size-10[data-size=lg]{width:calc(calc(var(--spacing)) * 10);height:calc(calc(var(--spacing)) * 10)}.tw\\:data-\\[size\\=md\\]\\:text-sm[data-size=md]{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:data-\\[size\\=sm\\]\\:size-6[data-size=sm]{width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:data-\\[size\\=sm\\]\\:h-7[data-size=sm]{height:calc(calc(var(--spacing)) * 7)}.tw\\:data-\\[size\\=sm\\]\\:h-\\[14px\\][data-size=sm]{height:14px}.tw\\:data-\\[size\\=sm\\]\\:w-\\[24px\\][data-size=sm]{width:24px}.tw\\:data-\\[size\\=sm\\]\\:gap-3[data-size=sm]{gap:calc(calc(var(--spacing)) * 3)}.tw\\:data-\\[size\\=sm\\]\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,10px\\)\\][data-size=sm]{border-radius:min(var(--tw-radius-md), 10px)}.tw\\:data-\\[size\\=sm\\]\\:py-3[data-size=sm]{padding-block:calc(calc(var(--spacing)) * 3)}.tw\\:data-\\[size\\=sm\\]\\:text-xs[data-size=sm]{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:data-\\[size\\=sm\\]\\:has-data-\\[slot\\=card-footer\\]\\:pb-0[data-size=sm]:has([data-slot=card-footer]){padding-bottom:calc(calc(var(--spacing)) * 0)}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-item]:focus{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-item]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:data-highlighted\\:bg-foreground\\/10 *)[data-slot$=-item][data-highlighted]{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:data-highlighted\\:bg-foreground\\/10 *)[data-slot$=-item][data-highlighted]{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-separator\\]\\:bg-foreground\\/5 *)[data-slot$=-separator]{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-separator\\]\\:bg-foreground\\/5 *)[data-slot$=-separator]{background-color:color-mix(in oklab, var(--foreground) 5%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-trigger]:focus{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-trigger]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:aria-expanded\\:bg-foreground\\/10\\! *)[data-slot$=-trigger][aria-expanded=true]{background-color:var(--foreground)!important}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:aria-expanded\\:bg-foreground\\/10\\! *)[data-slot$=-trigger][aria-expanded=true]{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)!important}}:is(.tw\\:\\*\\:data-\\[slot\\=alert-description\\]\\:text-destructive\\/90>*)[data-slot=alert-description]{color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\:data-\\[slot\\=alert-description\\]\\:text-destructive\\/90>*)[data-slot=alert-description]{color:color-mix(in oklab, var(--destructive) 90%, transparent)}}:is(.tw\\:\\*\\:data-\\[slot\\=avatar\\]\\:ring-2>*)[data-slot=avatar]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:is(.tw\\:\\*\\:data-\\[slot\\=avatar\\]\\:ring-background>*)[data-slot=avatar]{--tw-ring-color:var(--background)}:is(.tw\\:\\*\\:data-\\[slot\\=input-group-addon\\]\\:ps-2\\!>*)[data-slot=input-group-addon]{padding-inline-start:calc(calc(var(--spacing)) * 2)!important}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:relative *)[data-slot=kbd]{position:relative}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:isolate *)[data-slot=kbd]{isolation:isolate}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:z-50 *)[data-slot=kbd]{z-index:50}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:rounded-sm *)[data-slot=kbd]{border-radius:calc(var(--radius) * .6)}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:flex>*)[data-slot=select-value]{display:flex}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:flex-1>*)[data-slot=select-value]{flex:1}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:items-center>*)[data-slot=select-value]{align-items:center}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:gap-1\\.5>*)[data-slot=select-value]{gap:calc(calc(var(--spacing)) * 1.5)}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:text-start>*)[data-slot=select-value]{text-align:start}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:first\\:rounded-s-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:first-child{border-start-start-radius:var(--radius);border-end-start-radius:var(--radius)}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:first\\:rounded-t-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:first-child{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:last\\:rounded-e-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:last-child{border-start-end-radius:var(--radius);border-end-end-radius:var(--radius)}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:last\\:rounded-b-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:last-child{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.tw\\:data-\\[state\\=active\\]\\:bg-background[data-state=active]{background-color:var(--background)}.tw\\:data-\\[state\\=active\\]\\:text-foreground[data-state=active]{color:var(--foreground)}.tw\\:data-\\[state\\=active\\]\\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:data-\\[state\\=closed\\]\\:overflow-hidden[data-state=closed]{overflow:hidden}.tw\\:data-\\[state\\=delayed-open\\]\\:animate-in[data-state=delayed-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-\\[state\\=delayed-open\\]\\:fade-in-0[data-state=delayed-open]{--tw-enter-opacity:0}.tw\\:data-\\[state\\=delayed-open\\]\\:zoom-in-95[data-state=delayed-open]{--tw-enter-scale:.95}.tw\\:data-\\[state\\=on\\]\\:bg-muted[data-state=on]{background-color:var(--muted)}.tw\\:data-\\[state\\=open\\]\\:bg-accent[data-state=open]{background-color:var(--accent)}.tw\\:data-\\[state\\=open\\]\\:bg-muted[data-state=open]{background-color:var(--muted)}.tw\\:data-\\[state\\=open\\]\\:text-foreground[data-state=open]{color:var(--foreground)}.tw\\:data-\\[state\\=selected\\]\\:bg-muted[data-state=selected]{background-color:var(--muted)}.tw\\:data-\\[variant\\=destructive\\]\\:text-destructive[data-variant=destructive]{color:var(--destructive)}:is(:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:\\*\\*\\:text-accent-foreground\\! *)[data-variant=destructive] *),:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:text-accent-foreground\\! *)[data-variant=destructive]{color:var(--accent-foreground)!important}.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-foreground\\/10\\! *)[data-variant=destructive]:focus{background-color:var(--foreground)!important}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-foreground\\/10\\! *)[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)!important}}.tw\\:data-\\[variant\\=line\\]\\:rounded-none[data-variant=line]{border-radius:0}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:border-s-0:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:border-t-0:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]{border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:first\\:border-s:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]:first-child{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:first\\:border-t:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]:first-child{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:inset-x-0[data-vaul-drawer-direction=bottom]{inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:bottom-0[data-vaul-drawer-direction=bottom]{bottom:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:mt-24[data-vaul-drawer-direction=bottom]{margin-top:calc(calc(var(--spacing)) * 24)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:max-h-\\[80vh\\][data-vaul-drawer-direction=bottom]{max-height:80vh}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:rounded-t-xl[data-vaul-drawer-direction=bottom]{border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:border-t[data-vaul-drawer-direction=bottom]{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:inset-y-0[data-vaul-drawer-direction=left]{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:left-0[data-vaul-drawer-direction=left]{left:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:w-3\\/4[data-vaul-drawer-direction=left]{width:75%}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:flex-row[data-vaul-drawer-direction=left]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:rounded-r-xl[data-vaul-drawer-direction=left]{border-top-right-radius:calc(var(--radius) * 1.4);border-bottom-right-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:border-r[data-vaul-drawer-direction=left]{border-right-style:var(--tw-border-style);border-right-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=left\\/right\\]\\:flex-row[data-vaul-drawer-direction=left\\/right]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:inset-y-0[data-vaul-drawer-direction=right]{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:right-0[data-vaul-drawer-direction=right]{right:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:w-3\\/4[data-vaul-drawer-direction=right]{width:75%}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:flex-row[data-vaul-drawer-direction=right]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:rounded-l-xl[data-vaul-drawer-direction=right]{border-top-left-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:border-l[data-vaul-drawer-direction=right]{border-left-style:var(--tw-border-style);border-left-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:inset-x-0[data-vaul-drawer-direction=top]{inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:top-0[data-vaul-drawer-direction=top]{top:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:mb-24[data-vaul-drawer-direction=top]{margin-bottom:calc(calc(var(--spacing)) * 24)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:max-h-\\[80vh\\][data-vaul-drawer-direction=top]{max-height:80vh}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:rounded-b-xl[data-vaul-drawer-direction=top]{border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:border-b[data-vaul-drawer-direction=top]{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}@supports ((-webkit-backdrop-filter:var(--tw)) or (backdrop-filter:var(--tw))){.tw\\:supports-backdrop-filter\\:backdrop-blur-xs{--tw-backdrop-blur:blur(var(--tw-blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}}@media (min-width:40rem){.tw\\:sm\\:flex{display:flex}.tw\\:sm\\:max-w-sm{max-width:var(--tw-container-sm)}.tw\\:sm\\:flex-row{flex-direction:row}.tw\\:sm\\:justify-end{justify-content:flex-end}.tw\\:sm\\:p-8{padding:calc(calc(var(--spacing)) * 8)}.tw\\:sm\\:text-start{text-align:start}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:sm\\:max-w-sm[data-vaul-drawer-direction=left],.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:sm\\:max-w-sm[data-vaul-drawer-direction=right]{max-width:var(--tw-container-sm)}}@media (min-width:48rem){.tw\\:md\\:block{display:block}.tw\\:md\\:flex{display:flex}.tw\\:md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.tw\\:md\\:gap-0\\.5{gap:calc(calc(var(--spacing)) * .5)}.tw\\:md\\:text-start{text-align:start}.tw\\:md\\:text-sm{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:md\\:text-pretty{text-wrap:pretty}.tw\\:md\\:opacity-0{opacity:0}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:m-2:is(:where(.tw\\:peer)[data-variant=inset]~*){margin:calc(calc(var(--spacing)) * 2)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:ms-0:is(:where(.tw\\:peer)[data-variant=inset]~*){margin-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:rounded-xl:is(:where(.tw\\:peer)[data-variant=inset]~*){border-radius:calc(var(--radius) * 1.4)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:shadow-sm:is(:where(.tw\\:peer)[data-variant=inset]~*){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:peer-data-\\[state\\=collapsed\\]\\:ms-2:is(:where(.tw\\:peer)[data-variant=inset]~*):is(:where(.tw\\:peer)[data-state=collapsed]~*){margin-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:md\\:after\\:hidden:after{content:var(--tw-content);display:none}}@media (min-width:64rem){.tw\\:lg\\:flex{display:flex}.tw\\:lg\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}:where(.tw\\:lg\\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 8) * calc(1 - var(--tw-space-x-reverse)))}.tw\\:lg\\:text-5xl{font-size:var(--tw-text-5xl);line-height:var(--tw-leading,var(--tw-text-5xl--line-height))}}@media (min-width:48rem){@media (min-width:64rem){.tw\\:md\\:lg\\:hidden{display:none}}}@container (min-width:24rem){.tw\\:\\@sm\\:basis-auto{flex-basis:auto}}.tw\\:ltr\\:left-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:calc(calc(var(--spacing)) * 2)}.tw\\:ltr\\:-translate-x-1\\/2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:right-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:calc(calc(var(--spacing)) * 2)}.tw\\:rtl\\:flex:where(:dir(rtl),[dir=rtl],[dir=rtl] *){display:flex}.tw\\:rtl\\:-translate-x-px:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:translate-x-1\\/2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(1 / 2 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:translate-x-px:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:after\\:translate-x-1\\/2:where(:dir(rtl),[dir=rtl],[dir=rtl] *):after{content:var(--tw-content);--tw-translate-x:calc(1 / 2 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}:where([data-side=primary]) .tw\\:rtl\\:in-data-\\[side\\=primary\\]\\:cursor-e-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:e-resize}:where([data-side=secondary]) .tw\\:rtl\\:in-data-\\[side\\=secondary\\]\\:cursor-w-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:w-resize}.tw\\:rtl\\:aria-\\[orientation\\=horizontal\\]\\:after\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:data-\\[side\\=left\\]\\:translate-x-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[data-side=left]{--tw-translate-x:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:data-\\[side\\=right\\]\\:-translate-x-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[data-side=right]{--tw-translate-x:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:border-input:is(.dark *){border-color:var(--input)}.tw\\:dark\\:bg-destructive\\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:bg-destructive\\/20:is(.dark *){background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:dark\\:bg-input\\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:bg-input\\/30:is(.dark *){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:dark\\:bg-transparent:is(.dark *){background-color:#0000}.tw\\:dark\\:text-muted-foreground:is(.dark *){color:var(--muted-foreground)}.tw\\:dark\\:text-rose-400:is(.dark *){color:var(--tw-color-rose-400)}.tw\\:dark\\:text-sky-400:is(.dark *){color:var(--tw-color-sky-400)}.tw\\:dark\\:text-teal-400:is(.dark *){color:var(--tw-color-teal-400)}.tw\\:dark\\:after\\:mix-blend-lighten:is(.dark *):after{content:var(--tw-content);mix-blend-mode:lighten}@media (hover:hover){.tw\\:dark\\:hover\\:bg-blue-500:is(.dark *):hover{background-color:var(--tw-color-blue-500)}.tw\\:dark\\:hover\\:bg-destructive\\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-destructive\\/30:is(.dark *):hover{background-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.tw\\:dark\\:hover\\:bg-input\\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-input\\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:dark\\:hover\\:bg-muted\\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-muted\\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:dark\\:hover\\:text-foreground:is(.dark *):hover{color:var(--foreground)}}.tw\\:dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:disabled\\:bg-input\\/80:is(.dark *):disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:disabled\\:bg-input\\/80:is(.dark *):disabled{background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:dark\\:disabled\\:bg-transparent:is(.dark *):disabled{background-color:#0000}:where([data-slot=tooltip-content]) .tw\\:dark\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/10:is(.dark *){background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){:where([data-slot=tooltip-content]) .tw\\:dark\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/10:is(.dark *){background-color:color-mix(in oklab, var(--background) 10%, transparent)}}.tw\\:dark\\:has-disabled\\:bg-input\\/80:is(.dark *):has(:disabled){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:has-disabled\\:bg-input\\/80:is(.dark *):has(:disabled){background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:dark\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/40:is(.dark *):has([data-slot][aria-invalid=true]){--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/40:is(.dark *):has([data-slot][aria-invalid=true]){--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:aria-invalid\\:border-destructive\\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:aria-invalid\\:border-destructive\\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab, var(--destructive) 50%, transparent)}}.tw\\:dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:data-open\\:animate-in:where([data-state=open]),.tw\\:data-open\\:animate-in:where([data-open]:not([data-open=false])){animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-open\\:bg-accent:where([data-state=open]),.tw\\:data-open\\:bg-accent:where([data-open]:not([data-open=false])){background-color:var(--accent)}.tw\\:data-open\\:text-accent-foreground:where([data-state=open]),.tw\\:data-open\\:text-accent-foreground:where([data-open]:not([data-open=false])){color:var(--accent-foreground)}.tw\\:data-open\\:fade-in-0:where([data-state=open]),.tw\\:data-open\\:fade-in-0:where([data-open]:not([data-open=false])){--tw-enter-opacity:0}.tw\\:data-open\\:zoom-in-95:where([data-state=open]),.tw\\:data-open\\:zoom-in-95:where([data-open]:not([data-open=false])){--tw-enter-scale:.95}@media (hover:hover){:is(.tw\\:data-open\\:hover\\:bg-sidebar-accent:where([data-state=open]),.tw\\:data-open\\:hover\\:bg-sidebar-accent:where([data-open]:not([data-open=false]))):hover{background-color:var(--sidebar-accent)}:is(.tw\\:data-open\\:hover\\:text-sidebar-accent-foreground:where([data-state=open]),.tw\\:data-open\\:hover\\:text-sidebar-accent-foreground:where([data-open]:not([data-open=false]))):hover{color:var(--sidebar-accent-foreground)}}.tw\\:data-closed\\:animate-out:where([data-state=closed]),.tw\\:data-closed\\:animate-out:where([data-closed]:not([data-closed=false])){animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-closed\\:fade-out-0:where([data-state=closed]),.tw\\:data-closed\\:fade-out-0:where([data-closed]:not([data-closed=false])){--tw-exit-opacity:0}.tw\\:data-closed\\:zoom-out-95:where([data-state=closed]),.tw\\:data-closed\\:zoom-out-95:where([data-closed]:not([data-closed=false])){--tw-exit-scale:.95}.tw\\:data-checked\\:border-primary:where([data-state=checked]),.tw\\:data-checked\\:border-primary:where([data-checked]:not([data-checked=false])){border-color:var(--primary)}.tw\\:data-checked\\:bg-primary:where([data-state=checked]),.tw\\:data-checked\\:bg-primary:where([data-checked]:not([data-checked=false])){background-color:var(--primary)}.tw\\:data-checked\\:text-primary-foreground:where([data-state=checked]),.tw\\:data-checked\\:text-primary-foreground:where([data-checked]:not([data-checked=false])){color:var(--primary-foreground)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=checked]),.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-checked]:not([data-checked=false])),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=checked]),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-checked]:not([data-checked=false])){--tw-translate-x:calc(100% - 2px);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=checked]),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-checked]:not([data-checked=false])),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=checked]),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-checked]:not([data-checked=false])){--tw-translate-x:calc(calc(100% - 2px) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:data-checked\\:bg-primary:is(.dark *):where([data-state=checked]),.tw\\:dark\\:data-checked\\:bg-primary:is(.dark *):where([data-checked]:not([data-checked=false])){background-color:var(--primary)}.tw\\:dark\\:data-checked\\:bg-primary-foreground:is(.dark *):where([data-state=checked]),.tw\\:dark\\:data-checked\\:bg-primary-foreground:is(.dark *):where([data-checked]:not([data-checked=false])){background-color:var(--primary-foreground)}.tw\\:data-unchecked\\:bg-input:where([data-state=unchecked]),.tw\\:data-unchecked\\:bg-input:where([data-unchecked]:not([data-unchecked=false])){background-color:var(--input)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=unchecked]),.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=unchecked]),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=unchecked]),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=unchecked]),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-unchecked]:not([data-unchecked=false])){--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:data-unchecked\\:bg-foreground:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-foreground:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:var(--foreground)}.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:data-selected\\:bg-muted:where([data-selected=true]){background-color:var(--muted)}.tw\\:data-selected\\:text-foreground:where([data-selected=true]){color:var(--foreground)}.tw\\:data-disabled\\:pointer-events-none:where([data-disabled=true]),.tw\\:data-disabled\\:pointer-events-none:where([data-disabled]:not([data-disabled=false])){pointer-events:none}.tw\\:data-disabled\\:cursor-not-allowed:where([data-disabled=true]),.tw\\:data-disabled\\:cursor-not-allowed:where([data-disabled]:not([data-disabled=false])){cursor:not-allowed}.tw\\:data-disabled\\:opacity-50:where([data-disabled=true]),.tw\\:data-disabled\\:opacity-50:where([data-disabled]:not([data-disabled=false])){opacity:.5}.tw\\:data-active\\:bg-background:where([data-state=active]),.tw\\:data-active\\:bg-background:where([data-active]:not([data-active=false])){background-color:var(--background)}.tw\\:data-active\\:bg-sidebar-accent:where([data-state=active]),.tw\\:data-active\\:bg-sidebar-accent:where([data-active]:not([data-active=false])){background-color:var(--sidebar-accent)}.tw\\:data-active\\:font-medium:where([data-state=active]),.tw\\:data-active\\:font-medium:where([data-active]:not([data-active=false])){--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:data-active\\:text-foreground:where([data-state=active]),.tw\\:data-active\\:text-foreground:where([data-active]:not([data-active=false])){color:var(--foreground)}.tw\\:data-active\\:text-sidebar-accent-foreground:where([data-state=active]),.tw\\:data-active\\:text-sidebar-accent-foreground:where([data-active]:not([data-active=false])){color:var(--sidebar-accent-foreground)}.tw\\:group-data-\\[variant\\=default\\]\\/tabs-list\\:data-active\\:shadow-sm:is(:where(.tw\\:group\\/tabs-list)[data-variant=default] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=default\\]\\/tabs-list\\:data-active\\:shadow-sm:is(:where(.tw\\:group\\/tabs-list)[data-variant=default] *):where([data-active]:not([data-active=false])){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){background-color:#0000}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:shadow-none:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:shadow-none:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:is(.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:after\\:opacity-100:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:after\\:opacity-100:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false]))):after{content:var(--tw-content);opacity:1}.tw\\:dark\\:data-active\\:border-input:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:border-input:is(.dark *):where([data-active]:not([data-active=false])){border-color:var(--input)}.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-active]:not([data-active=false])){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-active]:not([data-active=false])){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:dark\\:data-active\\:text-foreground:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:text-foreground:is(.dark *):where([data-active]:not([data-active=false])){color:var(--foreground)}.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:border-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:border-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){border-color:#0000}.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){background-color:#0000}.tw\\:data-horizontal\\:mx-px:where([data-orientation=horizontal]){margin-inline:1px}.tw\\:data-horizontal\\:h-1:where([data-orientation=horizontal]){height:calc(calc(var(--spacing)) * 1)}.tw\\:data-horizontal\\:h-full:where([data-orientation=horizontal]){height:100%}.tw\\:data-horizontal\\:h-px:where([data-orientation=horizontal]){height:1px}.tw\\:data-horizontal\\:w-auto:where([data-orientation=horizontal]){width:auto}.tw\\:data-horizontal\\:w-full:where([data-orientation=horizontal]){width:100%}.tw\\:data-horizontal\\:flex-col:where([data-orientation=horizontal]){flex-direction:column}.tw\\:data-vertical\\:my-px:where([data-orientation=vertical]){margin-block:1px}.tw\\:data-vertical\\:h-auto:where([data-orientation=vertical]){height:auto}.tw\\:data-vertical\\:h-full:where([data-orientation=vertical]){height:100%}.tw\\:data-vertical\\:min-h-40:where([data-orientation=vertical]){min-height:calc(calc(var(--spacing)) * 40)}.tw\\:data-vertical\\:w-1:where([data-orientation=vertical]){width:calc(calc(var(--spacing)) * 1)}.tw\\:data-vertical\\:w-auto:where([data-orientation=vertical]){width:auto}.tw\\:data-vertical\\:w-full:where([data-orientation=vertical]){width:100%}.tw\\:data-vertical\\:w-px:where([data-orientation=vertical]){width:1px}.tw\\:data-vertical\\:flex-col:where([data-orientation=vertical]){flex-direction:column}.tw\\:data-vertical\\:items-stretch:where([data-orientation=vertical]){align-items:stretch}.tw\\:data-vertical\\:self-stretch:where([data-orientation=vertical]){align-self:stretch}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:mt-0 [data-lexical-editor=true]>blockquote{margin-top:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:border-s-0 [data-lexical-editor=true]>blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:ps-0 [data-lexical-editor=true]>blockquote{padding-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:font-normal [data-lexical-editor=true]>blockquote{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:text-foreground [data-lexical-editor=true]>blockquote{color:var(--foreground)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:not-italic [data-lexical-editor=true]>blockquote{font-style:normal}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:mt-0 [data-lexical-editor=\\"true\\"]>blockquote{margin-top:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:border-s-0 [data-lexical-editor=\\"true\\"]>blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:ps-0 [data-lexical-editor=\\"true\\"]>blockquote{padding-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:font-normal [data-lexical-editor=\\"true\\"]>blockquote{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:text-foreground [data-lexical-editor=\\"true\\"]>blockquote{color:var(--foreground)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\\\\\"true\\\\\\"\\]\\>blockquote\\]\\:not-italic [data-lexical-editor=\\"true\\"]>blockquote{font-style:normal}.tw\\:\\[\\&_a\\]\\:underline a{text-decoration-line:underline}.tw\\:\\[\\&_a\\]\\:underline-offset-3 a{text-underline-offset:3px}@media (hover:hover){.tw\\:\\[\\&_a\\]\\:hover\\:text-foreground a:hover{color:var(--foreground)}}.tw\\:\\[\\&_p\\:not\\(\\:last-child\\)\\]\\:mb-4 p:not(:last-child){margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_svg\\]\\:pointer-events-none svg{pointer-events:none}.tw\\:\\[\\&_svg\\]\\:size-4 svg{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_svg\\]\\:shrink-0 svg{flex-shrink:0}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3\\.5 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_tr\\]\\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.tw\\:\\[\\&_tr\\:last-child\\]\\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.tw\\:\\[\\&\\:has\\(\\[role\\=checkbox\\]\\)\\]\\:pe-0:has([role=checkbox]){padding-inline-end:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\.border-b\\]\\:pb-2.border-b{padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:\\[\\.border-b\\]\\:pb-4.border-b{padding-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:\\[\\.border-b\\]\\:pb-3:is(:where(.tw\\:group\\/card)[data-size=sm] *).border-b{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:\\[\\.border-t\\]\\:pt-2.border-t{padding-top:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:px-2 *)[cmdk-group-heading]{padding-inline:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:py-1\\.5 *)[cmdk-group-heading]{padding-block:calc(calc(var(--spacing)) * 1.5)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:text-xs *)[cmdk-group-heading]{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:font-medium *)[cmdk-group-heading]{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:text-muted-foreground *)[cmdk-group-heading]{color:var(--muted-foreground)}:is(.tw\\:\\*\\:\\[a\\]\\:underline>*):is(a){text-decoration-line:underline}:is(.tw\\:\\*\\:\\[a\\]\\:underline-offset-3>*):is(a){text-underline-offset:3px}@media (hover:hover){.tw\\:\\[a\\]\\:hover\\:bg-destructive\\/20:is(a):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-destructive\\/20:is(a):hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:\\[a\\]\\:hover\\:bg-muted:is(a):hover{background-color:var(--muted)}.tw\\:\\[a\\]\\:hover\\:bg-primary\\/80:is(a):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-primary\\/80:is(a):hover{background-color:color-mix(in oklab, var(--primary) 80%, transparent)}}.tw\\:\\[a\\]\\:hover\\:bg-secondary\\/80:is(a):hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-secondary\\/80:is(a):hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.tw\\:\\[a\\]\\:hover\\:text-muted-foreground:is(a):hover{color:var(--muted-foreground)}:is(.tw\\:\\*\\:\\[a\\]\\:hover\\:text-foreground>*):is(a):hover{color:var(--foreground)}}:is(.tw\\:\\*\\:\\[img\\]\\:row-span-2>*):is(img){grid-row:span 2/span 2}:is(.tw\\:\\*\\:\\[img\\]\\:translate-y-0\\.5>*):is(img){--tw-translate-y:calc(calc(var(--spacing)) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}:is(.tw\\:\\*\\:\\[img\\]\\:text-current>*):is(img){color:currentColor}:is(.tw\\:\\*\\:\\[img\\:first-child\\]\\:rounded-t-xl>*):is(img:first-child){border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}:is(.tw\\:\\*\\:\\[img\\:last-child\\]\\:rounded-b-xl>*):is(img:last-child){border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}:is(.tw\\:\\*\\:\\[img\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>*):is(img:not([class*=size-])){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:flex>*):is(span):last-child{display:flex}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:items-center>*):is(span):last-child{align-items:center}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:gap-2>*):is(span):last-child{gap:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\:\\[svg\\]\\:row-span-2>*):is(svg){grid-row:span 2/span 2}:is(.tw\\:\\*\\:\\[svg\\]\\:translate-y-0\\.5>*):is(svg){--tw-translate-y:calc(calc(var(--spacing)) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}:is(.tw\\:\\*\\:\\[svg\\]\\:text-current>*):is(svg){color:currentColor}:is(.tw\\:focus\\:\\*\\:\\[svg\\]\\:text-accent-foreground:focus>*):is(svg){color:var(--accent-foreground)}:is(.tw\\:data-\\[variant\\=destructive\\]\\:\\*\\:\\[svg\\]\\:text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)}:is(.tw\\:data-\\[variant\\=destructive\\]\\:\\*\\:\\[svg\\]\\:text-destructive\\![data-variant=destructive]>*):is(svg){color:var(--destructive)!important}:is(.tw\\:data-selected\\:\\*\\:\\[svg\\]\\:text-foreground:where([data-selected=true])>*):is(svg){color:var(--foreground)}:is(.tw\\:\\*\\:\\[svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>*):is(svg:not([class*=size-])){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:rounded-s-none>:not(:first-child){border-start-start-radius:0;border-end-start-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:rounded-t-none>:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:border-s-0>:not(:first-child){border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:border-t-0>:not(:first-child){border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:rounded-e-none>:not(:last-child){border-start-end-radius:0;border-end-end-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:rounded-b-none>:not(:last-child){border-bottom-right-radius:0;border-bottom-left-radius:0}.tw\\:has-\\[select\\[aria-hidden\\=true\\]\\:last-child\\]\\:\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:last-of-type\\]\\:rounded-e-lg:has(:is(select[aria-hidden=true]:last-child))>[data-slot=select-trigger]:last-of-type{border-start-end-radius:var(--radius);border-end-end-radius:var(--radius)}.tw\\:\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:not\\(\\[class\\*\\=w-\\]\\)\\]\\:w-fit>[data-slot=select-trigger]:not([class*=w-]){width:fit-content}.tw\\:\\[\\&\\>\\[data-slot\\]\\:not\\(\\:has\\(\\~\\[data-slot\\]\\)\\)\\]\\:rounded-e-lg\\!>[data-slot]:not(:has(~[data-slot])){border-start-end-radius:var(--radius)!important;border-end-end-radius:var(--radius)!important}.tw\\:\\[\\&\\>\\[data-slot\\]\\:not\\(\\:has\\(\\~\\[data-slot\\]\\)\\)\\]\\:rounded-b-lg\\!>[data-slot]:not(:has(~[data-slot])){border-bottom-right-radius:var(--radius)!important;border-bottom-left-radius:var(--radius)!important}.tw\\:\\[\\&\\>blockquote\\]\\:border-s-0>blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&\\>blockquote\\]\\:p-0>blockquote{padding:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&\\>blockquote\\]\\:ps-0>blockquote{padding-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&\\>blockquote\\]\\:font-normal>blockquote{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:\\[\\&\\>blockquote\\]\\:text-foreground>blockquote{color:var(--foreground)}.tw\\:\\[\\&\\>blockquote\\]\\:not-italic>blockquote{font-style:normal}.tw\\:\\[\\&\\>input\\]\\:flex-1>input{flex:1}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:\\[\\&\\>input\\]\\:pt-3:has(>[data-align=block-end])>input{padding-top:calc(calc(var(--spacing)) * 3)}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:\\[\\&\\>input\\]\\:pb-3:has(>[data-align=block-start])>input{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:has-\\[\\>\\[data-align\\=inline-end\\]\\]\\:\\[\\&\\>input\\]\\:pe-1\\.5:has(>[data-align=inline-end])>input{padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-\\[\\>\\[data-align\\=inline-start\\]\\]\\:\\[\\&\\>input\\]\\:ps-1\\.5:has(>[data-align=inline-start])>input{padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:\\[\\&\\>kbd\\]\\:rounded-\\[calc\\(var\\(--radius\\)-5px\\)\\]>kbd{border-radius:calc(var(--radius) - 5px)}.tw\\:\\[\\&\\>li\\]\\:mt-2>li{margin-top:calc(calc(var(--spacing)) * 2)}.tw\\:\\[\\&\\>span\\:last-child\\]\\:truncate>span:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tw\\:\\[\\&\\>svg\\]\\:pointer-events-none>svg{pointer-events:none}.tw\\:\\[\\&\\>svg\\]\\:size-3\\!>svg{width:calc(calc(var(--spacing)) * 3)!important;height:calc(calc(var(--spacing)) * 3)!important}.tw\\:\\[\\&\\>svg\\]\\:size-3\\.5>svg{width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&\\>svg\\]\\:size-4>svg{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>svg\\]\\:shrink-0>svg{flex-shrink:0}.tw\\:\\[\\&\\>svg\\]\\:text-sidebar-accent-foreground>svg{color:var(--sidebar-accent-foreground)}.tw\\:group-has-data-\\[size\\=lg\\]\\/avatar-group\\:\\[\\&\\>svg\\]\\:size-5:is(:where(.tw\\:group\\/avatar-group):has([data-size=lg]) *)>svg{width:calc(calc(var(--spacing)) * 5);height:calc(calc(var(--spacing)) * 5)}.tw\\:group-has-data-\\[size\\=sm\\]\\/avatar-group\\:\\[\\&\\>svg\\]\\:size-3:is(:where(.tw\\:group\\/avatar-group):has([data-size=sm]) *)>svg{width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=default\\]\\/avatar\\:\\[\\&\\>svg\\]\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=default] *)>svg,.tw\\:group-data-\\[size\\=lg\\]\\/avatar\\:\\[\\&\\>svg\\]\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=lg] *)>svg{width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:\\[\\&\\>svg\\]\\:hidden:is(:where(.tw\\:group\\/avatar)[data-size=sm] *)>svg{display:none}.tw\\:\\[\\&\\>svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3\\.5>svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&\\>svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>tr\\]\\:last\\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.tw\\:\\[\\&\\[align\\=center\\]\\]\\:text-center[align=center]{text-align:center}.tw\\:\\[\\&\\[align\\=right\\]\\]\\:text-right[align=right]{text-align:right}.tw\\:\\[\\&\\[aria-orientation\\=horizontal\\]\\>div\\]\\:rotate-90[aria-orientation=horizontal]>div{rotate:90deg}[data-side=primary][data-collapsible=offcanvas] .tw\\:\\[\\[data-side\\=primary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:-end-2{inset-inline-end:calc(calc(var(--spacing)) * -2)}[data-side=primary][data-state=collapsed] .tw\\:\\[\\[data-side\\=primary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-e-resize{cursor:e-resize}[data-side=primary][data-state=collapsed] .tw\\:rtl\\:\\[\\[data-side\\=primary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-w-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:w-resize}[data-side=secondary][data-collapsible=offcanvas] .tw\\:\\[\\[data-side\\=secondary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:-start-2{inset-inline-start:calc(calc(var(--spacing)) * -2)}[data-side=secondary][data-state=collapsed] .tw\\:\\[\\[data-side\\=secondary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-w-resize{cursor:w-resize}[data-side=secondary][data-state=collapsed] .tw\\:rtl\\:\\[\\[data-side\\=secondary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-e-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:e-resize}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.light,:root{--radius:.625rem;--spacing:.25rem;--background:oklch(100% 0 0);--foreground:oklch(13.71% .036 258.53);--card:oklch(100% 0 0);--card-foreground:oklch(13.71% .036 258.53);--popover:oklch(98.43% .0018 248.56);--popover-foreground:oklch(13.71% .036 258.53);--primary:oklch(20.79% .0399 265.73);--primary-foreground:oklch(98.38% .0036 248.23);--secondary:oklch(95.89% .011 248.06);--secondary-foreground:oklch(20.79% .0399 265.73);--muted:oklch(95.89% .011 248.06);--muted-foreground:oklch(55.47% .0408 257.45);--accent:oklch(95.89% .011 248.06);--accent-foreground:oklch(20.79% .0399 265.73);--destructive:oklch(63.69% .2077 25.32);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(62.7% .194 149.214);--warning:oklch(84% .16 84);--warning-foreground:oklch(28% .07 46);--border:oklch(92.9% .0127 255.58);--input:oklch(92.9% .0127 255.58);--ring:oklch(13.71% .036 258.53);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--sidebar:oklch(98.43% .0018 248.56);--sidebar-foreground:oklch(13.71% .036 258.53);--sidebar-primary:oklch(20.79% .0399 265.73);--sidebar-primary-foreground:oklch(98.38% .0036 248.23);--sidebar-accent:oklch(95.89% .011 248.06);--sidebar-accent-foreground:oklch(20.79% .0399 265.73);--sidebar-border:oklch(92.9% .0127 255.58);--sidebar-ring:oklch(13.71% .036 258.53)}.dark{--background:oklch(13.71% .036 258.53);--foreground:oklch(98.38% .0036 248.23);--card:oklch(13.71% .036 258.53);--card-foreground:oklch(98.38% .0036 248.23);--popover:oklch(13.71% .036 258.53);--popover-foreground:oklch(98.38% .0036 248.23);--primary:oklch(98.38% .0036 248.23);--primary-foreground:oklch(20.79% .0399 265.73);--secondary:oklch(28% .037 259.98);--secondary-foreground:oklch(98.38% .0036 248.23);--muted:oklch(28% .037 259.98);--muted-foreground:oklch(71.07% .0351 256.8);--accent:oklch(28% .037 259.98);--accent-foreground:oklch(98.38% .0036 248.23);--destructive:oklch(39.6% .1331 25.71);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(79.2% .209 151.711);--warning:oklch(41% .11 46);--warning-foreground:oklch(99% .02 95);--border:oklch(44.54% .0374 257.3);--input:oklch(44.54% .0374 257.3);--ring:oklch(86.88% .0199 252.89);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439);--sidebar:oklch(13.71% .036 258.53);--sidebar-foreground:oklch(71.07% .0351 256.8);--sidebar-primary:oklch(98.38% .0036 248.23);--sidebar-primary-foreground:oklch(20.79% .0399 265.73);--sidebar-accent:oklch(28% .037 259.98);--sidebar-accent-foreground:oklch(71.07% .0351 256.8);--sidebar-border:oklch(28% .037 259.98);--sidebar-ring:oklch(86.88% .0199 252.89)}.paratext-light{--background:oklch(100% 0 0);--foreground:oklch(15.3% .006 107.1);--card:oklch(100% 0 0);--card-foreground:oklch(15.3% .006 107.1);--popover:oklch(100% 0 0);--popover-foreground:oklch(15.3% .006 107.1);--primary:oklch(55.5% .163 48.998);--primary-foreground:oklch(98.7% .022 95.277);--secondary:oklch(96.7% .001 286.375);--secondary-foreground:oklch(21% .006 285.885);--muted:oklch(96.6% .005 106.5);--muted-foreground:oklch(58% .031 107.3);--accent:oklch(96.6% .005 106.5);--accent-foreground:oklch(22.8% .013 107.4);--destructive:oklch(57.7% .245 27.325);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(62.7% .194 149.214);--warning:oklch(84% .16 84);--warning-foreground:oklch(28% .07 46);--border:oklch(93% .007 106.5);--input:oklch(93% .007 106.5);--ring:oklch(73.7% .021 106.9);--chart-1:oklch(88% .011 106.6);--chart-2:oklch(58% .031 107.3);--chart-3:oklch(46.6% .025 107.3);--chart-4:oklch(39.4% .023 107.4);--chart-5:oklch(28.6% .016 107.4);--sidebar:oklch(98.8% .003 106.5);--sidebar-foreground:oklch(15.3% .006 107.1);--sidebar-primary:oklch(66.6% .179 58.318);--sidebar-primary-foreground:oklch(98.7% .022 95.277);--sidebar-accent:oklch(96.6% .005 106.5);--sidebar-accent-foreground:oklch(22.8% .013 107.4);--sidebar-border:oklch(93% .007 106.5);--sidebar-ring:oklch(73.7% .021 106.9)}.paratext-dark{--background:oklch(15.3% .006 107.1);--foreground:oklch(98.8% .003 106.5);--card:oklch(22.8% .013 107.4);--card-foreground:oklch(98.8% .003 106.5);--popover:oklch(22.8% .013 107.4);--popover-foreground:oklch(98.8% .003 106.5);--primary:oklch(47.3% .137 46.201);--primary-foreground:oklch(98.7% .022 95.277);--secondary:oklch(27.4% .006 286.033);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(28.6% .016 107.4);--muted-foreground:oklch(73.7% .021 106.9);--accent:oklch(28.6% .016 107.4);--accent-foreground:oklch(98.8% .003 106.5);--destructive:oklch(70.4% .191 22.216);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(79.2% .209 151.711);--warning:oklch(41% .11 46);--warning-foreground:oklch(99% .02 95);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(58% .031 107.3);--chart-1:oklch(88% .011 106.6);--chart-2:oklch(58% .031 107.3);--chart-3:oklch(46.6% .025 107.3);--chart-4:oklch(39.4% .023 107.4);--chart-5:oklch(28.6% .016 107.4);--sidebar:oklch(22.8% .013 107.4);--sidebar-foreground:oklch(98.8% .003 106.5);--sidebar-primary:oklch(76.9% .188 70.08);--sidebar-primary-foreground:oklch(27.9% .077 45.635);--sidebar-accent:oklch(28.6% .016 107.4);--sidebar-accent-foreground:oklch(98.8% .003 106.5);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(58% .031 107.3)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}} +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-outline-style:solid;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--tw-font-sans:"IBM Plex Sans Variable", sans-serif;--tw-font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--tw-color-red-100:oklch(93.6% .032 17.717);--tw-color-red-200:oklch(88.5% .062 18.334);--tw-color-red-300:oklch(80.8% .114 19.571);--tw-color-red-400:oklch(70.4% .191 22.216);--tw-color-red-500:oklch(63.7% .237 25.331);--tw-color-red-600:oklch(57.7% .245 27.325);--tw-color-red-700:oklch(50.5% .213 27.518);--tw-color-red-800:oklch(44.4% .177 26.899);--tw-color-orange-100:oklch(95.4% .038 75.164);--tw-color-orange-800:oklch(47% .157 37.304);--tw-color-amber-200:oklch(92.4% .12 95.746);--tw-color-yellow-50:oklch(98.7% .026 102.212);--tw-color-yellow-100:oklch(97.3% .071 103.193);--tw-color-yellow-400:oklch(85.2% .199 91.936);--tw-color-yellow-500:oklch(79.5% .184 86.047);--tw-color-yellow-600:oklch(68.1% .162 75.834);--tw-color-yellow-700:oklch(55.4% .135 66.442);--tw-color-green-50:oklch(98.2% .018 155.826);--tw-color-green-100:oklch(96.2% .044 156.743);--tw-color-green-500:oklch(72.3% .219 149.579);--tw-color-green-600:oklch(62.7% .194 149.214);--tw-color-green-700:oklch(52.7% .154 150.069);--tw-color-green-800:oklch(44.8% .119 151.328);--tw-color-teal-400:oklch(77.7% .152 181.912);--tw-color-teal-500:oklch(70.4% .14 182.503);--tw-color-teal-600:oklch(60% .118 184.704);--tw-color-sky-400:oklch(74.6% .16 232.661);--tw-color-sky-500:oklch(68.5% .169 237.323);--tw-color-sky-600:oklch(58.8% .158 241.966);--tw-color-blue-50:oklch(97% .014 254.604);--tw-color-blue-100:oklch(93.2% .032 255.585);--tw-color-blue-400:oklch(70.7% .165 254.624);--tw-color-blue-500:oklch(62.3% .214 259.815);--tw-color-blue-600:oklch(54.6% .245 262.881);--tw-color-blue-800:oklch(42.4% .199 265.638);--tw-color-indigo-200:oklch(87% .065 274.039);--tw-color-purple-50:oklch(97.7% .014 308.299);--tw-color-purple-200:oklch(90.2% .063 306.703);--tw-color-purple-900:oklch(38.1% .176 304.987);--tw-color-rose-400:oklch(71.2% .194 13.428);--tw-color-rose-500:oklch(64.5% .246 16.439);--tw-color-rose-600:oklch(58.6% .253 17.585);--tw-color-slate-300:oklch(86.9% .022 252.894);--tw-color-slate-900:oklch(20.8% .042 265.755);--tw-color-gray-50:oklch(98.5% .002 247.839);--tw-color-gray-100:oklch(96.7% .003 264.542);--tw-color-gray-300:oklch(87.2% .01 258.338);--tw-color-gray-500:oklch(55.1% .027 264.364);--tw-color-gray-600:oklch(44.6% .03 256.802);--tw-color-gray-700:oklch(37.3% .034 259.733);--tw-color-gray-800:oklch(27.8% .033 256.848);--tw-color-zinc-400:oklch(70.5% .015 286.067);--tw-color-neutral-300:oklch(87% 0 0);--tw-color-black:#000;--tw-color-white:#fff;--tw-spacing:calc(var(--spacing));--tw-container-xs:20rem;--tw-container-sm:24rem;--tw-container-md:28rem;--tw-container-lg:32rem;--tw-container-2xl:42rem;--tw-container-3xl:48rem;--tw-container-4xl:56rem;--tw-container-6xl:72rem;--tw-text-xs:.75rem;--tw-text-xs--line-height:calc(1 / .75);--tw-text-sm:.875rem;--tw-text-sm--line-height:calc(1.25 / .875);--tw-text-base:1rem;--tw-text-base--line-height:calc(1.5 / 1);--tw-text-lg:1.125rem;--tw-text-lg--line-height:calc(1.75 / 1.125);--tw-text-xl:1.25rem;--tw-text-xl--line-height:calc(1.75 / 1.25);--tw-text-2xl:1.5rem;--tw-text-2xl--line-height:calc(2 / 1.5);--tw-text-3xl:1.875rem;--tw-text-3xl--line-height:calc(2.25 / 1.875);--tw-text-4xl:2.25rem;--tw-text-4xl--line-height:calc(2.5 / 2.25);--tw-text-5xl:3rem;--tw-text-5xl--line-height:1;--tw-font-weight-normal:400;--tw-font-weight-medium:500;--tw-font-weight-semibold:600;--tw-font-weight-bold:700;--tw-font-weight-extrabold:800;--tw-tracking-tight:-.025em;--tw-tracking-widest:.1em;--tw-leading-tight:1.25;--tw-leading-snug:1.375;--tw-leading-relaxed:1.625;--tw-leading-loose:2;--tw-radius-md:calc(var(--radius) * .8);--tw-radius-xl:calc(var(--radius) * 1.4);--tw-radius-2xl:calc(var(--radius) * 1.8);--tw-drop-shadow-sm:0 1px 2px #00000026;--tw-ease-in-out:cubic-bezier(.4, 0, .2, 1);--tw-animate-spin:spin 1s linear infinite;--tw-animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--tw-blur-xs:4px;--tw-blur-2xl:40px;--tw-default-transition-duration:.15s;--tw-default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--tw-default-font-family:"IBM Plex Sans Variable", sans-serif;--tw-default-mono-font-family:var(--tw-font-mono)}}@layer base{.pr-twp,.pr-twp *{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.pr-twp,.pr-twp *{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}body.pr-twp{background-color:var(--background);color:var(--foreground)}html.pr-twp{font-family:IBM Plex Sans Variable,sans-serif}:where(.pr-twp,.pr-twp *),:where(.pr-twp,.pr-twp *):after,:where(.pr-twp,.pr-twp *):before,:where(.pr-twp,.pr-twp *) ::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}:where(.pr-twp,.pr-twp *) ::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}.pr-twp{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--tw-default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--tw-default-font-feature-settings,normal);font-variation-settings:var(--tw-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr:where(.pr-twp,.pr-twp *){height:0;color:inherit;border-top-width:1px}abbr:where([title]):where(.pr-twp,.pr-twp *){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1:where(.pr-twp,.pr-twp *),h2:where(.pr-twp,.pr-twp *),h3:where(.pr-twp,.pr-twp *),h4:where(.pr-twp,.pr-twp *),h5:where(.pr-twp,.pr-twp *),h6:where(.pr-twp,.pr-twp *){font-size:inherit;font-weight:inherit}a:where(.pr-twp,.pr-twp *){color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b:where(.pr-twp,.pr-twp *),strong:where(.pr-twp,.pr-twp *){font-weight:bolder}code:where(.pr-twp,.pr-twp *),kbd:where(.pr-twp,.pr-twp *),samp:where(.pr-twp,.pr-twp *),pre:where(.pr-twp,.pr-twp *){font-family:var(--tw-default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--tw-default-mono-font-feature-settings,normal);font-variation-settings:var(--tw-default-mono-font-variation-settings,normal);font-size:1em}small:where(.pr-twp,.pr-twp *){font-size:80%}sub:where(.pr-twp,.pr-twp *),sup:where(.pr-twp,.pr-twp *){vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub:where(.pr-twp,.pr-twp *){bottom:-.25em}sup:where(.pr-twp,.pr-twp *){top:-.5em}table:where(.pr-twp,.pr-twp *){text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(.pr-twp,.pr-twp *){outline:auto}progress:where(.pr-twp,.pr-twp *){vertical-align:baseline}summary:where(.pr-twp,.pr-twp *){display:list-item}ol:where(.pr-twp,.pr-twp *),ul:where(.pr-twp,.pr-twp *),menu:where(.pr-twp,.pr-twp *){list-style:none}img:where(.pr-twp,.pr-twp *),svg:where(.pr-twp,.pr-twp *),video:where(.pr-twp,.pr-twp *),canvas:where(.pr-twp,.pr-twp *),audio:where(.pr-twp,.pr-twp *),iframe:where(.pr-twp,.pr-twp *),embed:where(.pr-twp,.pr-twp *),object:where(.pr-twp,.pr-twp *){vertical-align:middle;display:block}img:where(.pr-twp,.pr-twp *),video:where(.pr-twp,.pr-twp *){max-width:100%;height:auto}button:where(.pr-twp,.pr-twp *),input:where(.pr-twp,.pr-twp *),select:where(.pr-twp,.pr-twp *),optgroup:where(.pr-twp,.pr-twp *),textarea:where(.pr-twp,.pr-twp *){font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(.pr-twp,.pr-twp *) ::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup:where(.pr-twp,.pr-twp *){font-weight:bolder}:where(select:is([multiple],[size])) optgroup option:where(.pr-twp,.pr-twp *){padding-inline-start:20px}:where(.pr-twp,.pr-twp *) ::file-selector-button{margin-inline-end:4px}:where(.pr-twp,.pr-twp *) ::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){:where(.pr-twp,.pr-twp *) ::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){:where(.pr-twp,.pr-twp *) ::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea:where(.pr-twp,.pr-twp *){resize:vertical}:where(.pr-twp,.pr-twp *) ::-webkit-search-decoration{-webkit-appearance:none}:where(.pr-twp,.pr-twp *) ::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit{display:inline-flex}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-fields-wrapper{padding:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-year-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-month-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-day-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-hour-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-minute-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-second-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-millisecond-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-datetime-edit-meridiem-field{padding-block:0}:where(.pr-twp,.pr-twp *) ::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid:where(.pr-twp,.pr-twp *){box-shadow:none}button:where(.pr-twp,.pr-twp *),input:where([type=button],[type=reset],[type=submit]):where(.pr-twp,.pr-twp *){appearance:button}:where(.pr-twp,.pr-twp *) ::file-selector-button{appearance:button}:where(.pr-twp,.pr-twp *) ::-webkit-inner-spin-button{height:auto}:where(.pr-twp,.pr-twp *) ::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])):where(.pr-twp,.pr-twp *){display:none!important}}@layer components;@layer utilities{.tw\\:\\@container\\/card-header{container:card-header/inline-size}.tw\\:\\@container\\/toolbar{container:toolbar/inline-size}.tw\\:pointer-events-auto{pointer-events:auto}.tw\\:pointer-events-none{pointer-events:none}.tw\\:invisible{visibility:hidden}.tw\\:sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.tw\\:absolute{position:absolute}.tw\\:fixed{position:fixed}.tw\\:relative{position:relative}.tw\\:sticky{position:sticky}.tw\\:inset-0{inset:calc(calc(var(--spacing)) * 0)}.tw\\:inset-y-0{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:start-1\\.5{inset-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:start-1\\/2{inset-inline-start:50%}.tw\\:end-0{inset-inline-end:calc(calc(var(--spacing)) * 0)}.tw\\:end-1{inset-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:end-2{inset-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:end-3{inset-inline-end:calc(calc(var(--spacing)) * 3)}.tw\\:-top-\\[1px\\]{top:-1px}.tw\\:top-0{top:calc(calc(var(--spacing)) * 0)}.tw\\:top-1\\.5{top:calc(calc(var(--spacing)) * 1.5)}.tw\\:top-1\\/2{top:50%}.tw\\:top-1\\/3{top:33.3333%}.tw\\:top-2{top:calc(calc(var(--spacing)) * 2)}.tw\\:top-2\\.5{top:calc(calc(var(--spacing)) * 2.5)}.tw\\:top-3\\.5{top:calc(calc(var(--spacing)) * 3.5)}.tw\\:top-\\[-1px\\]{top:-1px}.tw\\:-right-1{right:calc(calc(var(--spacing)) * -1)}.tw\\:right-0{right:calc(calc(var(--spacing)) * 0)}.tw\\:right-1{right:calc(calc(var(--spacing)) * 1)}.tw\\:right-3{right:calc(calc(var(--spacing)) * 3)}.tw\\:bottom-0{bottom:calc(calc(var(--spacing)) * 0)}.tw\\:-left-\\[1px\\]{left:-1px}.tw\\:left-0{left:calc(calc(var(--spacing)) * 0)}.tw\\:left-2{left:calc(calc(var(--spacing)) * 2)}.tw\\:left-3{left:calc(calc(var(--spacing)) * 3)}.tw\\:isolate{isolation:isolate}.tw\\:z-10{z-index:10}.tw\\:z-20{z-index:20}.tw\\:z-50{z-index:50}.tw\\:order-first{order:-9999}.tw\\:order-last{order:9999}.tw\\:col-span-1{grid-column:span 1/span 1}.tw\\:col-span-2{grid-column:span 2/span 2}.tw\\:col-span-3{grid-column:span 3/span 3}.tw\\:col-start-1{grid-column-start:1}.tw\\:col-start-2{grid-column-start:2}.tw\\:row-span-2{grid-row:span 2/span 2}.tw\\:row-start-1{grid-row-start:1}.tw\\:row-start-2{grid-row-start:2}.tw\\:m-0{margin:calc(calc(var(--spacing)) * 0)}.tw\\:m-1{margin:calc(calc(var(--spacing)) * 1)}.tw\\:m-2{margin:calc(calc(var(--spacing)) * 2)}.tw\\:-mx-1{margin-inline:calc(calc(var(--spacing)) * -1)}.tw\\:-mx-4{margin-inline:calc(calc(var(--spacing)) * -4)}.tw\\:mx-0{margin-inline:calc(calc(var(--spacing)) * 0)}.tw\\:mx-1{margin-inline:calc(calc(var(--spacing)) * 1)}.tw\\:mx-2{margin-inline:calc(calc(var(--spacing)) * 2)}.tw\\:mx-3\\.5{margin-inline:calc(calc(var(--spacing)) * 3.5)}.tw\\:mx-8{margin-inline:calc(calc(var(--spacing)) * 8)}.tw\\:my-1{margin-block:calc(calc(var(--spacing)) * 1)}.tw\\:my-2\\.5{margin-block:calc(calc(var(--spacing)) * 2.5)}.tw\\:my-4{margin-block:calc(calc(var(--spacing)) * 4)}.tw\\:ms-1{margin-inline-start:calc(calc(var(--spacing)) * 1)}.tw\\:ms-2{margin-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:ms-5{margin-inline-start:calc(calc(var(--spacing)) * 5)}.tw\\:ms-auto{margin-inline-start:auto}.tw\\:me-1{margin-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:me-2{margin-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:prose{color:var(--tw-prose-body);max-width:65ch}.tw\\:prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.tw\\:prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);margin-top:1.2em;margin-bottom:1.2em;font-size:1.25em;line-height:1.6}.tw\\:prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.tw\\:prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.tw\\:prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:decimal}.tw\\:prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.tw\\:prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.tw\\:prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.tw\\:prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.tw\\:prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.tw\\:prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.tw\\:prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.tw\\:prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.tw\\:prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.tw\\:prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:disc}.tw\\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.tw\\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.tw\\:prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.25em;font-weight:600}.tw\\:prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.tw\\:prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em;font-style:italic;font-weight:500}.tw\\:prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.tw\\:prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.tw\\:prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:0;margin-bottom:.888889em;font-size:2.25em;font-weight:800;line-height:1.11111}.tw\\:prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.tw\\:prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:2em;margin-bottom:1em;font-size:1.5em;font-weight:700;line-height:1.33333}.tw\\:prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.tw\\:prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.6em;margin-bottom:.6em;font-size:1.25em;font-weight:600;line-height:1.6}.tw\\:prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.tw\\:prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.5em;margin-bottom:.5em;font-weight:600;line-height:1.5}.tw\\:prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.tw\\:prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em;display:block}.tw\\:prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-kbd);box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%);padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;border-radius:.3125rem;padding-inline-start:.375em;font-family:inherit;font-size:.875em;font-weight:500}.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"\`"}.tw\\:prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.tw\\:prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.tw\\:prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.tw\\:prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);padding-top:.857143em;padding-inline-end:1.14286em;padding-bottom:.857143em;border-radius:.375rem;margin-top:1.71429em;margin-bottom:1.71429em;padding-inline-start:1.14286em;font-size:.875em;font-weight:400;line-height:1.71429;overflow-x:auto}.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit;background-color:#0000;border-width:0;border-radius:0;padding:0}.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.tw\\:prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){table-layout:auto;width:100%;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.71429}.tw\\:prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.tw\\:prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);vertical-align:bottom;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em;font-weight:600}.tw\\:prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.tw\\:prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.tw\\:prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.tw\\:prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.tw\\:prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.tw\\:prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.tw\\:prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);margin-top:.857143em;font-size:.875em;line-height:1.42857}.tw\\:prose{--tw-prose-body:var(--foreground);--tw-prose-headings:var(--foreground);--tw-prose-lead:var(--muted-foreground);--tw-prose-links:var(--primary);--tw-prose-bold:var(--foreground);--tw-prose-counters:var(--muted-foreground);--tw-prose-bullets:var(--muted-foreground);--tw-prose-hr:var(--border);--tw-prose-quotes:var(--foreground);--tw-prose-quote-borders:var(--border);--tw-prose-captions:var(--muted-foreground);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:NaN NaN NaN;--tw-prose-code:var(--foreground);--tw-prose-pre-code:var(--muted-foreground);--tw-prose-pre-bg:var(--muted);--tw-prose-th-borders:var(--border);--tw-prose-td-borders:var(--border);--tw-prose-invert-body:var(--foreground);--tw-prose-invert-headings:var(--foreground);--tw-prose-invert-lead:var(--muted-foreground);--tw-prose-invert-links:var(--primary);--tw-prose-invert-bold:var(--foreground);--tw-prose-invert-counters:var(--muted-foreground);--tw-prose-invert-bullets:var(--muted-foreground);--tw-prose-invert-hr:var(--border);--tw-prose-invert-quotes:var(--foreground);--tw-prose-invert-quote-borders:var(--border);--tw-prose-invert-captions:var(--muted-foreground);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:var(--foreground);--tw-prose-invert-pre-code:var(--muted-foreground);--tw-prose-invert-pre-bg:var(--muted);--tw-prose-invert-th-borders:var(--border);--tw-prose-invert-td-borders:var(--border);font-size:1rem;line-height:1.75}.tw\\:prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.tw\\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.tw\\:prose :where(.tw\\:prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.tw\\:prose :where(.tw\\:prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.tw\\:prose :where(.tw\\:prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.tw\\:prose :where(.tw\\:prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.tw\\:prose :where(.tw\\:prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.tw\\:prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.tw\\:prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.tw\\:prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.tw\\:prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.571429em;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em}.tw\\:prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.tw\\:prose :where(.tw\\:prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose :where(.tw\\:prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.tw\\:prose-sm{font-size:.875rem;line-height:1.71429}.tw\\:prose-sm :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.tw\\:prose-sm :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.888889em;margin-bottom:.888889em;font-size:1.28571em;line-height:1.55556}.tw\\:prose-sm :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.33333em;margin-bottom:1.33333em;padding-inline-start:1.11111em}.tw\\:prose-sm :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:.8em;font-size:2.14286em;line-height:1.2}.tw\\:prose-sm :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.6em;margin-bottom:.8em;font-size:1.42857em;line-height:1.4}.tw\\:prose-sm :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.55556em;margin-bottom:.444444em;font-size:1.28571em;line-height:1.55556}.tw\\:prose-sm :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.42857em;margin-bottom:.571429em;line-height:1.42857}.tw\\:prose-sm :where(img):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose-sm :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.142857em;padding-inline-end:.357143em;padding-bottom:.142857em;border-radius:.3125rem;padding-inline-start:.357143em;font-size:.857143em}.tw\\:prose-sm :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em}.tw\\:prose-sm :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.9em}.tw\\:prose-sm :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.888889em}.tw\\:prose-sm :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;border-radius:.25rem;margin-top:1.66667em;margin-bottom:1.66667em;padding-inline-start:1em;font-size:.857143em;line-height:1.66667}.tw\\:prose-sm :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em;padding-inline-start:1.57143em}.tw\\:prose-sm :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;margin-bottom:.285714em}.tw\\:prose-sm :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.428571em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(.tw\\:prose-sm>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.14286em}.tw\\:prose-sm :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.571429em;margin-bottom:.571429em}.tw\\:prose-sm :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em;margin-bottom:1.14286em}.tw\\:prose-sm :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.14286em}.tw\\:prose-sm :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.285714em;padding-inline-start:1.57143em}.tw\\:prose-sm :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2.85714em;margin-bottom:2.85714em}.tw\\:prose-sm :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.tw\\:prose-sm :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose-sm :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.857143em;line-height:1.5}.tw\\:prose-sm :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.tw\\:prose-sm :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose-sm :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose-sm :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.666667em;padding-inline-end:1em;padding-bottom:.666667em;padding-inline-start:1em}.tw\\:prose-sm :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.tw\\:prose-sm :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.tw\\:prose-sm :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.71429em;margin-bottom:1.71429em}.tw\\:prose-sm :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.tw\\:prose-sm :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.666667em;font-size:.857143em;line-height:1.33333}.tw\\:prose-sm :where(.tw\\:prose-sm>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.tw\\:prose-sm :where(.tw\\:prose-sm>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.tw\\:-mt-4{margin-top:calc(calc(var(--spacing)) * -4)}.tw\\:mt-1{margin-top:calc(calc(var(--spacing)) * 1)}.tw\\:mt-2{margin-top:calc(calc(var(--spacing)) * 2)}.tw\\:mt-3{margin-top:calc(calc(var(--spacing)) * 3)}.tw\\:mt-4{margin-top:calc(calc(var(--spacing)) * 4)}.tw\\:mt-6{margin-top:calc(calc(var(--spacing)) * 6)}.tw\\:mt-auto{margin-top:auto}.tw\\:mr-1{margin-right:calc(calc(var(--spacing)) * 1)}.tw\\:mr-2{margin-right:calc(calc(var(--spacing)) * 2)}.tw\\:mr-3{margin-right:calc(calc(var(--spacing)) * 3)}.tw\\:-mb-4{margin-bottom:calc(calc(var(--spacing)) * -4)}.tw\\:mb-1{margin-bottom:calc(calc(var(--spacing)) * 1)}.tw\\:mb-2{margin-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:mb-3{margin-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:mb-4{margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:ml-2{margin-left:calc(calc(var(--spacing)) * 2)}.tw\\:ml-4{margin-left:calc(calc(var(--spacing)) * 4)}.tw\\:ml-auto{margin-left:auto}.tw\\:box-border{box-sizing:border-box}.tw\\:line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.tw\\:no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.tw\\:no-scrollbar::-webkit-scrollbar{display:none}.tw\\:block{display:block}.tw\\:flex{display:flex}.tw\\:grid{display:grid}.tw\\:hidden{display:none}.tw\\:inline-block{display:inline-block}.tw\\:inline-flex{display:inline-flex}.tw\\:inline-grid{display:inline-grid}.tw\\:field-sizing-content{field-sizing:content}.tw\\:aspect-square{aspect-ratio:1}.tw\\:size-2{width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:size-2\\.5{width:calc(calc(var(--spacing)) * 2.5);height:calc(calc(var(--spacing)) * 2.5)}.tw\\:size-3{width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:size-3\\.5{width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:size-4{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:size-6{width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:size-7{width:calc(calc(var(--spacing)) * 7);height:calc(calc(var(--spacing)) * 7)}.tw\\:size-8{width:calc(calc(var(--spacing)) * 8);height:calc(calc(var(--spacing)) * 8)}.tw\\:size-9{width:calc(calc(var(--spacing)) * 9);height:calc(calc(var(--spacing)) * 9)}.tw\\:size-full{width:100%;height:100%}.tw\\:h-1{height:calc(calc(var(--spacing)) * 1)}.tw\\:h-2{height:calc(calc(var(--spacing)) * 2)}.tw\\:h-3{height:calc(calc(var(--spacing)) * 3)}.tw\\:h-3\\.5{height:calc(calc(var(--spacing)) * 3.5)}.tw\\:h-4{height:calc(calc(var(--spacing)) * 4)}.tw\\:h-5{height:calc(calc(var(--spacing)) * 5)}.tw\\:h-6{height:calc(calc(var(--spacing)) * 6)}.tw\\:h-7{height:calc(calc(var(--spacing)) * 7)}.tw\\:h-8{height:calc(calc(var(--spacing)) * 8)}.tw\\:h-8\\!{height:calc(calc(var(--spacing)) * 8)!important}.tw\\:h-9{height:calc(calc(var(--spacing)) * 9)}.tw\\:h-10{height:calc(calc(var(--spacing)) * 10)}.tw\\:h-12{height:calc(calc(var(--spacing)) * 12)}.tw\\:h-14{height:calc(calc(var(--spacing)) * 14)}.tw\\:h-20{height:calc(calc(var(--spacing)) * 20)}.tw\\:h-24{height:calc(calc(var(--spacing)) * 24)}.tw\\:h-32{height:calc(calc(var(--spacing)) * 32)}.tw\\:h-40{height:calc(calc(var(--spacing)) * 40)}.tw\\:h-64{height:calc(calc(var(--spacing)) * 64)}.tw\\:h-96{height:calc(calc(var(--spacing)) * 96)}.tw\\:h-\\[1\\.2rem\\]{height:1.2rem}.tw\\:h-\\[5px\\]{height:5px}.tw\\:h-\\[300px\\]{height:300px}.tw\\:h-\\[calc\\(100\\%-1px\\)\\]{height:calc(100% - 1px)}.tw\\:h-\\[calc\\(100\\%-2px\\)\\]{height:calc(100% - 2px)}.tw\\:h-auto{height:auto}.tw\\:h-full{height:100%}.tw\\:h-px{height:1px}.tw\\:h-svh{height:100svh}.tw\\:max-h-\\(--radix-context-menu-content-available-height\\){max-height:var(--radix-context-menu-content-available-height)}.tw\\:max-h-\\(--radix-dropdown-menu-content-available-height\\){max-height:var(--radix-dropdown-menu-content-available-height)}.tw\\:max-h-\\(--radix-select-content-available-height\\){max-height:var(--radix-select-content-available-height)}.tw\\:max-h-5{max-height:calc(calc(var(--spacing)) * 5)}.tw\\:max-h-10{max-height:calc(calc(var(--spacing)) * 10)}.tw\\:max-h-72{max-height:calc(calc(var(--spacing)) * 72)}.tw\\:max-h-80{max-height:calc(calc(var(--spacing)) * 80)}.tw\\:max-h-\\[96\\%\\]{max-height:96%}.tw\\:max-h-\\[300px\\]{max-height:300px}.tw\\:min-h-0{min-height:calc(calc(var(--spacing)) * 0)}.tw\\:min-h-11{min-height:calc(calc(var(--spacing)) * 11)}.tw\\:min-h-16{min-height:calc(calc(var(--spacing)) * 16)}.tw\\:min-h-svh{min-height:100svh}.tw\\:w-\\(--radix-dropdown-menu-trigger-width\\){width:var(--radix-dropdown-menu-trigger-width)}.tw\\:w-\\(--sidebar-width\\){width:var(--sidebar-width)}.tw\\:w-1{width:calc(calc(var(--spacing)) * 1)}.tw\\:w-1\\/2{width:50%}.tw\\:w-2{width:calc(calc(var(--spacing)) * 2)}.tw\\:w-3{width:calc(calc(var(--spacing)) * 3)}.tw\\:w-3\\.5{width:calc(calc(var(--spacing)) * 3.5)}.tw\\:w-3\\/4{width:75%}.tw\\:w-4{width:calc(calc(var(--spacing)) * 4)}.tw\\:w-4\\/5{width:80%}.tw\\:w-4\\/6{width:66.6667%}.tw\\:w-5{width:calc(calc(var(--spacing)) * 5)}.tw\\:w-5\\/6{width:83.3333%}.tw\\:w-6{width:calc(calc(var(--spacing)) * 6)}.tw\\:w-8{width:calc(calc(var(--spacing)) * 8)}.tw\\:w-9{width:calc(calc(var(--spacing)) * 9)}.tw\\:w-9\\/12{width:75%}.tw\\:w-10{width:calc(calc(var(--spacing)) * 10)}.tw\\:w-12{width:calc(calc(var(--spacing)) * 12)}.tw\\:w-20{width:calc(calc(var(--spacing)) * 20)}.tw\\:w-24{width:calc(calc(var(--spacing)) * 24)}.tw\\:w-32{width:calc(calc(var(--spacing)) * 32)}.tw\\:w-48{width:calc(calc(var(--spacing)) * 48)}.tw\\:w-56{width:calc(calc(var(--spacing)) * 56)}.tw\\:w-60{width:calc(calc(var(--spacing)) * 60)}.tw\\:w-64{width:calc(calc(var(--spacing)) * 64)}.tw\\:w-72{width:calc(calc(var(--spacing)) * 72)}.tw\\:w-80{width:calc(calc(var(--spacing)) * 80)}.tw\\:w-96{width:calc(calc(var(--spacing)) * 96)}.tw\\:w-\\[1\\.2rem\\]{width:1.2rem}.tw\\:w-\\[1px\\]{width:1px}.tw\\:w-\\[5px\\]{width:5px}.tw\\:w-\\[70px\\]{width:70px}.tw\\:w-\\[100px\\]{width:100px}.tw\\:w-\\[116px\\]{width:116px}.tw\\:w-\\[124px\\]{width:124px}.tw\\:w-\\[150px\\]{width:150px}.tw\\:w-\\[180px\\]{width:180px}.tw\\:w-\\[200px\\]{width:200px}.tw\\:w-\\[250px\\]{width:250px}.tw\\:w-\\[300px\\]{width:300px}.tw\\:w-\\[320px\\]{width:320px}.tw\\:w-\\[350px\\]{width:350px}.tw\\:w-\\[400px\\]{width:400px}.tw\\:w-\\[500px\\]{width:500px}.tw\\:w-\\[600px\\]{width:600px}.tw\\:w-\\[calc\\(100\\%-2px\\)\\]{width:calc(100% - 2px)}.tw\\:w-\\[var\\(--radix-dropdown-menu-trigger-width\\)\\]{width:var(--radix-dropdown-menu-trigger-width)}.tw\\:w-\\[var\\(--radix-popper-anchor-width\\,280px\\)\\]{width:var(--radix-popper-anchor-width,280px)}.tw\\:w-auto{width:auto}.tw\\:w-fit{width:fit-content}.tw\\:w-full{width:100%}.tw\\:w-max{width:max-content}.tw\\:w-px{width:1px}.tw\\:max-w-\\(--skeleton-width\\){max-width:var(--skeleton-width)}.tw\\:max-w-2xl{max-width:var(--tw-container-2xl)}.tw\\:max-w-3xl{max-width:var(--tw-container-3xl)}.tw\\:max-w-4xl{max-width:var(--tw-container-4xl)}.tw\\:max-w-5{max-width:calc(calc(var(--spacing)) * 5)}.tw\\:max-w-6xl{max-width:var(--tw-container-6xl)}.tw\\:max-w-40{max-width:calc(calc(var(--spacing)) * 40)}.tw\\:max-w-48{max-width:calc(calc(var(--spacing)) * 48)}.tw\\:max-w-64{max-width:calc(calc(var(--spacing)) * 64)}.tw\\:max-w-96{max-width:calc(calc(var(--spacing)) * 96)}.tw\\:max-w-\\[200px\\]{max-width:200px}.tw\\:max-w-\\[220px\\]{max-width:220px}.tw\\:max-w-\\[280px\\]{max-width:280px}.tw\\:max-w-\\[calc\\(100\\%-2rem\\)\\]{max-width:calc(100% - 2rem)}.tw\\:max-w-\\[calc\\(100vw-2rem\\)\\]{max-width:calc(100vw - 2rem)}.tw\\:max-w-fit{max-width:fit-content}.tw\\:max-w-full{max-width:100%}.tw\\:max-w-lg{max-width:var(--tw-container-lg)}.tw\\:max-w-md{max-width:var(--tw-container-md)}.tw\\:max-w-none{max-width:none}.tw\\:max-w-sm{max-width:var(--tw-container-sm)}.tw\\:max-w-xs{max-width:var(--tw-container-xs)}.tw\\:min-w-0{min-width:calc(calc(var(--spacing)) * 0)}.tw\\:min-w-5{min-width:calc(calc(var(--spacing)) * 5)}.tw\\:min-w-7{min-width:calc(calc(var(--spacing)) * 7)}.tw\\:min-w-8{min-width:calc(calc(var(--spacing)) * 8)}.tw\\:min-w-9{min-width:calc(calc(var(--spacing)) * 9)}.tw\\:min-w-16{min-width:calc(calc(var(--spacing)) * 16)}.tw\\:min-w-32{min-width:calc(calc(var(--spacing)) * 32)}.tw\\:min-w-36{min-width:calc(calc(var(--spacing)) * 36)}.tw\\:min-w-80{min-width:calc(calc(var(--spacing)) * 80)}.tw\\:min-w-\\[12rem\\]{min-width:12rem}.tw\\:min-w-\\[26px\\]{min-width:26px}.tw\\:min-w-\\[96px\\]{min-width:96px}.tw\\:min-w-\\[140px\\]{min-width:140px}.tw\\:min-w-\\[200px\\]{min-width:200px}.tw\\:min-w-\\[215px\\]{min-width:215px}.tw\\:min-w-\\[500px\\]{min-width:500px}.tw\\:min-w-min{min-width:min-content}.tw\\:flex-1{flex:1}.tw\\:shrink{flex-shrink:1}.tw\\:shrink-0{flex-shrink:0}.tw\\:flex-grow,.tw\\:grow,.tw\\:grow-\\[1\\]{flex-grow:1}.tw\\:grow-\\[10\\]{flex-grow:10}.tw\\:basis-0{flex-basis:calc(calc(var(--spacing)) * 0)}.tw\\:caption-bottom{caption-side:bottom}.tw\\:border-collapse{border-collapse:collapse}.tw\\:origin-\\(--radix-context-menu-content-transform-origin\\){transform-origin:var(--radix-context-menu-content-transform-origin)}.tw\\:origin-\\(--radix-dropdown-menu-content-transform-origin\\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.tw\\:origin-\\(--radix-menubar-content-transform-origin\\){transform-origin:var(--radix-menubar-content-transform-origin)}.tw\\:origin-\\(--radix-popover-content-transform-origin\\){transform-origin:var(--radix-popover-content-transform-origin)}.tw\\:origin-\\(--radix-select-content-transform-origin\\){transform-origin:var(--radix-select-content-transform-origin)}.tw\\:origin-\\(--radix-tooltip-content-transform-origin\\){transform-origin:var(--radix-tooltip-content-transform-origin)}.tw\\:-translate-x-1\\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:-translate-x-px{--tw-translate-x:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-x-px{--tw-translate-x:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:-translate-y-1\\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-y-0{--tw-translate-y:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:translate-y-\\[calc\\(-50\\%_-_2px\\)\\]{--tw-translate-y:calc(-50% - 2px);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rotate-45{rotate:45deg}.tw\\:rotate-180{rotate:180deg}.tw\\:transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.tw\\:animate-none\\!{animation:none!important}.tw\\:animate-pulse{animation:var(--tw-animate-pulse)}.tw\\:animate-spin{animation:var(--tw-animate-spin)}.tw\\:cursor-default{cursor:default}.tw\\:cursor-ew-resize{cursor:ew-resize}.tw\\:cursor-not-allowed{cursor:not-allowed}.tw\\:cursor-pointer{cursor:pointer}.tw\\:cursor-text{cursor:text}.tw\\:touch-none{touch-action:none}.tw\\:resize{resize:both}.tw\\:resize-none{resize:none}.tw\\:scroll-m-20{scroll-margin:calc(calc(var(--spacing)) * 20)}.tw\\:scroll-my-1{scroll-margin-block:calc(calc(var(--spacing)) * 1)}.tw\\:scroll-py-1{scroll-padding-block:calc(calc(var(--spacing)) * 1)}.tw\\:list-inside{list-style-position:inside}.tw\\:list-outside{list-style-position:outside}.tw\\:\\!list-\\[lower-alpha\\]{list-style-type:lower-alpha!important}.tw\\:\\!list-\\[lower-roman\\]{list-style-type:lower-roman!important}.tw\\:\\!list-\\[upper-alpha\\]{list-style-type:upper-alpha!important}.tw\\:\\!list-\\[upper-roman\\]{list-style-type:upper-roman!important}.tw\\:\\!list-decimal{list-style-type:decimal!important}.tw\\:\\!list-disc{list-style-type:disc!important}.tw\\:list-decimal{list-style-type:decimal}.tw\\:list-disc{list-style-type:disc}.tw\\:list-none{list-style-type:none}.tw\\:grid-flow-col{grid-auto-flow:column}.tw\\:auto-rows-min{grid-auto-rows:min-content}.tw\\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.tw\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.tw\\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.tw\\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.tw\\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.tw\\:grid-cols-\\[25\\%_25\\%_50\\%\\]{grid-template-columns:25% 25% 50%}.tw\\:grid-cols-\\[25\\%_50\\%_25\\%\\]{grid-template-columns:25% 50% 25%}.tw\\:grid-cols-\\[min-content_1fr\\]{grid-template-columns:min-content 1fr}.tw\\:grid-cols-\\[min-content_min-content_1fr\\]{grid-template-columns:min-content min-content 1fr}.tw\\:grid-cols-subgrid{grid-template-columns:subgrid}.tw\\:flex-col{flex-direction:column}.tw\\:flex-col-reverse{flex-direction:column-reverse}.tw\\:flex-row{flex-direction:row}.tw\\:flex-row-reverse{flex-direction:row-reverse}.tw\\:flex-wrap{flex-wrap:wrap}.tw\\:place-content-center{place-content:center}.tw\\:content-center{align-content:center}.tw\\:items-baseline{align-items:baseline}.tw\\:items-center{align-items:center}.tw\\:items-end{align-items:flex-end}.tw\\:items-start{align-items:flex-start}.tw\\:items-stretch{align-items:stretch}.tw\\:justify-between{justify-content:space-between}.tw\\:justify-center{justify-content:center}.tw\\:justify-end{justify-content:flex-end}.tw\\:justify-start{justify-content:flex-start}.tw\\:gap-0{gap:calc(calc(var(--spacing)) * 0)}.tw\\:gap-0\\.5{gap:calc(calc(var(--spacing)) * .5)}.tw\\:gap-1{gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-1\\.5{gap:calc(calc(var(--spacing)) * 1.5)}.tw\\:gap-2{gap:calc(calc(var(--spacing)) * 2)}.tw\\:gap-2\\.5{gap:calc(calc(var(--spacing)) * 2.5)}.tw\\:gap-3{gap:calc(calc(var(--spacing)) * 3)}.tw\\:gap-4{gap:calc(calc(var(--spacing)) * 4)}.tw\\:gap-5{gap:calc(calc(var(--spacing)) * 5)}.tw\\:gap-6{gap:calc(calc(var(--spacing)) * 6)}.tw\\:gap-16{gap:calc(calc(var(--spacing)) * 16)}.tw\\:gap-\\[--spacing\\(var\\(--gap\\)\\)\\]{gap:calc(calc(var(--spacing)) * var(--gap))}.tw\\:gap-\\[12px\\]{gap:12px}:where(.tw\\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-1\\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.tw\\:space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(calc(var(--spacing)) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(calc(var(--spacing)) * 8) * calc(1 - var(--tw-space-y-reverse)))}.tw\\:gap-x-1{column-gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-x-2{column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:gap-x-3{column-gap:calc(calc(var(--spacing)) * 3)}.tw\\:gap-x-4{column-gap:calc(calc(var(--spacing)) * 4)}:where(.tw\\:-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.tw\\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 6) * calc(1 - var(--tw-space-x-reverse)))}.tw\\:gap-y-1{row-gap:calc(calc(var(--spacing)) * 1)}.tw\\:gap-y-2{row-gap:calc(calc(var(--spacing)) * 2)}:where(.tw\\:divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.tw\\:divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}.tw\\:self-start{align-self:flex-start}.tw\\:self-stretch{align-self:stretch}.tw\\:justify-self-end{justify-self:flex-end}.tw\\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tw\\:overflow-auto{overflow:auto}.tw\\:overflow-clip{overflow:clip}.tw\\:overflow-hidden{overflow:hidden}.tw\\:overflow-scroll{overflow:scroll}.tw\\:overflow-visible{overflow:visible}.tw\\:overflow-x-auto{overflow-x:auto}.tw\\:overflow-x-hidden{overflow-x:hidden}.tw\\:overflow-y-auto{overflow-y:auto}.tw\\:overflow-y-hidden{overflow-y:hidden}.tw\\:rounded{border-radius:.25rem}.tw\\:rounded-2xl{border-radius:calc(var(--radius) * 1.8)}.tw\\:rounded-4xl{border-radius:calc(var(--radius) * 2.6)}.tw\\:rounded-\\[2px\\]{border-radius:2px}.tw\\:rounded-\\[4px\\]{border-radius:4px}.tw\\:rounded-\\[6px\\]{border-radius:6px}.tw\\:rounded-\\[calc\\(var\\(--radius\\)-3px\\)\\]{border-radius:calc(var(--radius) - 3px)}.tw\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,10px\\)\\]{border-radius:min(var(--tw-radius-md), 10px)}.tw\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,12px\\)\\]{border-radius:min(var(--tw-radius-md), 12px)}.tw\\:rounded-full{border-radius:3.40282e38px}.tw\\:rounded-lg{border-radius:var(--radius)}.tw\\:rounded-lg\\!{border-radius:var(--radius)!important}.tw\\:rounded-md{border-radius:calc(var(--radius) * .8)}.tw\\:rounded-none{border-radius:0}.tw\\:rounded-sm{border-radius:calc(var(--radius) * .6)}.tw\\:rounded-xl{border-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-xl\\!{border-radius:calc(var(--radius) * 1.4)!important}.tw\\:rounded-s-none{border-start-start-radius:0;border-end-start-radius:0}.tw\\:rounded-e-none{border-start-end-radius:0;border-end-end-radius:0}.tw\\:rounded-t-xl{border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-l-lg{border-top-left-radius:var(--radius);border-bottom-left-radius:var(--radius)}.tw\\:rounded-r-xl{border-top-right-radius:calc(var(--radius) * 1.4);border-bottom-right-radius:calc(var(--radius) * 1.4)}.tw\\:rounded-b-xl{border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:border{border-style:var(--tw-border-style);border-width:1px}.tw\\:border-0{border-style:var(--tw-border-style);border-width:0}.tw\\:border-2{border-style:var(--tw-border-style);border-width:2px}.tw\\:border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:border-s-0{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.tw\\:border-e{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.tw\\:border-e-0{border-inline-end-style:var(--tw-border-style);border-inline-end-width:0}.tw\\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.tw\\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.tw\\:border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.tw\\:border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.tw\\:border-dashed{--tw-border-style:dashed;border-style:dashed}.tw\\:border-none{--tw-border-style:none;border-style:none}.tw\\:border-solid{--tw-border-style:solid;border-style:solid}.tw\\:border-black{border-color:var(--tw-color-black)}.tw\\:border-blue-400{border-color:var(--tw-color-blue-400)}.tw\\:border-blue-500{border-color:var(--tw-color-blue-500)}.tw\\:border-border,.tw\\:border-border\\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.tw\\:border-border\\/50{border-color:color-mix(in oklab, var(--border) 50%, transparent)}}.tw\\:border-gray-300{border-color:var(--tw-color-gray-300)}.tw\\:border-input,.tw\\:border-input\\/30{border-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:border-input\\/30{border-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:border-muted-foreground{border-color:var(--muted-foreground)}.tw\\:border-primary{border-color:var(--primary)}.tw\\:border-red-300{border-color:var(--tw-color-red-300)}.tw\\:border-red-400{border-color:var(--tw-color-red-400)}.tw\\:border-red-500{border-color:var(--tw-color-red-500)}.tw\\:border-red-600{border-color:var(--tw-color-red-600)}.tw\\:border-ring{border-color:var(--ring)}.tw\\:border-sidebar-border{border-color:var(--sidebar-border)}.tw\\:border-slate-300{border-color:var(--tw-color-slate-300)}.tw\\:border-transparent{border-color:#0000}.tw\\:border-yellow-400{border-color:var(--tw-color-yellow-400)}.tw\\:border-yellow-500{border-color:var(--tw-color-yellow-500)}.tw\\:border-s-amber-200{border-inline-start-color:var(--tw-color-amber-200)}.tw\\:border-s-indigo-200{border-inline-start-color:var(--tw-color-indigo-200)}.tw\\:border-s-purple-200{border-inline-start-color:var(--tw-color-purple-200)}.tw\\:border-s-red-200{border-inline-start-color:var(--tw-color-red-200)}.tw\\:\\!bg-destructive\\/50{background-color:var(--destructive)!important}@supports (color:color-mix(in lab, red, red)){.tw\\:\\!bg-destructive\\/50{background-color:color-mix(in oklab, var(--destructive) 50%, transparent)!important}}.tw\\:bg-accent{background-color:var(--accent)}.tw\\:bg-accent-foreground{background-color:var(--accent-foreground)}.tw\\:bg-background,.tw\\:bg-background\\/50{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-background\\/50{background-color:color-mix(in oklab, var(--background) 50%, transparent)}}.tw\\:bg-black\\/10{background-color:var(--tw-color-black)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-black\\/10{background-color:color-mix(in oklab, var(--tw-color-black) 10%, transparent)}}.tw\\:bg-blue-50{background-color:var(--tw-color-blue-50)}.tw\\:bg-blue-100{background-color:var(--tw-color-blue-100)}.tw\\:bg-blue-400{background-color:var(--tw-color-blue-400)}.tw\\:bg-blue-500{background-color:var(--tw-color-blue-500)}.tw\\:bg-border{background-color:var(--border)}.tw\\:bg-card{background-color:var(--card)}.tw\\:bg-card-foreground{background-color:var(--card-foreground)}.tw\\:bg-destructive-foreground{background-color:var(--destructive-foreground)}.tw\\:bg-destructive\\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-destructive\\/10{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.tw\\:bg-foreground{background-color:var(--foreground)}.tw\\:bg-gray-50{background-color:var(--tw-color-gray-50)}.tw\\:bg-gray-100{background-color:var(--tw-color-gray-100)}.tw\\:bg-gray-500{background-color:var(--tw-color-gray-500)}.tw\\:bg-green-50{background-color:var(--tw-color-green-50)}.tw\\:bg-green-100{background-color:var(--tw-color-green-100)}.tw\\:bg-green-500{background-color:var(--tw-color-green-500)}.tw\\:bg-input,.tw\\:bg-input\\/30{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-input\\/30{background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:bg-muted{background-color:var(--muted)}.tw\\:bg-muted-foreground{background-color:var(--muted-foreground)}.tw\\:bg-muted\\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-muted\\/50{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:bg-neutral-300{background-color:var(--tw-color-neutral-300)}.tw\\:bg-orange-100{background-color:var(--tw-color-orange-100)}.tw\\:bg-popover{background-color:var(--popover)}.tw\\:bg-popover-foreground{background-color:var(--popover-foreground)}.tw\\:bg-popover\\/70{background-color:var(--popover)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-popover\\/70{background-color:color-mix(in oklab, var(--popover) 70%, transparent)}}.tw\\:bg-primary{background-color:var(--primary)}.tw\\:bg-primary-foreground{background-color:var(--primary-foreground)}.tw\\:bg-purple-50{background-color:var(--tw-color-purple-50)}.tw\\:bg-red-100{background-color:var(--tw-color-red-100)}.tw\\:bg-red-500{background-color:var(--tw-color-red-500)}.tw\\:bg-rose-500,.tw\\:bg-rose-500\\/5{background-color:var(--tw-color-rose-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-rose-500\\/5{background-color:color-mix(in oklab, var(--tw-color-rose-500) 5%, transparent)}}.tw\\:bg-rose-500\\/15{background-color:var(--tw-color-rose-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-rose-500\\/15{background-color:color-mix(in oklab, var(--tw-color-rose-500) 15%, transparent)}}.tw\\:bg-secondary{background-color:var(--secondary)}.tw\\:bg-secondary-foreground{background-color:var(--secondary-foreground)}.tw\\:bg-sidebar{background-color:var(--sidebar)}.tw\\:bg-sidebar-accent{background-color:var(--sidebar-accent)}.tw\\:bg-sidebar-border{background-color:var(--sidebar-border)}.tw\\:bg-sky-500,.tw\\:bg-sky-500\\/5{background-color:var(--tw-color-sky-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-sky-500\\/5{background-color:color-mix(in oklab, var(--tw-color-sky-500) 5%, transparent)}}.tw\\:bg-sky-500\\/15{background-color:var(--tw-color-sky-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-sky-500\\/15{background-color:color-mix(in oklab, var(--tw-color-sky-500) 15%, transparent)}}.tw\\:bg-teal-500,.tw\\:bg-teal-500\\/5{background-color:var(--tw-color-teal-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-teal-500\\/5{background-color:color-mix(in oklab, var(--tw-color-teal-500) 5%, transparent)}}.tw\\:bg-teal-500\\/15{background-color:var(--tw-color-teal-500)}@supports (color:color-mix(in lab, red, red)){.tw\\:bg-teal-500\\/15{background-color:color-mix(in oklab, var(--tw-color-teal-500) 15%, transparent)}}.tw\\:bg-transparent{background-color:#0000}.tw\\:bg-white{background-color:var(--tw-color-white)}.tw\\:bg-yellow-50{background-color:var(--tw-color-yellow-50)}.tw\\:bg-yellow-100{background-color:var(--tw-color-yellow-100)}.tw\\:bg-yellow-500{background-color:var(--tw-color-yellow-500)}.tw\\:bg-zinc-400{background-color:var(--tw-color-zinc-400)}.tw\\:bg-clip-padding{background-clip:padding-box}.tw\\:fill-destructive{fill:var(--destructive)}.tw\\:fill-foreground{fill:var(--foreground)}.tw\\:fill-yellow-400,.tw\\:fill-yellow-400\\/50{fill:var(--tw-color-yellow-400)}@supports (color:color-mix(in lab, red, red)){.tw\\:fill-yellow-400\\/50{fill:color-mix(in oklab, var(--tw-color-yellow-400) 50%, transparent)}}.tw\\:object-cover{object-fit:cover}.tw\\:\\!p-4{padding:calc(calc(var(--spacing)) * 4)!important}.tw\\:p-0{padding:calc(calc(var(--spacing)) * 0)}.tw\\:p-0\\.5{padding:calc(calc(var(--spacing)) * .5)}.tw\\:p-1{padding:calc(calc(var(--spacing)) * 1)}.tw\\:p-2{padding:calc(calc(var(--spacing)) * 2)}.tw\\:p-2\\.5{padding:calc(calc(var(--spacing)) * 2.5)}.tw\\:p-3{padding:calc(calc(var(--spacing)) * 3)}.tw\\:p-4{padding:calc(calc(var(--spacing)) * 4)}.tw\\:p-6{padding:calc(calc(var(--spacing)) * 6)}.tw\\:p-8{padding:calc(calc(var(--spacing)) * 8)}.tw\\:p-\\[1px\\]{padding:1px}.tw\\:p-\\[3px\\]{padding:3px}.tw\\:p-\\[10px\\]{padding:10px}.tw\\:p-\\[16px\\]{padding:16px}.tw\\:px-0{padding-inline:calc(calc(var(--spacing)) * 0)}.tw\\:px-1{padding-inline:calc(calc(var(--spacing)) * 1)}.tw\\:px-1\\.5{padding-inline:calc(calc(var(--spacing)) * 1.5)}.tw\\:px-2{padding-inline:calc(calc(var(--spacing)) * 2)}.tw\\:px-2\\.5{padding-inline:calc(calc(var(--spacing)) * 2.5)}.tw\\:px-3{padding-inline:calc(calc(var(--spacing)) * 3)}.tw\\:px-4{padding-inline:calc(calc(var(--spacing)) * 4)}.tw\\:px-6{padding-inline:calc(calc(var(--spacing)) * 6)}.tw\\:py-0{padding-block:calc(calc(var(--spacing)) * 0)}.tw\\:py-0\\.5{padding-block:calc(calc(var(--spacing)) * .5)}.tw\\:py-1{padding-block:calc(calc(var(--spacing)) * 1)}.tw\\:py-1\\.5{padding-block:calc(calc(var(--spacing)) * 1.5)}.tw\\:py-2{padding-block:calc(calc(var(--spacing)) * 2)}.tw\\:py-3{padding-block:calc(calc(var(--spacing)) * 3)}.tw\\:py-4{padding-block:calc(calc(var(--spacing)) * 4)}.tw\\:py-6{padding-block:calc(calc(var(--spacing)) * 6)}.tw\\:py-8{padding-block:calc(calc(var(--spacing)) * 8)}.tw\\:py-\\[2px\\]{padding-block:2px}.tw\\:ps-1\\.5{padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:ps-2{padding-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:ps-2\\.5{padding-inline-start:calc(calc(var(--spacing)) * 2.5)}.tw\\:ps-4{padding-inline-start:calc(calc(var(--spacing)) * 4)}.tw\\:ps-7{padding-inline-start:calc(calc(var(--spacing)) * 7)}.tw\\:ps-8{padding-inline-start:calc(calc(var(--spacing)) * 8)}.tw\\:ps-9{padding-inline-start:calc(calc(var(--spacing)) * 9)}.tw\\:ps-12{padding-inline-start:calc(calc(var(--spacing)) * 12)}.tw\\:ps-\\[85px\\]{padding-inline-start:85px}.tw\\:pe-1{padding-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:pe-1\\.5{padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:pe-2{padding-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:pe-4{padding-inline-end:calc(calc(var(--spacing)) * 4)}.tw\\:pe-8{padding-inline-end:calc(calc(var(--spacing)) * 8)}.tw\\:pe-9{padding-inline-end:calc(calc(var(--spacing)) * 9)}.tw\\:pe-\\[calc\\(138px\\+1rem\\)\\]{padding-inline-end:calc(138px + 1rem)}.tw\\:pt-1{padding-top:calc(calc(var(--spacing)) * 1)}.tw\\:pt-2{padding-top:calc(calc(var(--spacing)) * 2)}.tw\\:pt-3{padding-top:calc(calc(var(--spacing)) * 3)}.tw\\:pt-6{padding-top:calc(calc(var(--spacing)) * 6)}.tw\\:\\!pr-10{padding-right:calc(calc(var(--spacing)) * 10)!important}.tw\\:pr-0{padding-right:calc(calc(var(--spacing)) * 0)}.tw\\:pr-3{padding-right:calc(calc(var(--spacing)) * 3)}.tw\\:pr-4{padding-right:calc(calc(var(--spacing)) * 4)}.tw\\:pb-0{padding-bottom:calc(calc(var(--spacing)) * 0)}.tw\\:pb-1{padding-bottom:calc(calc(var(--spacing)) * 1)}.tw\\:pb-2{padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:pb-3{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:pb-4{padding-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:pb-8{padding-bottom:calc(calc(var(--spacing)) * 8)}.tw\\:pb-16{padding-bottom:calc(calc(var(--spacing)) * 16)}.tw\\:pb-24{padding-bottom:calc(calc(var(--spacing)) * 24)}.tw\\:pl-2{padding-left:calc(calc(var(--spacing)) * 2)}.tw\\:pl-3{padding-left:calc(calc(var(--spacing)) * 3)}.tw\\:pl-4{padding-left:calc(calc(var(--spacing)) * 4)}.tw\\:pl-5{padding-left:calc(calc(var(--spacing)) * 5)}.tw\\:pl-6{padding-left:calc(calc(var(--spacing)) * 6)}.tw\\:pl-8{padding-left:calc(calc(var(--spacing)) * 8)}.tw\\:text-center{text-align:center}.tw\\:text-end{text-align:end}.tw\\:text-left{text-align:left}.tw\\:text-right{text-align:right}.tw\\:text-start{text-align:start}.tw\\:align-middle{vertical-align:middle}.tw\\:font-heading{font-family:var(--font-sans)}.tw\\:font-mono{font-family:var(--tw-font-mono)}.tw\\:font-sans{font-family:IBM Plex Sans Variable,sans-serif}.tw\\:text-2xl{font-size:var(--tw-text-2xl);line-height:var(--tw-leading,var(--tw-text-2xl--line-height))}.tw\\:text-3xl{font-size:var(--tw-text-3xl);line-height:var(--tw-leading,var(--tw-text-3xl--line-height))}.tw\\:text-4xl{font-size:var(--tw-text-4xl);line-height:var(--tw-leading,var(--tw-text-4xl--line-height))}.tw\\:text-base{font-size:var(--tw-text-base);line-height:var(--tw-leading,var(--tw-text-base--line-height))}.tw\\:text-lg{font-size:var(--tw-text-lg);line-height:var(--tw-leading,var(--tw-text-lg--line-height))}.tw\\:text-sm{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:text-xl{font-size:var(--tw-text-xl);line-height:var(--tw-leading,var(--tw-text-xl--line-height))}.tw\\:text-xs{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:text-\\[0\\.8rem\\]{font-size:.8rem}.tw\\:leading-loose{--tw-leading:var(--tw-leading-loose);line-height:var(--tw-leading-loose)}.tw\\:leading-none{--tw-leading:1;line-height:1}.tw\\:leading-relaxed{--tw-leading:var(--tw-leading-relaxed);line-height:var(--tw-leading-relaxed)}.tw\\:leading-snug{--tw-leading:var(--tw-leading-snug);line-height:var(--tw-leading-snug)}.tw\\:leading-tight{--tw-leading:var(--tw-leading-tight);line-height:var(--tw-leading-tight)}.tw\\:font-bold{--tw-font-weight:var(--tw-font-weight-bold);font-weight:var(--tw-font-weight-bold)}.tw\\:font-extrabold{--tw-font-weight:var(--tw-font-weight-extrabold);font-weight:var(--tw-font-weight-extrabold)}.tw\\:font-medium{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:font-normal{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:font-semibold{--tw-font-weight:var(--tw-font-weight-semibold);font-weight:var(--tw-font-weight-semibold)}.tw\\:tracking-tight{--tw-tracking:var(--tw-tracking-tight);letter-spacing:var(--tw-tracking-tight)}.tw\\:tracking-widest{--tw-tracking:var(--tw-tracking-widest);letter-spacing:var(--tw-tracking-widest)}.tw\\:text-balance{text-wrap:balance}.tw\\:text-nowrap{text-wrap:nowrap}.tw\\:break-words{overflow-wrap:break-word}.tw\\:text-clip{text-overflow:clip}.tw\\:text-ellipsis{text-overflow:ellipsis}.tw\\:whitespace-normal{white-space:normal}.tw\\:whitespace-nowrap{white-space:nowrap}.tw\\:\\[color\\:blue\\]{color:#00f}.tw\\:text-accent{color:var(--accent)}.tw\\:text-accent-foreground{color:var(--accent-foreground)}.tw\\:text-background{color:var(--background)}.tw\\:text-blue-400{color:var(--tw-color-blue-400)}.tw\\:text-blue-500{color:var(--tw-color-blue-500)}.tw\\:text-blue-600{color:var(--tw-color-blue-600)}.tw\\:text-blue-800{color:var(--tw-color-blue-800)}.tw\\:text-card{color:var(--card)}.tw\\:text-card-foreground{color:var(--card-foreground)}.tw\\:text-current{color:currentColor}.tw\\:text-destructive{color:var(--destructive)}.tw\\:text-destructive-foreground{color:var(--destructive-foreground)}.tw\\:text-foreground,.tw\\:text-foreground\\/30{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/30{color:color-mix(in oklab, var(--foreground) 30%, transparent)}}.tw\\:text-foreground\\/50{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/50{color:color-mix(in oklab, var(--foreground) 50%, transparent)}}.tw\\:text-foreground\\/60{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/60{color:color-mix(in oklab, var(--foreground) 60%, transparent)}}.tw\\:text-foreground\\/70{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-foreground\\/70{color:color-mix(in oklab, var(--foreground) 70%, transparent)}}.tw\\:text-gray-300{color:var(--tw-color-gray-300)}.tw\\:text-gray-500{color:var(--tw-color-gray-500)}.tw\\:text-gray-600{color:var(--tw-color-gray-600)}.tw\\:text-gray-700{color:var(--tw-color-gray-700)}.tw\\:text-gray-800{color:var(--tw-color-gray-800)}.tw\\:text-green-600{color:var(--tw-color-green-600)}.tw\\:text-green-700{color:var(--tw-color-green-700)}.tw\\:text-green-800{color:var(--tw-color-green-800)}.tw\\:text-inherit{color:inherit}.tw\\:text-muted{color:var(--muted)}.tw\\:text-muted-foreground,.tw\\:text-muted-foreground\\/50{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-muted-foreground\\/50{color:color-mix(in oklab, var(--muted-foreground) 50%, transparent)}}.tw\\:text-orange-800{color:var(--tw-color-orange-800)}.tw\\:text-popover{color:var(--popover)}.tw\\:text-popover-foreground{color:var(--popover-foreground)}.tw\\:text-primary{color:var(--primary)}.tw\\:text-primary-foreground{color:var(--primary-foreground)}.tw\\:text-purple-900{color:var(--tw-color-purple-900)}.tw\\:text-red-500{color:var(--tw-color-red-500)}.tw\\:text-red-600{color:var(--tw-color-red-600)}.tw\\:text-red-700{color:var(--tw-color-red-700)}.tw\\:text-red-800{color:var(--tw-color-red-800)}.tw\\:text-rose-600{color:var(--tw-color-rose-600)}.tw\\:text-secondary{color:var(--secondary)}.tw\\:text-secondary-foreground{color:var(--secondary-foreground)}.tw\\:text-sidebar-accent-foreground{color:var(--sidebar-accent-foreground)}.tw\\:text-sidebar-foreground,.tw\\:text-sidebar-foreground\\/70{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:text-sidebar-foreground\\/70{color:color-mix(in oklab, var(--sidebar-foreground) 70%, transparent)}}.tw\\:text-sky-600{color:var(--tw-color-sky-600)}.tw\\:text-slate-900{color:var(--tw-color-slate-900)}.tw\\:text-teal-600{color:var(--tw-color-teal-600)}.tw\\:text-white{color:var(--tw-color-white)}.tw\\:text-yellow-400{color:var(--tw-color-yellow-400)}.tw\\:text-yellow-600{color:var(--tw-color-yellow-600)}.tw\\:text-yellow-700{color:var(--tw-color-yellow-700)}.tw\\:capitalize{text-transform:capitalize}.tw\\:uppercase{text-transform:uppercase}.tw\\:italic{font-style:italic}.tw\\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.tw\\:line-through{text-decoration-line:line-through}.tw\\:underline{text-decoration-line:underline}.tw\\:decoration-destructive{-webkit-text-decoration-color:var(--destructive);-webkit-text-decoration-color:var(--destructive);text-decoration-color:var(--destructive)}.tw\\:underline-offset-4{text-underline-offset:4px}.tw\\:opacity-0{opacity:0}.tw\\:opacity-40{opacity:.4}.tw\\:opacity-50{opacity:.5}.tw\\:opacity-60{opacity:.6}.tw\\:opacity-100{opacity:1}.tw\\:bg-blend-color{background-blend-mode:color}.tw\\:shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-\\[0_0_0_1px_var\\(--sidebar-border\\)\\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,var(--sidebar-border));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:shadow-none\\!{--tw-shadow:0 0 #0000!important;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)!important}.tw\\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:ring-background{--tw-ring-color:var(--background)}.tw\\:ring-foreground\\/10{--tw-ring-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.tw\\:ring-foreground\\/10{--tw-ring-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}.tw\\:ring-primary{--tw-ring-color:var(--primary)}.tw\\:ring-ring\\/50{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:ring-ring\\/50{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:ring-sidebar-ring{--tw-ring-color:var(--sidebar-ring)}.tw\\:ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:ring-offset-background{--tw-ring-offset-color:var(--background)}.tw\\:outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:outline-hidden{outline-offset:2px;outline:2px solid #0000}}.tw\\:drop-shadow-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--tw-drop-shadow-sm));filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.tw\\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[color\\,box-shadow\\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[left\\,right\\,width\\]{transition-property:left,right,width;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[margin\\,opacity\\]{transition-property:margin,opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[width\\,height\\,padding\\]{transition-property:width,height,padding;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-\\[width\\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:transition-none{transition-property:none}.tw\\:duration-100{--tw-duration:.1s;transition-duration:.1s}.tw\\:duration-200{--tw-duration:.2s;transition-duration:.2s}.tw\\:ease-linear{--tw-ease:linear;transition-timing-function:linear}.tw\\:prose-quoteless :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before,.tw\\:prose-quoteless :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.tw\\:outline-none{--tw-outline-style:none;outline-style:none}.tw\\:select-none{-webkit-user-select:none;user-select:none}.tw\\:group-focus-within\\/menu-item\\:opacity-100:is(:where(.tw\\:group\\/menu-item):focus-within *){opacity:1}@media (hover:hover){.tw\\:group-hover\\:visible:is(:where(.tw\\:group):hover *){visibility:visible}.tw\\:group-hover\\:hidden:is(:where(.tw\\:group):hover *){display:none}.tw\\:group-hover\\:opacity-100:is(:where(.tw\\:group):hover *),.tw\\:group-hover\\/menu-item\\:opacity-100:is(:where(.tw\\:group\\/menu-item):hover *){opacity:1}}.tw\\:group-focus\\/context-menu-item\\:text-accent-foreground:is(:where(.tw\\:group\\/context-menu-item):focus *),.tw\\:group-focus\\/dropdown-menu-item\\:text-accent-foreground:is(:where(.tw\\:group\\/dropdown-menu-item):focus *),.tw\\:group-focus\\/menubar-item\\:text-accent-foreground:is(:where(.tw\\:group\\/menubar-item):focus *){color:var(--accent-foreground)}.tw\\:group-has-disabled\\/field\\:opacity-50:is(:where(.tw\\:group\\/field):has(:disabled) *){opacity:.5}.tw\\:group-has-data-\\[sidebar\\=menu-action\\]\\/menu-item\\:pe-8:is(:where(.tw\\:group\\/menu-item):has([data-sidebar=menu-action]) *){padding-inline-end:calc(calc(var(--spacing)) * 8)}.tw\\:group-has-data-\\[size\\=lg\\]\\/avatar-group\\:size-10:is(:where(.tw\\:group\\/avatar-group):has([data-size=lg]) *){width:calc(calc(var(--spacing)) * 10);height:calc(calc(var(--spacing)) * 10)}.tw\\:group-has-data-\\[size\\=sm\\]\\/avatar-group\\:size-6:is(:where(.tw\\:group\\/avatar-group):has([data-size=sm]) *){width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:group-has-data-\\[slot\\=command-shortcut\\]\\/command-item\\:hidden:is(:where(.tw\\:group\\/command-item):has([data-slot=command-shortcut]) *){display:none}.tw\\:group-has-\\[\\>input\\]\\/input-group\\:pt-2:is(:where(.tw\\:group\\/input-group):has(>input) *){padding-top:calc(calc(var(--spacing)) * 2)}.tw\\:group-has-\\[\\>input\\]\\/input-group\\:pb-2:is(:where(.tw\\:group\\/input-group):has(>input) *){padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:group-has-\\[\\>svg\\]\\/alert\\:col-start-2:is(:where(.tw\\:group\\/alert):has(>svg) *){grid-column-start:2}.tw\\:group-data-\\[checked\\=true\\]\\/command-item\\:opacity-100:is(:where(.tw\\:group\\/command-item)[data-checked=true] *){opacity:1}.tw\\:group-data-\\[collapsible\\=icon\\]\\:-mt-8:is(:where(.tw\\:group)[data-collapsible=icon] *){margin-top:calc(calc(var(--spacing)) * -8)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:hidden:is(:where(.tw\\:group)[data-collapsible=icon] *){display:none}.tw\\:group-data-\\[collapsible\\=icon\\]\\:size-8\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(calc(var(--spacing)) * 8)!important;height:calc(calc(var(--spacing)) * 8)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\(--sidebar-width-icon\\):is(:where(.tw\\:group)[data-collapsible=icon] *){width:var(--sidebar-width-icon)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\[calc\\(var\\(--sidebar-width-icon\\)\\+\\(--spacing\\(4\\)\\)\\)\\]:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(calc(var(--spacing)) * 4)))}.tw\\:group-data-\\[collapsible\\=icon\\]\\:w-\\[calc\\(var\\(--sidebar-width-icon\\)\\+\\(--spacing\\(4\\)\\)\\+2px\\)\\]:is(:where(.tw\\:group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(calc(var(--spacing)) * 4)) + 2px)}.tw\\:group-data-\\[collapsible\\=icon\\]\\:overflow-hidden:is(:where(.tw\\:group)[data-collapsible=icon] *){overflow:hidden}.tw\\:group-data-\\[collapsible\\=icon\\]\\:p-0\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){padding:calc(calc(var(--spacing)) * 0)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:p-2\\!:is(:where(.tw\\:group)[data-collapsible=icon] *){padding:calc(calc(var(--spacing)) * 2)!important}.tw\\:group-data-\\[collapsible\\=icon\\]\\:opacity-0:is(:where(.tw\\:group)[data-collapsible=icon] *){opacity:0}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:right-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\]:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){right:calc(var(--sidebar-width) * -1)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:left-\\[calc\\(var\\(--sidebar-width\\)\\*-1\\)\\]:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){left:calc(var(--sidebar-width) * -1)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:w-0:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){width:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:translate-x-0:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:group-data-\\[disabled\\=true\\]\\:pointer-events-none:is(:where(.tw\\:group)[data-disabled=true] *){pointer-events:none}.tw\\:group-data-\\[disabled\\=true\\]\\:opacity-50:is(:where(.tw\\:group)[data-disabled=true] *),.tw\\:group-data-\\[disabled\\=true\\]\\/input-group\\:opacity-50:is(:where(.tw\\:group\\/input-group)[data-disabled=true] *){opacity:.5}.tw\\:group-data-\\[side\\=primary\\]\\:-right-4:is(:where(.tw\\:group)[data-side=primary] *){right:calc(calc(var(--spacing)) * -4)}.tw\\:group-data-\\[side\\=primary\\]\\:border-e:is(:where(.tw\\:group)[data-side=primary] *){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.tw\\:group-data-\\[side\\=secondary\\]\\:left-0:is(:where(.tw\\:group)[data-side=secondary] *){left:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-\\[side\\=secondary\\]\\:rotate-180:is(:where(.tw\\:group)[data-side=secondary] *){rotate:180deg}.tw\\:group-data-\\[side\\=secondary\\]\\:border-s:is(:where(.tw\\:group)[data-side=secondary] *){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:group-data-\\[size\\=default\\]\\/avatar\\:size-2\\.5:is(:where(.tw\\:group\\/avatar)[data-size=default] *){width:calc(calc(var(--spacing)) * 2.5);height:calc(calc(var(--spacing)) * 2.5)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:size-4:is(:where(.tw\\:group\\/switch)[data-size=default] *){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[size\\=lg\\]\\/avatar\\:size-3:is(:where(.tw\\:group\\/avatar)[data-size=lg] *){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=sm] *){width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:text-xs:is(:where(.tw\\:group\\/avatar)[data-size=sm] *){font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:p-3:is(:where(.tw\\:group\\/card)[data-size=sm] *){padding:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:px-3:is(:where(.tw\\:group\\/card)[data-size=sm] *){padding-inline:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:text-sm:is(:where(.tw\\:group\\/card)[data-size=sm] *){font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:size-3:is(:where(.tw\\:group\\/switch)[data-size=sm] *){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:rounded-none:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *){border-radius:0}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:px-2:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *){padding-inline:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[variant\\=floating\\]\\:rounded-lg:is(:where(.tw\\:group)[data-variant=floating] *){border-radius:var(--radius)}.tw\\:group-data-\\[variant\\=floating\\]\\:shadow-sm:is(:where(.tw\\:group)[data-variant=floating] *){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=floating\\]\\:ring-1:is(:where(.tw\\:group)[data-variant=floating] *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=floating\\]\\:ring-sidebar-border:is(:where(.tw\\:group)[data-variant=floating] *){--tw-ring-color:var(--sidebar-border)}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *){background-color:#0000}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:mx-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){margin-inline:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:mt-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){margin-top:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:h-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){height:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:w-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){width:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=bottom\\]\\/drawer-content\\:text-center:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=bottom] *){text-align:center}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:my-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){margin-block:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:me-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){margin-inline-end:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:h-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){height:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=left\\]\\/drawer-content\\:w-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=left] *){width:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:my-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){margin-block:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:ms-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){margin-inline-start:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:h-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){height:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=right\\]\\/drawer-content\\:w-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=right] *){width:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:mx-auto:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){margin-inline:auto}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:mb-4:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:block:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){display:block}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:h-1\\.5:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){height:calc(calc(var(--spacing)) * 1.5)}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:w-\\[100px\\]:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){width:100px}.tw\\:group-data-\\[vaul-drawer-direction\\=top\\]\\/drawer-content\\:text-center:is(:where(.tw\\:group\\/drawer-content)[data-vaul-drawer-direction=top] *){text-align:center}.tw\\:group-data-selected\\/command-item\\:text-foreground:is(:where(.tw\\:group\\/command-item):where([data-selected=true]) *){color:var(--foreground)}.tw\\:group-data-horizontal\\/tabs\\:h-8:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *){height:calc(calc(var(--spacing)) * 8)}.tw\\:group-data-vertical\\/tabs\\:h-fit:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){height:fit-content}.tw\\:group-data-vertical\\/tabs\\:w-full:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){width:100%}.tw\\:group-data-vertical\\/tabs\\:flex-col:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){flex-direction:column}.tw\\:group-data-vertical\\/tabs\\:justify-start:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *){justify-content:flex-start}@media (hover:hover){.tw\\:peer-hover\\/menu-button\\:text-sidebar-accent-foreground:is(:where(.tw\\:peer\\/menu-button):hover~*){color:var(--sidebar-accent-foreground)}.tw\\:peer-focus\\:group-hover\\:text-blue-500:is(:where(.tw\\:peer):focus~*):is(:where(.tw\\:group):hover *){color:var(--tw-color-blue-500)}}.tw\\:peer-disabled\\:cursor-not-allowed:is(:where(.tw\\:peer):disabled~*){cursor:not-allowed}.tw\\:peer-disabled\\:opacity-50:is(:where(.tw\\:peer):disabled~*){opacity:.5}.tw\\:peer-data-\\[size\\=default\\]\\/menu-button\\:top-1\\.5:is(:where(.tw\\:peer\\/menu-button)[data-size=default]~*){top:calc(calc(var(--spacing)) * 1.5)}.tw\\:peer-data-\\[size\\=lg\\]\\/menu-button\\:top-2\\.5:is(:where(.tw\\:peer\\/menu-button)[data-size=lg]~*){top:calc(calc(var(--spacing)) * 2.5)}.tw\\:peer-data-\\[size\\=sm\\]\\/menu-button\\:top-1:is(:where(.tw\\:peer\\/menu-button)[data-size=sm]~*){top:calc(calc(var(--spacing)) * 1)}.tw\\:peer-data-active\\/menu-button\\:text-sidebar-accent-foreground:is(:is(:where(.tw\\:peer\\/menu-button):where([data-state=active]),:where(.tw\\:peer\\/menu-button):where([data-active]:not([data-active=false])))~*){color:var(--sidebar-accent-foreground)}.tw\\:file\\:inline-flex::file-selector-button{display:inline-flex}.tw\\:file\\:h-6::file-selector-button{height:calc(calc(var(--spacing)) * 6)}.tw\\:file\\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.tw\\:file\\:bg-transparent::file-selector-button{background-color:#0000}.tw\\:file\\:text-sm::file-selector-button{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:file\\:font-medium::file-selector-button{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:file\\:text-foreground::file-selector-button{color:var(--foreground)}.tw\\:placeholder\\:text-muted-foreground::placeholder{color:var(--muted-foreground)}.tw\\:before\\:pointer-events-none:before{content:var(--tw-content);pointer-events:none}.tw\\:before\\:absolute:before{content:var(--tw-content);position:absolute}.tw\\:before\\:inset-0:before{content:var(--tw-content);inset:calc(calc(var(--spacing)) * 0)}.tw\\:before\\:top-0\\.5:before{content:var(--tw-content);top:calc(calc(var(--spacing)) * .5)}.tw\\:before\\:left-0:before{content:var(--tw-content);left:calc(calc(var(--spacing)) * 0)}.tw\\:before\\:-z-1:before{content:var(--tw-content);z-index:calc(1 * -1)}.tw\\:before\\:block:before{content:var(--tw-content);display:block}.tw\\:before\\:hidden:before{content:var(--tw-content);display:none}.tw\\:before\\:h-4:before{content:var(--tw-content);height:calc(calc(var(--spacing)) * 4)}.tw\\:before\\:w-4:before{content:var(--tw-content);width:calc(calc(var(--spacing)) * 4)}.tw\\:before\\:cursor-pointer:before{content:var(--tw-content);cursor:pointer}.tw\\:before\\:rounded:before{content:var(--tw-content);border-radius:.25rem}.tw\\:before\\:rounded-\\[inherit\\]:before{content:var(--tw-content);border-radius:inherit}.tw\\:before\\:border:before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.tw\\:before\\:border-primary:before{content:var(--tw-content);border-color:var(--primary)}.tw\\:before\\:bg-primary:before{content:var(--tw-content);background-color:var(--primary)}.tw\\:before\\:bg-cover:before{content:var(--tw-content);background-size:cover}.tw\\:before\\:bg-no-repeat:before{content:var(--tw-content);background-repeat:no-repeat}.tw\\:before\\:backdrop-blur-2xl:before{content:var(--tw-content);--tw-backdrop-blur:blur(var(--tw-blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.tw\\:before\\:backdrop-saturate-150:before{content:var(--tw-content);--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.tw\\:before\\:content-\\[\\"\\"\\]:before{--tw-content:"";content:var(--tw-content)}.tw\\:after\\:absolute:after{content:var(--tw-content);position:absolute}.tw\\:after\\:-inset-2:after{content:var(--tw-content);inset:calc(calc(var(--spacing)) * -2)}.tw\\:after\\:inset-0:after{content:var(--tw-content);inset:calc(calc(var(--spacing)) * 0)}.tw\\:after\\:-inset-x-3:after{content:var(--tw-content);inset-inline:calc(calc(var(--spacing)) * -3)}.tw\\:after\\:-inset-y-2:after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * -2)}.tw\\:after\\:inset-y-0:after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:after\\:start-1\\/2:after{content:var(--tw-content);inset-inline-start:50%}.tw\\:after\\:top-\\[6px\\]:after{content:var(--tw-content);top:6px}.tw\\:after\\:right-\\[7px\\]:after{content:var(--tw-content);right:7px}.tw\\:after\\:left-\\[7px\\]:after{content:var(--tw-content);left:7px}.tw\\:after\\:block:after{content:var(--tw-content);display:block}.tw\\:after\\:hidden:after{content:var(--tw-content);display:none}.tw\\:after\\:h-0\\.5:after{content:var(--tw-content);height:calc(calc(var(--spacing)) * .5)}.tw\\:after\\:h-\\[6px\\]:after{content:var(--tw-content);height:6px}.tw\\:after\\:w-1:after{content:var(--tw-content);width:calc(calc(var(--spacing)) * 1)}.tw\\:after\\:w-\\[2px\\]:after{content:var(--tw-content);width:2px}.tw\\:after\\:w-\\[3px\\]:after{content:var(--tw-content);width:3px}.tw\\:after\\:-translate-x-1\\/2:after{content:var(--tw-content);--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:after\\:rotate-45:after{content:var(--tw-content);rotate:45deg}.tw\\:after\\:cursor-pointer:after{content:var(--tw-content);cursor:pointer}.tw\\:after\\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.tw\\:after\\:border:after{content:var(--tw-content);border-style:var(--tw-border-style);border-width:1px}.tw\\:after\\:border-t-0:after{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:after\\:border-r-2:after{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:2px}.tw\\:after\\:border-b-2:after{content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.tw\\:after\\:border-l-0:after{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:0}.tw\\:after\\:border-solid:after{content:var(--tw-content);--tw-border-style:solid;border-style:solid}.tw\\:after\\:border-border:after{content:var(--tw-content);border-color:var(--border)}.tw\\:after\\:border-white:after{content:var(--tw-content);border-color:var(--tw-color-white)}.tw\\:after\\:bg-foreground:after{content:var(--tw-content);background-color:var(--foreground)}.tw\\:after\\:bg-muted:after{content:var(--tw-content);background-color:var(--muted)}.tw\\:after\\:opacity-0:after{content:var(--tw-content);opacity:0}.tw\\:after\\:mix-blend-darken:after{content:var(--tw-content);mix-blend-mode:darken}.tw\\:after\\:transition-opacity:after{content:var(--tw-content);transition-property:opacity;transition-timing-function:var(--tw-ease,var(--tw-default-transition-timing-function));transition-duration:var(--tw-duration,var(--tw-default-transition-duration))}.tw\\:after\\:content-\\[\\"\\"\\]:after{--tw-content:"";content:var(--tw-content)}.tw\\:group-data-\\[collapsible\\=offcanvas\\]\\:after\\:start-full:is(:where(.tw\\:group)[data-collapsible=offcanvas] *):after{content:var(--tw-content);inset-inline-start:100%}.tw\\:group-data-horizontal\\/tabs\\:after\\:inset-x-0:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-horizontal\\/tabs\\:after\\:bottom-\\[-5px\\]:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);bottom:-5px}.tw\\:group-data-horizontal\\/tabs\\:after\\:h-0\\.5:is(:where(.tw\\:group\\/tabs):where([data-orientation=horizontal]) *):after{content:var(--tw-content);height:calc(calc(var(--spacing)) * .5)}.tw\\:group-data-vertical\\/tabs\\:after\\:inset-y-0:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:group-data-vertical\\/tabs\\:after\\:-end-1:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);inset-inline-end:calc(calc(var(--spacing)) * -1)}.tw\\:group-data-vertical\\/tabs\\:after\\:w-0\\.5:is(:where(.tw\\:group\\/tabs):where([data-orientation=vertical]) *):after{content:var(--tw-content);width:calc(calc(var(--spacing)) * .5)}.tw\\:first\\:mt-0:first-child{margin-top:calc(calc(var(--spacing)) * 0)}.tw\\:even\\:bg-muted:nth-child(2n){background-color:var(--muted)}@media (hover:hover){.tw\\:hover\\:-mt-4:hover{margin-top:calc(calc(var(--spacing)) * -4)}.tw\\:hover\\:cursor-pointer:hover{cursor:pointer}.tw\\:hover\\:bg-accent:hover,.tw\\:hover\\:bg-accent\\/80:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-accent\\/80:hover{background-color:color-mix(in oklab, var(--accent) 80%, transparent)}}.tw\\:hover\\:bg-blue-600:hover{background-color:var(--tw-color-blue-600)}.tw\\:hover\\:bg-destructive\\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-destructive\\/20:hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:hover\\:bg-gray-50:hover{background-color:var(--tw-color-gray-50)}.tw\\:hover\\:bg-input:hover{background-color:var(--input)}.tw\\:hover\\:bg-muted:hover,.tw\\:hover\\:bg-muted\\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-muted\\/50:hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:hover\\:bg-muted\\/80:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-muted\\/80:hover{background-color:color-mix(in oklab, var(--muted) 80%, transparent)}}.tw\\:hover\\:bg-primary\\/10:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-primary\\/10:hover{background-color:color-mix(in oklab, var(--primary) 10%, transparent)}}.tw\\:hover\\:bg-primary\\/70:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-primary\\/70:hover{background-color:color-mix(in oklab, var(--primary) 70%, transparent)}}.tw\\:hover\\:bg-red-500:hover{background-color:var(--tw-color-red-500)}.tw\\:hover\\:bg-secondary:hover,.tw\\:hover\\:bg-secondary\\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.tw\\:hover\\:bg-secondary\\/80:hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.tw\\:hover\\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.tw\\:hover\\:bg-transparent:hover{background-color:#0000}.tw\\:hover\\:text-foreground:hover{color:var(--foreground)}.tw\\:hover\\:text-muted-foreground:hover{color:var(--muted-foreground)}.tw\\:hover\\:text-primary-foreground:hover{color:var(--primary-foreground)}.tw\\:hover\\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}.tw\\:hover\\:underline:hover{text-decoration-line:underline}.tw\\:hover\\:opacity-80:hover{opacity:.8}.tw\\:hover\\:opacity-100:hover{opacity:1}.tw\\:hover\\:shadow-\\[0_0_0_1px_var\\(--sidebar-accent\\)\\]:hover{--tw-shadow:0 0 0 1px var(--tw-shadow-color,var(--sidebar-accent));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:ring-3:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:hover\\:group-data-\\[collapsible\\=offcanvas\\]\\:bg-sidebar:hover:is(:where(.tw\\:group)[data-collapsible=offcanvas] *){background-color:var(--sidebar)}.tw\\:hover\\:after\\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:var(--sidebar-border)}}.tw\\:focus\\:relative:focus{position:relative}.tw\\:focus\\:z-10:focus{z-index:10}.tw\\:focus\\:bg-accent:focus{background-color:var(--accent)}.tw\\:focus\\:bg-muted:focus{background-color:var(--muted)}.tw\\:focus\\:text-accent-foreground:focus{color:var(--accent-foreground)}.tw\\:focus\\:text-foreground:focus{color:var(--foreground)}.tw\\:focus\\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus\\:ring-ring:focus{--tw-ring-color:var(--ring)}.tw\\:focus\\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:focus\\:ring-offset-background:focus{--tw-ring-offset-color:var(--background)}.tw\\:focus\\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:focus\\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}:is(.tw\\:focus\\:\\*\\*\\:text-accent-foreground:focus *),:is(.tw\\:not-data-\\[variant\\=destructive\\]\\:focus\\:\\*\\*\\:text-accent-foreground:not([data-variant=destructive]):focus *){color:var(--accent-foreground)}.tw\\:focus-visible\\:relative:focus-visible{position:relative}.tw\\:focus-visible\\:z-10:focus-visible{z-index:10}.tw\\:focus-visible\\:border-destructive\\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:border-destructive\\/40:focus-visible{border-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:focus-visible\\:border-ring:focus-visible{border-color:var(--ring)}.tw\\:focus-visible\\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-3:focus-visible,.tw\\:focus-visible\\:ring-\\[3px\\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:focus-visible\\:ring-\\[color\\:hsl\\(240\\,5\\%\\,64\\.9\\%\\)\\]:focus-visible{--tw-ring-color:#a1a1aa}.tw\\:focus-visible\\:ring-destructive\\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:ring-destructive\\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:focus-visible\\:ring-ring:focus-visible,.tw\\:focus-visible\\:ring-ring\\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:focus-visible\\:ring-ring\\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:focus-visible\\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.tw\\:focus-visible\\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.tw\\:focus-visible\\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.tw\\:focus-visible\\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.tw\\:focus-visible\\:outline-ring:focus-visible{outline-color:var(--ring)}:is(.tw\\:\\*\\:focus-visible\\:relative>*):focus-visible{position:relative}:is(.tw\\:\\*\\:focus-visible\\:z-10>*):focus-visible{z-index:10}.tw\\:active\\:bg-sidebar-accent:active{background-color:var(--sidebar-accent)}.tw\\:active\\:text-sidebar-accent-foreground:active{color:var(--sidebar-accent-foreground)}.tw\\:active\\:ring-3:active{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:active\\:not-aria-\\[haspopup\\]\\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:disabled\\:pointer-events-none:disabled{pointer-events:none}.tw\\:disabled\\:cursor-not-allowed:disabled{cursor:not-allowed}.tw\\:disabled\\:bg-input\\/50:disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:disabled\\:bg-input\\/50:disabled{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:disabled\\:bg-transparent:disabled{background-color:#0000}.tw\\:disabled\\:opacity-50:disabled{opacity:.5}:where([data-side=primary]) .tw\\:in-data-\\[side\\=primary\\]\\:cursor-w-resize{cursor:w-resize}:where([data-side=secondary]) .tw\\:in-data-\\[side\\=secondary\\]\\:cursor-e-resize{cursor:e-resize}:where([data-slot=button-group]) .tw\\:in-data-\\[slot\\=button-group\\]\\:rounded-lg{border-radius:var(--radius)}:where([data-slot=combobox-content]) .tw\\:in-data-\\[slot\\=combobox-content\\]\\:focus-within\\:border-inherit:focus-within{border-color:inherit}:where([data-slot=combobox-content]) .tw\\:in-data-\\[slot\\=combobox-content\\]\\:focus-within\\:ring-0:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:where([data-slot=dialog-content]) .tw\\:in-data-\\[slot\\=dialog-content\\]\\:rounded-lg\\!{border-radius:var(--radius)!important}:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/20{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/20{background-color:color-mix(in oklab, var(--background) 20%, transparent)}}:where([data-slot=tooltip-content]) .tw\\:in-data-\\[slot\\=tooltip-content\\]\\:text-background{color:var(--background)}.tw\\:has-disabled\\:bg-input\\/50:has(:disabled){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-disabled\\:bg-input\\/50:has(:disabled){background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:has-disabled\\:opacity-50:has(:disabled){opacity:.5}.tw\\:has-aria-expanded\\:bg-muted\\/50:has([aria-expanded=true]){background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-aria-expanded\\:bg-muted\\/50:has([aria-expanded=true]){background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-1:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1)}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-1\\.5:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-end\\]\\:pe-2:has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:has-data-\\[icon\\=inline-end\\]\\:pe-1\\.5:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-end]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-1:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-1\\.5:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[icon\\=inline-start\\]\\:ps-2:has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[spacing\\=0\\]\\/toggle-group\\:has-data-\\[icon\\=inline-start\\]\\:ps-1\\.5:is(:where(.tw\\:group\\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-start]){padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[slot\\=alert-action\\]\\:relative:has([data-slot=alert-action]){position:relative}.tw\\:has-data-\\[slot\\=alert-action\\]\\:pe-18:has([data-slot=alert-action]){padding-inline-end:calc(calc(var(--spacing)) * 18)}.tw\\:has-data-\\[slot\\=card-action\\]\\:grid-cols-\\[1fr_auto\\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.tw\\:has-data-\\[slot\\=card-description\\]\\:grid-rows-\\[auto_auto\\]:has([data-slot=card-description]){grid-template-rows:auto auto}.tw\\:has-data-\\[slot\\=card-footer\\]\\:pb-0:has([data-slot=card-footer]){padding-bottom:calc(calc(var(--spacing)) * 0)}.tw\\:has-data-\\[slot\\=kbd\\]\\:pe-1\\.5:has([data-slot=kbd]){padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-data-\\[variant\\=inset\\]\\:bg-sidebar:has([data-variant=inset]){background-color:var(--sidebar)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:border-ring:has([data-slot=input-group-control]:focus-visible){border-color:var(--ring)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-3:has([data-slot=input-group-control]:focus-visible){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-ring\\/50:has([data-slot=input-group-control]:focus-visible){--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-\\[\\[data-slot\\=input-group-control\\]\\:focus-visible\\]\\:ring-ring\\/50:has([data-slot=input-group-control]:focus-visible){--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:border-destructive:has([data-slot][aria-invalid=true]){border-color:var(--destructive)}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-3:has([data-slot][aria-invalid=true]){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/20:has([data-slot][aria-invalid=true]){--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/20:has([data-slot][aria-invalid=true]){--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:h-auto:has(>[data-align=block-end]){height:auto}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:flex-col:has(>[data-align=block-end]){flex-direction:column}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:h-auto:has(>[data-align=block-start]){height:auto}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:flex-col:has(>[data-align=block-start]){flex-direction:column}.tw\\:has-\\[\\>\\[data-slot\\=button-group\\]\\]\\:gap-2:has(>[data-slot=button-group]){gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>button\\]\\:ms-\\[-0\\.3rem\\]:has(>button){margin-inline-start:-.3rem}.tw\\:has-\\[\\>button\\]\\:me-\\[-0\\.3rem\\]:has(>button){margin-inline-end:-.3rem}.tw\\:has-\\[\\>img\\]\\:grid-cols-\\[auto_1fr\\]:has(>img){grid-template-columns:auto 1fr}.tw\\:has-\\[\\>img\\]\\:gap-x-2:has(>img){column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>img\\:first-child\\]\\:pt-0:has(>img:first-child){padding-top:calc(calc(var(--spacing)) * 0)}.tw\\:has-\\[\\>kbd\\]\\:ms-\\[-0\\.15rem\\]:has(>kbd){margin-inline-start:-.15rem}.tw\\:has-\\[\\>kbd\\]\\:me-\\[-0\\.15rem\\]:has(>kbd){margin-inline-end:-.15rem}.tw\\:has-\\[\\>svg\\]\\:grid-cols-\\[auto_1fr\\]:has(>svg){grid-template-columns:auto 1fr}.tw\\:has-\\[\\>svg\\]\\:gap-x-2:has(>svg){column-gap:calc(calc(var(--spacing)) * 2)}.tw\\:has-\\[\\>svg\\]\\:p-0:has(>svg){padding:calc(calc(var(--spacing)) * 0)}.tw\\:has-\\[\\>textarea\\]\\:h-auto:has(>textarea){height:auto}.tw\\:aria-disabled\\:pointer-events-none[aria-disabled=true]{pointer-events:none}.tw\\:aria-disabled\\:opacity-50[aria-disabled=true]{opacity:.5}.tw\\:aria-expanded\\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.tw\\:aria-expanded\\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.tw\\:aria-expanded\\:text-foreground[aria-expanded=true]{color:var(--foreground)}.tw\\:aria-expanded\\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.tw\\:aria-expanded\\:opacity-100[aria-expanded=true]{opacity:1}.tw\\:aria-invalid\\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.tw\\:aria-invalid\\:ring-0[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:aria-invalid\\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:aria-invalid\\:ring-destructive\\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:aria-invalid\\:ring-destructive\\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:aria-invalid\\:aria-checked\\:border-primary[aria-invalid=true][aria-checked=true]{border-color:var(--primary)}.tw\\:aria-pressed\\:bg-muted[aria-pressed=true]{background-color:var(--muted)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:h-px[aria-orientation=horizontal]{height:1px}.tw\\:aria-\\[orientation\\=horizontal\\]\\:w-full[aria-orientation=horizontal]{width:100%}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:start-0[aria-orientation=horizontal]:after{content:var(--tw-content);inset-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:h-1[aria-orientation=horizontal]:after{content:var(--tw-content);height:calc(calc(var(--spacing)) * 1)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:w-full[aria-orientation=horizontal]:after{content:var(--tw-content);width:100%}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:translate-x-0[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:aria-\\[orientation\\=horizontal\\]\\:after\\:-translate-y-1\\/2[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:aria-\\[orientation\\=vertical\\]\\:flex-col[aria-orientation=vertical]{flex-direction:column}.tw\\:data-inset\\:ps-7[data-inset]{padding-inline-start:calc(calc(var(--spacing)) * 7)}.tw\\:data-placeholder\\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.tw\\:data-\\[align-trigger\\=false\\]\\:min-w-36[data-align-trigger=false]{min-width:calc(calc(var(--spacing)) * 36)}.tw\\:data-\\[align-trigger\\=true\\]\\:min-w-\\(--radix-select-trigger-width\\)[data-align-trigger=true]{min-width:var(--radix-select-trigger-width)}.tw\\:data-\\[align-trigger\\=true\\]\\:animate-none[data-align-trigger=true]{animation:none}.tw\\:data-\\[disabled\\=true\\]\\:pointer-events-none[data-disabled=true]{pointer-events:none}.tw\\:data-\\[disabled\\=true\\]\\:opacity-50[data-disabled=true]{opacity:.5}.tw\\:data-\\[position\\=popper\\]\\:h-\\(--radix-select-trigger-height\\)[data-position=popper]{height:var(--radix-select-trigger-height)}.tw\\:data-\\[position\\=popper\\]\\:w-full[data-position=popper]{width:100%}.tw\\:data-\\[position\\=popper\\]\\:min-w-\\(--radix-select-trigger-width\\)[data-position=popper]{min-width:var(--radix-select-trigger-width)}.tw\\:data-\\[side\\=bottom\\]\\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=bottom\\]\\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.tw\\:data-\\[side\\=left\\]\\:-translate-x-1[data-side=left]{--tw-translate-x:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=left\\]\\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.tw\\:data-\\[side\\=right\\]\\:translate-x-1[data-side=right]{--tw-translate-x:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=right\\]\\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.tw\\:data-\\[side\\=top\\]\\:-translate-y-1[data-side=top]{--tw-translate-y:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:data-\\[side\\=top\\]\\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.tw\\:data-\\[size\\=default\\]\\:h-8[data-size=default]{height:calc(calc(var(--spacing)) * 8)}.tw\\:data-\\[size\\=default\\]\\:h-\\[18\\.4px\\][data-size=default]{height:18.4px}.tw\\:data-\\[size\\=default\\]\\:w-\\[32px\\][data-size=default]{width:32px}.tw\\:data-\\[size\\=lg\\]\\:size-10[data-size=lg]{width:calc(calc(var(--spacing)) * 10);height:calc(calc(var(--spacing)) * 10)}.tw\\:data-\\[size\\=md\\]\\:text-sm[data-size=md]{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:data-\\[size\\=sm\\]\\:size-6[data-size=sm]{width:calc(calc(var(--spacing)) * 6);height:calc(calc(var(--spacing)) * 6)}.tw\\:data-\\[size\\=sm\\]\\:h-7[data-size=sm]{height:calc(calc(var(--spacing)) * 7)}.tw\\:data-\\[size\\=sm\\]\\:h-\\[14px\\][data-size=sm]{height:14px}.tw\\:data-\\[size\\=sm\\]\\:w-\\[24px\\][data-size=sm]{width:24px}.tw\\:data-\\[size\\=sm\\]\\:gap-3[data-size=sm]{gap:calc(calc(var(--spacing)) * 3)}.tw\\:data-\\[size\\=sm\\]\\:rounded-\\[min\\(var\\(--tw-radius-md\\)\\,10px\\)\\][data-size=sm]{border-radius:min(var(--tw-radius-md), 10px)}.tw\\:data-\\[size\\=sm\\]\\:py-3[data-size=sm]{padding-block:calc(calc(var(--spacing)) * 3)}.tw\\:data-\\[size\\=sm\\]\\:text-xs[data-size=sm]{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}.tw\\:data-\\[size\\=sm\\]\\:has-data-\\[slot\\=card-footer\\]\\:pb-0[data-size=sm]:has([data-slot=card-footer]){padding-bottom:calc(calc(var(--spacing)) * 0)}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-item]:focus{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-item]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:data-highlighted\\:bg-foreground\\/10 *)[data-slot$=-item][data-highlighted]{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-item\\]\\:data-highlighted\\:bg-foreground\\/10 *)[data-slot$=-item][data-highlighted]{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-separator\\]\\:bg-foreground\\/5 *)[data-slot$=-separator]{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-separator\\]\\:bg-foreground\\/5 *)[data-slot$=-separator]{background-color:color-mix(in oklab, var(--foreground) 5%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-trigger]:focus{background-color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:focus\\:bg-foreground\\/10 *)[data-slot$=-trigger]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:aria-expanded\\:bg-foreground\\/10\\! *)[data-slot$=-trigger][aria-expanded=true]{background-color:var(--foreground)!important}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[slot\\$\\=-trigger\\]\\:aria-expanded\\:bg-foreground\\/10\\! *)[data-slot$=-trigger][aria-expanded=true]{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)!important}}:is(.tw\\:\\*\\:data-\\[slot\\=alert-description\\]\\:text-destructive\\/90>*)[data-slot=alert-description]{color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\:data-\\[slot\\=alert-description\\]\\:text-destructive\\/90>*)[data-slot=alert-description]{color:color-mix(in oklab, var(--destructive) 90%, transparent)}}:is(.tw\\:\\*\\:data-\\[slot\\=avatar\\]\\:ring-2>*)[data-slot=avatar]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:is(.tw\\:\\*\\:data-\\[slot\\=avatar\\]\\:ring-background>*)[data-slot=avatar]{--tw-ring-color:var(--background)}:is(.tw\\:\\*\\:data-\\[slot\\=input-group-addon\\]\\:ps-2\\!>*)[data-slot=input-group-addon]{padding-inline-start:calc(calc(var(--spacing)) * 2)!important}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:relative *)[data-slot=kbd]{position:relative}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:isolate *)[data-slot=kbd]{isolation:isolate}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:z-50 *)[data-slot=kbd]{z-index:50}:is(.tw\\:\\*\\*\\:data-\\[slot\\=kbd\\]\\:rounded-sm *)[data-slot=kbd]{border-radius:calc(var(--radius) * .6)}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:flex>*)[data-slot=select-value]{display:flex}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:flex-1>*)[data-slot=select-value]{flex:1}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:items-center>*)[data-slot=select-value]{align-items:center}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:gap-1\\.5>*)[data-slot=select-value]{gap:calc(calc(var(--spacing)) * 1.5)}:is(.tw\\:\\*\\:data-\\[slot\\=select-value\\]\\:text-start>*)[data-slot=select-value]{text-align:start}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:first\\:rounded-s-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:first-child{border-start-start-radius:var(--radius);border-end-start-radius:var(--radius)}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:first\\:rounded-t-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:first-child{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:last\\:rounded-e-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:last-child{border-start-end-radius:var(--radius);border-end-end-radius:var(--radius)}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:last\\:rounded-b-lg:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:last-child{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.tw\\:data-\\[state\\=active\\]\\:bg-background[data-state=active]{background-color:var(--background)}.tw\\:data-\\[state\\=active\\]\\:text-foreground[data-state=active]{color:var(--foreground)}.tw\\:data-\\[state\\=active\\]\\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:data-\\[state\\=closed\\]\\:overflow-hidden[data-state=closed]{overflow:hidden}.tw\\:data-\\[state\\=delayed-open\\]\\:animate-in[data-state=delayed-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-\\[state\\=delayed-open\\]\\:fade-in-0[data-state=delayed-open]{--tw-enter-opacity:0}.tw\\:data-\\[state\\=delayed-open\\]\\:zoom-in-95[data-state=delayed-open]{--tw-enter-scale:.95}.tw\\:data-\\[state\\=on\\]\\:bg-muted[data-state=on]{background-color:var(--muted)}.tw\\:data-\\[state\\=open\\]\\:bg-accent[data-state=open]{background-color:var(--accent)}.tw\\:data-\\[state\\=open\\]\\:bg-muted[data-state=open]{background-color:var(--muted)}.tw\\:data-\\[state\\=open\\]\\:text-foreground[data-state=open]{color:var(--foreground)}.tw\\:data-\\[state\\=selected\\]\\:bg-muted[data-state=selected]{background-color:var(--muted)}.tw\\:data-\\[variant\\=destructive\\]\\:text-destructive[data-variant=destructive]{color:var(--destructive)}:is(:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:\\*\\*\\:text-accent-foreground\\! *)[data-variant=destructive] *),:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:text-accent-foreground\\! *)[data-variant=destructive]{color:var(--accent-foreground)!important}.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.tw\\:data-\\[variant\\=destructive\\]\\:focus\\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-foreground\\/10\\! *)[data-variant=destructive]:focus{background-color:var(--foreground)!important}@supports (color:color-mix(in lab, red, red)){:is(.tw\\:\\*\\*\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-foreground\\/10\\! *)[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--foreground) 10%, transparent)!important}}.tw\\:data-\\[variant\\=line\\]\\:rounded-none[data-variant=line]{border-radius:0}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:border-s-0:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:border-t-0:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]{border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:group-data-horizontal\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:first\\:border-s:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]:first-child{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.tw\\:group-data-vertical\\/toggle-group\\:data-\\[spacing\\=0\\]\\:data-\\[variant\\=outline\\]\\:first\\:border-t:is(:where(.tw\\:group\\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]:first-child{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:inset-x-0[data-vaul-drawer-direction=bottom]{inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:bottom-0[data-vaul-drawer-direction=bottom]{bottom:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:mt-24[data-vaul-drawer-direction=bottom]{margin-top:calc(calc(var(--spacing)) * 24)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:max-h-\\[80vh\\][data-vaul-drawer-direction=bottom]{max-height:80vh}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:rounded-t-xl[data-vaul-drawer-direction=bottom]{border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=bottom\\]\\:border-t[data-vaul-drawer-direction=bottom]{border-top-style:var(--tw-border-style);border-top-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:inset-y-0[data-vaul-drawer-direction=left]{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:left-0[data-vaul-drawer-direction=left]{left:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:w-3\\/4[data-vaul-drawer-direction=left]{width:75%}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:flex-row[data-vaul-drawer-direction=left]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:rounded-r-xl[data-vaul-drawer-direction=left]{border-top-right-radius:calc(var(--radius) * 1.4);border-bottom-right-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:border-r[data-vaul-drawer-direction=left]{border-right-style:var(--tw-border-style);border-right-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=left\\/right\\]\\:flex-row[data-vaul-drawer-direction=left\\/right]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:inset-y-0[data-vaul-drawer-direction=right]{inset-block:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:right-0[data-vaul-drawer-direction=right]{right:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:w-3\\/4[data-vaul-drawer-direction=right]{width:75%}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:flex-row[data-vaul-drawer-direction=right]{flex-direction:row}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:rounded-l-xl[data-vaul-drawer-direction=right]{border-top-left-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:border-l[data-vaul-drawer-direction=right]{border-left-style:var(--tw-border-style);border-left-width:1px}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:inset-x-0[data-vaul-drawer-direction=top]{inset-inline:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:top-0[data-vaul-drawer-direction=top]{top:calc(calc(var(--spacing)) * 0)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:mb-24[data-vaul-drawer-direction=top]{margin-bottom:calc(calc(var(--spacing)) * 24)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:max-h-\\[80vh\\][data-vaul-drawer-direction=top]{max-height:80vh}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:rounded-b-xl[data-vaul-drawer-direction=top]{border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}.tw\\:data-\\[vaul-drawer-direction\\=top\\]\\:border-b[data-vaul-drawer-direction=top]{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}@supports ((-webkit-backdrop-filter:var(--tw)) or (backdrop-filter:var(--tw))){.tw\\:supports-backdrop-filter\\:backdrop-blur-xs{--tw-backdrop-blur:blur(var(--tw-blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}}@media (min-width:40rem){.tw\\:sm\\:flex{display:flex}.tw\\:sm\\:max-w-sm{max-width:var(--tw-container-sm)}.tw\\:sm\\:flex-row{flex-direction:row}.tw\\:sm\\:justify-end{justify-content:flex-end}.tw\\:sm\\:p-8{padding:calc(calc(var(--spacing)) * 8)}.tw\\:sm\\:text-start{text-align:start}.tw\\:data-\\[vaul-drawer-direction\\=left\\]\\:sm\\:max-w-sm[data-vaul-drawer-direction=left],.tw\\:data-\\[vaul-drawer-direction\\=right\\]\\:sm\\:max-w-sm[data-vaul-drawer-direction=right]{max-width:var(--tw-container-sm)}}@media (min-width:48rem){.tw\\:md\\:block{display:block}.tw\\:md\\:flex{display:flex}.tw\\:md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.tw\\:md\\:gap-0\\.5{gap:calc(calc(var(--spacing)) * .5)}.tw\\:md\\:text-start{text-align:start}.tw\\:md\\:text-sm{font-size:var(--tw-text-sm);line-height:var(--tw-leading,var(--tw-text-sm--line-height))}.tw\\:md\\:text-pretty{text-wrap:pretty}.tw\\:md\\:opacity-0{opacity:0}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:m-2:is(:where(.tw\\:peer)[data-variant=inset]~*){margin:calc(calc(var(--spacing)) * 2)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:ms-0:is(:where(.tw\\:peer)[data-variant=inset]~*){margin-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:rounded-xl:is(:where(.tw\\:peer)[data-variant=inset]~*){border-radius:calc(var(--radius) * 1.4)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:shadow-sm:is(:where(.tw\\:peer)[data-variant=inset]~*){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:md\\:peer-data-\\[variant\\=inset\\]\\:peer-data-\\[state\\=collapsed\\]\\:ms-2:is(:where(.tw\\:peer)[data-variant=inset]~*):is(:where(.tw\\:peer)[data-state=collapsed]~*){margin-inline-start:calc(calc(var(--spacing)) * 2)}.tw\\:md\\:after\\:hidden:after{content:var(--tw-content);display:none}}@media (min-width:64rem){.tw\\:lg\\:flex{display:flex}.tw\\:lg\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}:where(.tw\\:lg\\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(calc(var(--spacing)) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(calc(var(--spacing)) * 8) * calc(1 - var(--tw-space-x-reverse)))}.tw\\:lg\\:text-5xl{font-size:var(--tw-text-5xl);line-height:var(--tw-leading,var(--tw-text-5xl--line-height))}}@media (min-width:48rem){@media (min-width:64rem){.tw\\:md\\:lg\\:hidden{display:none}}}@container (min-width:24rem){.tw\\:\\@sm\\:basis-auto{flex-basis:auto}}.tw\\:ltr\\:left-2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){left:calc(calc(var(--spacing)) * 2)}.tw\\:ltr\\:-translate-x-1\\/2:where(:dir(ltr),[dir=ltr],[dir=ltr] *){--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:right-2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){right:calc(calc(var(--spacing)) * 2)}.tw\\:rtl\\:flex:where(:dir(rtl),[dir=rtl],[dir=rtl] *){display:flex}.tw\\:rtl\\:-translate-x-px:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:translate-x-1\\/2:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(1 / 2 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:translate-x-px:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:after\\:translate-x-1\\/2:where(:dir(rtl),[dir=rtl],[dir=rtl] *):after{content:var(--tw-content);--tw-translate-x:calc(1 / 2 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}:where([data-side=primary]) .tw\\:rtl\\:in-data-\\[side\\=primary\\]\\:cursor-e-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:e-resize}:where([data-side=secondary]) .tw\\:rtl\\:in-data-\\[side\\=secondary\\]\\:cursor-w-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:w-resize}.tw\\:rtl\\:aria-\\[orientation\\=horizontal\\]\\:after\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[aria-orientation=horizontal]:after{content:var(--tw-content);--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:data-\\[side\\=left\\]\\:translate-x-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[data-side=left]{--tw-translate-x:calc(calc(var(--spacing)) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:data-\\[side\\=right\\]\\:-translate-x-1:where(:dir(rtl),[dir=rtl],[dir=rtl] *)[data-side=right]{--tw-translate-x:calc(calc(var(--spacing)) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:border-input:is(.dark *){border-color:var(--input)}.tw\\:dark\\:bg-destructive\\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:bg-destructive\\/20:is(.dark *){background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:dark\\:bg-input\\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:bg-input\\/30:is(.dark *){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:dark\\:bg-transparent:is(.dark *){background-color:#0000}.tw\\:dark\\:text-muted-foreground:is(.dark *){color:var(--muted-foreground)}.tw\\:dark\\:text-rose-400:is(.dark *){color:var(--tw-color-rose-400)}.tw\\:dark\\:text-sky-400:is(.dark *){color:var(--tw-color-sky-400)}.tw\\:dark\\:text-teal-400:is(.dark *){color:var(--tw-color-teal-400)}.tw\\:dark\\:after\\:mix-blend-lighten:is(.dark *):after{content:var(--tw-content);mix-blend-mode:lighten}@media (hover:hover){.tw\\:dark\\:hover\\:bg-blue-500:is(.dark *):hover{background-color:var(--tw-color-blue-500)}.tw\\:dark\\:hover\\:bg-destructive\\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-destructive\\/30:is(.dark *):hover{background-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.tw\\:dark\\:hover\\:bg-input\\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-input\\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.tw\\:dark\\:hover\\:bg-muted\\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:hover\\:bg-muted\\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}.tw\\:dark\\:hover\\:text-foreground:is(.dark *):hover{color:var(--foreground)}}.tw\\:dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:focus-visible\\:ring-destructive\\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:disabled\\:bg-input\\/80:is(.dark *):disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:disabled\\:bg-input\\/80:is(.dark *):disabled{background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:dark\\:disabled\\:bg-transparent:is(.dark *):disabled{background-color:#0000}:where([data-slot=tooltip-content]) .tw\\:dark\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/10:is(.dark *){background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){:where([data-slot=tooltip-content]) .tw\\:dark\\:in-data-\\[slot\\=tooltip-content\\]\\:bg-background\\/10:is(.dark *){background-color:color-mix(in oklab, var(--background) 10%, transparent)}}.tw\\:dark\\:has-disabled\\:bg-input\\/80:is(.dark *):has(:disabled){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:has-disabled\\:bg-input\\/80:is(.dark *):has(:disabled){background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:dark\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/40:is(.dark *):has([data-slot][aria-invalid=true]){--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:has-\\[\\[data-slot\\]\\[aria-invalid\\=true\\]\\]\\:ring-destructive\\/40:is(.dark *):has([data-slot][aria-invalid=true]){--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:aria-invalid\\:border-destructive\\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:aria-invalid\\:border-destructive\\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab, var(--destructive) 50%, transparent)}}.tw\\:dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:aria-invalid\\:ring-destructive\\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.tw\\:dark\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-\\[variant\\=destructive\\]\\:focus\\:bg-destructive\\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:data-open\\:animate-in:where([data-state=open]),.tw\\:data-open\\:animate-in:where([data-open]:not([data-open=false])){animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-open\\:bg-accent:where([data-state=open]),.tw\\:data-open\\:bg-accent:where([data-open]:not([data-open=false])){background-color:var(--accent)}.tw\\:data-open\\:text-accent-foreground:where([data-state=open]),.tw\\:data-open\\:text-accent-foreground:where([data-open]:not([data-open=false])){color:var(--accent-foreground)}.tw\\:data-open\\:fade-in-0:where([data-state=open]),.tw\\:data-open\\:fade-in-0:where([data-open]:not([data-open=false])){--tw-enter-opacity:0}.tw\\:data-open\\:zoom-in-95:where([data-state=open]),.tw\\:data-open\\:zoom-in-95:where([data-open]:not([data-open=false])){--tw-enter-scale:.95}@media (hover:hover){:is(.tw\\:data-open\\:hover\\:bg-sidebar-accent:where([data-state=open]),.tw\\:data-open\\:hover\\:bg-sidebar-accent:where([data-open]:not([data-open=false]))):hover{background-color:var(--sidebar-accent)}:is(.tw\\:data-open\\:hover\\:text-sidebar-accent-foreground:where([data-state=open]),.tw\\:data-open\\:hover\\:text-sidebar-accent-foreground:where([data-open]:not([data-open=false]))):hover{color:var(--sidebar-accent-foreground)}}.tw\\:data-closed\\:animate-out:where([data-state=closed]),.tw\\:data-closed\\:animate-out:where([data-closed]:not([data-closed=false])){animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.tw\\:data-closed\\:fade-out-0:where([data-state=closed]),.tw\\:data-closed\\:fade-out-0:where([data-closed]:not([data-closed=false])){--tw-exit-opacity:0}.tw\\:data-closed\\:zoom-out-95:where([data-state=closed]),.tw\\:data-closed\\:zoom-out-95:where([data-closed]:not([data-closed=false])){--tw-exit-scale:.95}.tw\\:data-checked\\:border-primary:where([data-state=checked]),.tw\\:data-checked\\:border-primary:where([data-checked]:not([data-checked=false])){border-color:var(--primary)}.tw\\:data-checked\\:bg-primary:where([data-state=checked]),.tw\\:data-checked\\:bg-primary:where([data-checked]:not([data-checked=false])){background-color:var(--primary)}.tw\\:data-checked\\:text-primary-foreground:where([data-state=checked]),.tw\\:data-checked\\:text-primary-foreground:where([data-checked]:not([data-checked=false])){color:var(--primary-foreground)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=checked]),.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-checked]:not([data-checked=false])),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=checked]),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:translate-x-\\[calc\\(100\\%-2px\\)\\]:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-checked]:not([data-checked=false])){--tw-translate-x:calc(100% - 2px);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=checked]),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-checked]:not([data-checked=false])),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=checked]),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-checked\\:-translate-x-\\[calc\\(100\\%-2px\\)\\]:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-checked]:not([data-checked=false])){--tw-translate-x:calc(calc(100% - 2px) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:data-checked\\:bg-primary:is(.dark *):where([data-state=checked]),.tw\\:dark\\:data-checked\\:bg-primary:is(.dark *):where([data-checked]:not([data-checked=false])){background-color:var(--primary)}.tw\\:dark\\:data-checked\\:bg-primary-foreground:is(.dark *):where([data-state=checked]),.tw\\:dark\\:data-checked\\:bg-primary-foreground:is(.dark *):where([data-checked]:not([data-checked=false])){background-color:var(--primary-foreground)}.tw\\:data-unchecked\\:bg-input:where([data-state=unchecked]),.tw\\:data-unchecked\\:bg-input:where([data-unchecked]:not([data-unchecked=false])){background-color:var(--input)}.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=unchecked]),.tw\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=unchecked]),.tw\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:translate-x-0:is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-state=unchecked]),.tw\\:rtl\\:group-data-\\[size\\=default\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=default] *):where([data-unchecked]:not([data-unchecked=false])),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-state=unchecked]),.tw\\:rtl\\:group-data-\\[size\\=sm\\]\\/switch\\:data-unchecked\\:-translate-x-0:where(:dir(rtl),[dir=rtl],[dir=rtl] *):is(:where(.tw\\:group\\/switch)[data-size=sm] *):where([data-unchecked]:not([data-unchecked=false])){--tw-translate-x:calc(calc(var(--spacing)) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.tw\\:dark\\:data-unchecked\\:bg-foreground:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-foreground:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:var(--foreground)}.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-state=unchecked]),.tw\\:dark\\:data-unchecked\\:bg-input\\/80:is(.dark *):where([data-unchecked]:not([data-unchecked=false])){background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.tw\\:data-selected\\:bg-muted:where([data-selected=true]){background-color:var(--muted)}.tw\\:data-selected\\:text-foreground:where([data-selected=true]){color:var(--foreground)}.tw\\:data-disabled\\:pointer-events-none:where([data-disabled=true]),.tw\\:data-disabled\\:pointer-events-none:where([data-disabled]:not([data-disabled=false])){pointer-events:none}.tw\\:data-disabled\\:cursor-not-allowed:where([data-disabled=true]),.tw\\:data-disabled\\:cursor-not-allowed:where([data-disabled]:not([data-disabled=false])){cursor:not-allowed}.tw\\:data-disabled\\:opacity-50:where([data-disabled=true]),.tw\\:data-disabled\\:opacity-50:where([data-disabled]:not([data-disabled=false])){opacity:.5}.tw\\:data-active\\:bg-background:where([data-state=active]),.tw\\:data-active\\:bg-background:where([data-active]:not([data-active=false])){background-color:var(--background)}.tw\\:data-active\\:bg-sidebar-accent:where([data-state=active]),.tw\\:data-active\\:bg-sidebar-accent:where([data-active]:not([data-active=false])){background-color:var(--sidebar-accent)}.tw\\:data-active\\:font-medium:where([data-state=active]),.tw\\:data-active\\:font-medium:where([data-active]:not([data-active=false])){--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}.tw\\:data-active\\:text-foreground:where([data-state=active]),.tw\\:data-active\\:text-foreground:where([data-active]:not([data-active=false])){color:var(--foreground)}.tw\\:data-active\\:text-sidebar-accent-foreground:where([data-state=active]),.tw\\:data-active\\:text-sidebar-accent-foreground:where([data-active]:not([data-active=false])){color:var(--sidebar-accent-foreground)}.tw\\:group-data-\\[variant\\=default\\]\\/tabs-list\\:data-active\\:shadow-sm:is(:where(.tw\\:group\\/tabs-list)[data-variant=default] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=default\\]\\/tabs-list\\:data-active\\:shadow-sm:is(:where(.tw\\:group\\/tabs-list)[data-variant=default] *):where([data-active]:not([data-active=false])){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){background-color:#0000}.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:shadow-none:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:shadow-none:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}:is(.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:after\\:opacity-100:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:after\\:opacity-100:is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false]))):after{content:var(--tw-content);opacity:1}.tw\\:dark\\:data-active\\:border-input:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:border-input:is(.dark *):where([data-active]:not([data-active=false])){border-color:var(--input)}.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-active]:not([data-active=false])){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:bg-input\\/30:is(.dark *):where([data-active]:not([data-active=false])){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}.tw\\:dark\\:data-active\\:text-foreground:is(.dark *):where([data-state=active]),.tw\\:dark\\:data-active\\:text-foreground:is(.dark *):where([data-active]:not([data-active=false])){color:var(--foreground)}.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:border-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:border-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){border-color:#0000}.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-state=active]),.tw\\:dark\\:group-data-\\[variant\\=line\\]\\/tabs-list\\:data-active\\:bg-transparent:is(.dark *):is(:where(.tw\\:group\\/tabs-list)[data-variant=line] *):where([data-active]:not([data-active=false])){background-color:#0000}.tw\\:data-horizontal\\:mx-px:where([data-orientation=horizontal]){margin-inline:1px}.tw\\:data-horizontal\\:h-1:where([data-orientation=horizontal]){height:calc(calc(var(--spacing)) * 1)}.tw\\:data-horizontal\\:h-full:where([data-orientation=horizontal]){height:100%}.tw\\:data-horizontal\\:h-px:where([data-orientation=horizontal]){height:1px}.tw\\:data-horizontal\\:w-auto:where([data-orientation=horizontal]){width:auto}.tw\\:data-horizontal\\:w-full:where([data-orientation=horizontal]){width:100%}.tw\\:data-horizontal\\:flex-col:where([data-orientation=horizontal]){flex-direction:column}.tw\\:data-vertical\\:my-px:where([data-orientation=vertical]){margin-block:1px}.tw\\:data-vertical\\:h-auto:where([data-orientation=vertical]){height:auto}.tw\\:data-vertical\\:h-full:where([data-orientation=vertical]){height:100%}.tw\\:data-vertical\\:min-h-40:where([data-orientation=vertical]){min-height:calc(calc(var(--spacing)) * 40)}.tw\\:data-vertical\\:w-1:where([data-orientation=vertical]){width:calc(calc(var(--spacing)) * 1)}.tw\\:data-vertical\\:w-auto:where([data-orientation=vertical]){width:auto}.tw\\:data-vertical\\:w-full:where([data-orientation=vertical]){width:100%}.tw\\:data-vertical\\:w-px:where([data-orientation=vertical]){width:1px}.tw\\:data-vertical\\:flex-col:where([data-orientation=vertical]){flex-direction:column}.tw\\:data-vertical\\:items-stretch:where([data-orientation=vertical]){align-items:stretch}.tw\\:data-vertical\\:self-stretch:where([data-orientation=vertical]){align-self:stretch}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:mt-0 [data-lexical-editor=true]>blockquote{margin-top:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:border-s-0 [data-lexical-editor=true]>blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:ps-0 [data-lexical-editor=true]>blockquote{padding-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:font-normal [data-lexical-editor=true]>blockquote{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:text-foreground [data-lexical-editor=true]>blockquote{color:var(--foreground)}.tw\\:\\[\\&_\\[data-lexical-editor\\=\\"true\\"\\]\\>blockquote\\]\\:not-italic [data-lexical-editor=true]>blockquote{font-style:normal}.tw\\:\\[\\&_a\\]\\:underline a{text-decoration-line:underline}.tw\\:\\[\\&_a\\]\\:underline-offset-3 a{text-underline-offset:3px}@media (hover:hover){.tw\\:\\[\\&_a\\]\\:hover\\:text-foreground a:hover{color:var(--foreground)}}.tw\\:\\[\\&_p\\:not\\(\\:last-child\\)\\]\\:mb-4 p:not(:last-child){margin-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_svg\\]\\:pointer-events-none svg{pointer-events:none}.tw\\:\\[\\&_svg\\]\\:size-4 svg{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_svg\\]\\:shrink-0 svg{flex-shrink:0}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3\\.5 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&_svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4 svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&_tr\\]\\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.tw\\:\\[\\&_tr\\:last-child\\]\\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.tw\\:\\[\\&\\:has\\(\\[role\\=checkbox\\]\\)\\]\\:pe-0:has([role=checkbox]){padding-inline-end:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\.border-b\\]\\:pb-2.border-b{padding-bottom:calc(calc(var(--spacing)) * 2)}.tw\\:\\[\\.border-b\\]\\:pb-4.border-b{padding-bottom:calc(calc(var(--spacing)) * 4)}.tw\\:group-data-\\[size\\=sm\\]\\/card\\:\\[\\.border-b\\]\\:pb-3:is(:where(.tw\\:group\\/card)[data-size=sm] *).border-b{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:\\[\\.border-t\\]\\:pt-2.border-t{padding-top:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:px-2 *)[cmdk-group-heading]{padding-inline:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:py-1\\.5 *)[cmdk-group-heading]{padding-block:calc(calc(var(--spacing)) * 1.5)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:text-xs *)[cmdk-group-heading]{font-size:var(--tw-text-xs);line-height:var(--tw-leading,var(--tw-text-xs--line-height))}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:font-medium *)[cmdk-group-heading]{--tw-font-weight:var(--tw-font-weight-medium);font-weight:var(--tw-font-weight-medium)}:is(.tw\\:\\*\\*\\:\\[\\[cmdk-group-heading\\]\\]\\:text-muted-foreground *)[cmdk-group-heading]{color:var(--muted-foreground)}:is(.tw\\:\\*\\:\\[a\\]\\:underline>*):is(a){text-decoration-line:underline}:is(.tw\\:\\*\\:\\[a\\]\\:underline-offset-3>*):is(a){text-underline-offset:3px}@media (hover:hover){.tw\\:\\[a\\]\\:hover\\:bg-destructive\\/20:is(a):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-destructive\\/20:is(a):hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.tw\\:\\[a\\]\\:hover\\:bg-muted:is(a):hover{background-color:var(--muted)}.tw\\:\\[a\\]\\:hover\\:bg-primary\\/80:is(a):hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-primary\\/80:is(a):hover{background-color:color-mix(in oklab, var(--primary) 80%, transparent)}}.tw\\:\\[a\\]\\:hover\\:bg-secondary\\/80:is(a):hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.tw\\:\\[a\\]\\:hover\\:bg-secondary\\/80:is(a):hover{background-color:color-mix(in oklab, var(--secondary) 80%, transparent)}}.tw\\:\\[a\\]\\:hover\\:text-muted-foreground:is(a):hover{color:var(--muted-foreground)}:is(.tw\\:\\*\\:\\[a\\]\\:hover\\:text-foreground>*):is(a):hover{color:var(--foreground)}}:is(.tw\\:\\*\\:\\[img\\]\\:row-span-2>*):is(img){grid-row:span 2/span 2}:is(.tw\\:\\*\\:\\[img\\]\\:translate-y-0\\.5>*):is(img){--tw-translate-y:calc(calc(var(--spacing)) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}:is(.tw\\:\\*\\:\\[img\\]\\:text-current>*):is(img){color:currentColor}:is(.tw\\:\\*\\:\\[img\\:first-child\\]\\:rounded-t-xl>*):is(img:first-child){border-top-left-radius:calc(var(--radius) * 1.4);border-top-right-radius:calc(var(--radius) * 1.4)}:is(.tw\\:\\*\\:\\[img\\:last-child\\]\\:rounded-b-xl>*):is(img:last-child){border-bottom-right-radius:calc(var(--radius) * 1.4);border-bottom-left-radius:calc(var(--radius) * 1.4)}:is(.tw\\:\\*\\:\\[img\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>*):is(img:not([class*=size-])){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:flex>*):is(span):last-child{display:flex}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:items-center>*):is(span):last-child{align-items:center}:is(.tw\\:\\*\\:\\[span\\]\\:last\\:gap-2>*):is(span):last-child{gap:calc(calc(var(--spacing)) * 2)}:is(.tw\\:\\*\\:\\[svg\\]\\:row-span-2>*):is(svg){grid-row:span 2/span 2}:is(.tw\\:\\*\\:\\[svg\\]\\:translate-y-0\\.5>*):is(svg){--tw-translate-y:calc(calc(var(--spacing)) * .5);translate:var(--tw-translate-x) var(--tw-translate-y)}:is(.tw\\:\\*\\:\\[svg\\]\\:text-current>*):is(svg){color:currentColor}:is(.tw\\:focus\\:\\*\\:\\[svg\\]\\:text-accent-foreground:focus>*):is(svg){color:var(--accent-foreground)}:is(.tw\\:data-\\[variant\\=destructive\\]\\:\\*\\:\\[svg\\]\\:text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)}:is(.tw\\:data-\\[variant\\=destructive\\]\\:\\*\\:\\[svg\\]\\:text-destructive\\![data-variant=destructive]>*):is(svg){color:var(--destructive)!important}:is(.tw\\:data-selected\\:\\*\\:\\[svg\\]\\:text-foreground:where([data-selected=true])>*):is(svg){color:var(--foreground)}:is(.tw\\:\\*\\:\\[svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>*):is(svg:not([class*=size-])){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:rounded-s-none>:not(:first-child){border-start-start-radius:0;border-end-start-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:rounded-t-none>:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:border-s-0>:not(:first-child){border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:first-child\\)\\]\\:border-t-0>:not(:first-child){border-top-style:var(--tw-border-style);border-top-width:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:rounded-e-none>:not(:last-child){border-start-end-radius:0;border-end-end-radius:0}.tw\\:\\[\\&\\>\\*\\:not\\(\\:last-child\\)\\]\\:rounded-b-none>:not(:last-child){border-bottom-right-radius:0;border-bottom-left-radius:0}.tw\\:has-\\[select\\[aria-hidden\\=true\\]\\:last-child\\]\\:\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:last-of-type\\]\\:rounded-e-lg:has(:is(select[aria-hidden=true]:last-child))>[data-slot=select-trigger]:last-of-type{border-start-end-radius:var(--radius);border-end-end-radius:var(--radius)}.tw\\:\\[\\&\\>\\[data-slot\\=select-trigger\\]\\:not\\(\\[class\\*\\=w-\\]\\)\\]\\:w-fit>[data-slot=select-trigger]:not([class*=w-]){width:fit-content}.tw\\:\\[\\&\\>\\[data-slot\\]\\:not\\(\\:has\\(\\~\\[data-slot\\]\\)\\)\\]\\:rounded-e-lg\\!>[data-slot]:not(:has(~[data-slot])){border-start-end-radius:var(--radius)!important;border-end-end-radius:var(--radius)!important}.tw\\:\\[\\&\\>\\[data-slot\\]\\:not\\(\\:has\\(\\~\\[data-slot\\]\\)\\)\\]\\:rounded-b-lg\\!>[data-slot]:not(:has(~[data-slot])){border-bottom-right-radius:var(--radius)!important;border-bottom-left-radius:var(--radius)!important}.tw\\:\\[\\&\\>blockquote\\]\\:border-s-0>blockquote{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.tw\\:\\[\\&\\>blockquote\\]\\:p-0>blockquote{padding:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&\\>blockquote\\]\\:ps-0>blockquote{padding-inline-start:calc(calc(var(--spacing)) * 0)}.tw\\:\\[\\&\\>blockquote\\]\\:font-normal>blockquote{--tw-font-weight:var(--tw-font-weight-normal);font-weight:var(--tw-font-weight-normal)}.tw\\:\\[\\&\\>blockquote\\]\\:text-foreground>blockquote{color:var(--foreground)}.tw\\:\\[\\&\\>blockquote\\]\\:not-italic>blockquote{font-style:normal}.tw\\:\\[\\&\\>input\\]\\:flex-1>input{flex:1}.tw\\:has-\\[\\>\\[data-align\\=block-end\\]\\]\\:\\[\\&\\>input\\]\\:pt-3:has(>[data-align=block-end])>input{padding-top:calc(calc(var(--spacing)) * 3)}.tw\\:has-\\[\\>\\[data-align\\=block-start\\]\\]\\:\\[\\&\\>input\\]\\:pb-3:has(>[data-align=block-start])>input{padding-bottom:calc(calc(var(--spacing)) * 3)}.tw\\:has-\\[\\>\\[data-align\\=inline-end\\]\\]\\:\\[\\&\\>input\\]\\:pe-1\\.5:has(>[data-align=inline-end])>input{padding-inline-end:calc(calc(var(--spacing)) * 1.5)}.tw\\:has-\\[\\>\\[data-align\\=inline-start\\]\\]\\:\\[\\&\\>input\\]\\:ps-1\\.5:has(>[data-align=inline-start])>input{padding-inline-start:calc(calc(var(--spacing)) * 1.5)}.tw\\:\\[\\&\\>kbd\\]\\:rounded-\\[calc\\(var\\(--radius\\)-5px\\)\\]>kbd{border-radius:calc(var(--radius) - 5px)}.tw\\:\\[\\&\\>li\\]\\:mt-2>li{margin-top:calc(calc(var(--spacing)) * 2)}.tw\\:\\[\\&\\>span\\:last-child\\]\\:truncate>span:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tw\\:\\[\\&\\>svg\\]\\:pointer-events-none>svg{pointer-events:none}.tw\\:\\[\\&\\>svg\\]\\:size-3\\!>svg{width:calc(calc(var(--spacing)) * 3)!important;height:calc(calc(var(--spacing)) * 3)!important}.tw\\:\\[\\&\\>svg\\]\\:size-3\\.5>svg{width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&\\>svg\\]\\:size-4>svg{width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>svg\\]\\:shrink-0>svg{flex-shrink:0}.tw\\:\\[\\&\\>svg\\]\\:text-sidebar-accent-foreground>svg{color:var(--sidebar-accent-foreground)}.tw\\:group-has-data-\\[size\\=lg\\]\\/avatar-group\\:\\[\\&\\>svg\\]\\:size-5:is(:where(.tw\\:group\\/avatar-group):has([data-size=lg]) *)>svg{width:calc(calc(var(--spacing)) * 5);height:calc(calc(var(--spacing)) * 5)}.tw\\:group-has-data-\\[size\\=sm\\]\\/avatar-group\\:\\[\\&\\>svg\\]\\:size-3:is(:where(.tw\\:group\\/avatar-group):has([data-size=sm]) *)>svg{width:calc(calc(var(--spacing)) * 3);height:calc(calc(var(--spacing)) * 3)}.tw\\:group-data-\\[size\\=default\\]\\/avatar\\:\\[\\&\\>svg\\]\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=default] *)>svg,.tw\\:group-data-\\[size\\=lg\\]\\/avatar\\:\\[\\&\\>svg\\]\\:size-2:is(:where(.tw\\:group\\/avatar)[data-size=lg] *)>svg{width:calc(calc(var(--spacing)) * 2);height:calc(calc(var(--spacing)) * 2)}.tw\\:group-data-\\[size\\=sm\\]\\/avatar\\:\\[\\&\\>svg\\]\\:hidden:is(:where(.tw\\:group\\/avatar)[data-size=sm] *)>svg{display:none}.tw\\:\\[\\&\\>svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-3\\.5>svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 3.5);height:calc(calc(var(--spacing)) * 3.5)}.tw\\:\\[\\&\\>svg\\:not\\(\\[class\\*\\=size-\\]\\)\\]\\:size-4>svg:not([class*=size-]){width:calc(calc(var(--spacing)) * 4);height:calc(calc(var(--spacing)) * 4)}.tw\\:\\[\\&\\>tr\\]\\:last\\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.tw\\:\\[\\&\\[align\\=center\\]\\]\\:text-center[align=center]{text-align:center}.tw\\:\\[\\&\\[align\\=right\\]\\]\\:text-right[align=right]{text-align:right}.tw\\:\\[\\&\\[aria-orientation\\=horizontal\\]\\>div\\]\\:rotate-90[aria-orientation=horizontal]>div{rotate:90deg}[data-side=primary][data-collapsible=offcanvas] .tw\\:\\[\\[data-side\\=primary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:-end-2{inset-inline-end:calc(calc(var(--spacing)) * -2)}[data-side=primary][data-state=collapsed] .tw\\:\\[\\[data-side\\=primary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-e-resize{cursor:e-resize}[data-side=primary][data-state=collapsed] .tw\\:rtl\\:\\[\\[data-side\\=primary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-w-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:w-resize}[data-side=secondary][data-collapsible=offcanvas] .tw\\:\\[\\[data-side\\=secondary\\]\\[data-collapsible\\=offcanvas\\]_\\&\\]\\:-start-2{inset-inline-start:calc(calc(var(--spacing)) * -2)}[data-side=secondary][data-state=collapsed] .tw\\:\\[\\[data-side\\=secondary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-w-resize{cursor:w-resize}[data-side=secondary][data-state=collapsed] .tw\\:rtl\\:\\[\\[data-side\\=secondary\\]\\[data-state\\=collapsed\\]_\\&\\]\\:cursor-e-resize:where(:dir(rtl),[dir=rtl],[dir=rtl] *){cursor:e-resize}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-cyrillic-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-cyrillic-wght-normal.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-greek-wght-normal.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-vietnamese-wght-normal.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-latin-ext-wght-normal.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:IBM Plex Sans Variable;font-style:normal;font-display:swap;font-weight:100 700;src:url(./files/ibm-plex-sans-latin-wght-normal.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}.light,:root{--radius:.625rem;--spacing:.25rem;--background:oklch(100% 0 0);--foreground:oklch(13.71% .036 258.53);--card:oklch(100% 0 0);--card-foreground:oklch(13.71% .036 258.53);--popover:oklch(98.43% .0018 248.56);--popover-foreground:oklch(13.71% .036 258.53);--primary:oklch(20.79% .0399 265.73);--primary-foreground:oklch(98.38% .0036 248.23);--secondary:oklch(95.89% .011 248.06);--secondary-foreground:oklch(20.79% .0399 265.73);--muted:oklch(95.89% .011 248.06);--muted-foreground:oklch(55.47% .0408 257.45);--accent:oklch(95.89% .011 248.06);--accent-foreground:oklch(20.79% .0399 265.73);--destructive:oklch(63.69% .2077 25.32);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(62.7% .194 149.214);--warning:oklch(84% .16 84);--warning-foreground:oklch(28% .07 46);--border:oklch(92.9% .0127 255.58);--input:oklch(92.9% .0127 255.58);--ring:oklch(13.71% .036 258.53);--chart-1:oklch(64.6% .222 41.116);--chart-2:oklch(60% .118 184.704);--chart-3:oklch(39.8% .07 227.392);--chart-4:oklch(82.8% .189 84.429);--chart-5:oklch(76.9% .188 70.08);--sidebar:oklch(98.43% .0018 248.56);--sidebar-foreground:oklch(13.71% .036 258.53);--sidebar-primary:oklch(20.79% .0399 265.73);--sidebar-primary-foreground:oklch(98.38% .0036 248.23);--sidebar-accent:oklch(95.89% .011 248.06);--sidebar-accent-foreground:oklch(20.79% .0399 265.73);--sidebar-border:oklch(92.9% .0127 255.58);--sidebar-ring:oklch(13.71% .036 258.53)}.dark{--background:oklch(13.71% .036 258.53);--foreground:oklch(98.38% .0036 248.23);--card:oklch(13.71% .036 258.53);--card-foreground:oklch(98.38% .0036 248.23);--popover:oklch(13.71% .036 258.53);--popover-foreground:oklch(98.38% .0036 248.23);--primary:oklch(98.38% .0036 248.23);--primary-foreground:oklch(20.79% .0399 265.73);--secondary:oklch(28% .037 259.98);--secondary-foreground:oklch(98.38% .0036 248.23);--muted:oklch(28% .037 259.98);--muted-foreground:oklch(71.07% .0351 256.8);--accent:oklch(28% .037 259.98);--accent-foreground:oklch(98.38% .0036 248.23);--destructive:oklch(39.6% .1331 25.71);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(79.2% .209 151.711);--warning:oklch(41% .11 46);--warning-foreground:oklch(99% .02 95);--border:oklch(44.54% .0374 257.3);--input:oklch(44.54% .0374 257.3);--ring:oklch(86.88% .0199 252.89);--chart-1:oklch(48.8% .243 264.376);--chart-2:oklch(69.6% .17 162.48);--chart-3:oklch(76.9% .188 70.08);--chart-4:oklch(62.7% .265 303.9);--chart-5:oklch(64.5% .246 16.439);--sidebar:oklch(13.71% .036 258.53);--sidebar-foreground:oklch(71.07% .0351 256.8);--sidebar-primary:oklch(98.38% .0036 248.23);--sidebar-primary-foreground:oklch(20.79% .0399 265.73);--sidebar-accent:oklch(28% .037 259.98);--sidebar-accent-foreground:oklch(71.07% .0351 256.8);--sidebar-border:oklch(28% .037 259.98);--sidebar-ring:oklch(86.88% .0199 252.89)}.paratext-light{--background:oklch(100% 0 0);--foreground:oklch(15.3% .006 107.1);--card:oklch(100% 0 0);--card-foreground:oklch(15.3% .006 107.1);--popover:oklch(100% 0 0);--popover-foreground:oklch(15.3% .006 107.1);--primary:oklch(55.5% .163 48.998);--primary-foreground:oklch(98.7% .022 95.277);--secondary:oklch(96.7% .001 286.375);--secondary-foreground:oklch(21% .006 285.885);--muted:oklch(96.6% .005 106.5);--muted-foreground:oklch(58% .031 107.3);--accent:oklch(96.6% .005 106.5);--accent-foreground:oklch(22.8% .013 107.4);--destructive:oklch(57.7% .245 27.325);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(62.7% .194 149.214);--warning:oklch(84% .16 84);--warning-foreground:oklch(28% .07 46);--border:oklch(93% .007 106.5);--input:oklch(93% .007 106.5);--ring:oklch(73.7% .021 106.9);--chart-1:oklch(88% .011 106.6);--chart-2:oklch(58% .031 107.3);--chart-3:oklch(46.6% .025 107.3);--chart-4:oklch(39.4% .023 107.4);--chart-5:oklch(28.6% .016 107.4);--sidebar:oklch(98.8% .003 106.5);--sidebar-foreground:oklch(15.3% .006 107.1);--sidebar-primary:oklch(66.6% .179 58.318);--sidebar-primary-foreground:oklch(98.7% .022 95.277);--sidebar-accent:oklch(96.6% .005 106.5);--sidebar-accent-foreground:oklch(22.8% .013 107.4);--sidebar-border:oklch(93% .007 106.5);--sidebar-ring:oklch(73.7% .021 106.9)}.paratext-dark{--background:oklch(15.3% .006 107.1);--foreground:oklch(98.8% .003 106.5);--card:oklch(22.8% .013 107.4);--card-foreground:oklch(98.8% .003 106.5);--popover:oklch(22.8% .013 107.4);--popover-foreground:oklch(98.8% .003 106.5);--primary:oklch(47.3% .137 46.201);--primary-foreground:oklch(98.7% .022 95.277);--secondary:oklch(27.4% .006 286.033);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(28.6% .016 107.4);--muted-foreground:oklch(73.7% .021 106.9);--accent:oklch(28.6% .016 107.4);--accent-foreground:oklch(98.8% .003 106.5);--destructive:oklch(70.4% .191 22.216);--destructive-foreground:oklch(98.38% .0036 248.23);--success-foreground:oklch(79.2% .209 151.711);--warning:oklch(41% .11 46);--warning-foreground:oklch(99% .02 95);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(58% .031 107.3);--chart-1:oklch(88% .011 106.6);--chart-2:oklch(58% .031 107.3);--chart-3:oklch(46.6% .025 107.3);--chart-4:oklch(39.4% .023 107.4);--chart-5:oklch(28.6% .016 107.4);--sidebar:oklch(22.8% .013 107.4);--sidebar-foreground:oklch(98.8% .003 106.5);--sidebar-primary:oklch(76.9% .188 70.08);--sidebar-primary-foreground:oklch(27.9% .077 45.635);--sidebar-accent:oklch(28.6% .016 107.4);--sidebar-accent-foreground:oklch(98.8% .003 106.5);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(58% .031 107.3)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}} `,"after-all"); //# sourceMappingURL=index.cjs.map diff --git a/lib/platform-bible-react/dist/index.cjs.map b/lib/platform-bible-react/dist/index.cjs.map index 3ce51e9b5ce..c3cf5bc1cb4 100644 --- a/lib/platform-bible-react/dist/index.cjs.map +++ b/lib/platform-bible-react/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"file":"index.cjs","sources":["../src/utils/shadcn-ui/utils.ts","../src/components/z-index.ts","../src/components/shadcn-ui/button.tsx","../src/utils/dir-helper.util.ts","../src/components/shadcn-ui/dialog.tsx","../src/components/shadcn-ui/input.tsx","../src/components/shadcn-ui/textarea.tsx","../src/components/shadcn-ui/input-group.tsx","../src/components/shadcn-ui/command.tsx","../src/components/shared/book.utils.ts","../src/components/shared/book-item.component.tsx","../src/components/shadcn-ui/popover.tsx","../src/components/shared/book-item.utils.ts","../src/components/advanced/recent-searches.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.utils.ts","../src/components/advanced/book-chapter-control/book-chapter-control.navigation.ts","../src/components/advanced/book-chapter-control/numbered-item-grid.component.tsx","../src/components/advanced/book-chapter-control/chapter-grid.component.tsx","../src/components/advanced/book-chapter-control/verse-grid.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.component.tsx","../src/components/advanced/book-chapter-control/book-chapter-control.types.ts","../src/components/shadcn-ui/label.tsx","../src/components/shadcn-ui/radio-group.tsx","../src/components/basics/combo-box.component.tsx","../src/components/basics/chapter-range-selector.component.tsx","../src/components/advanced/book-selector.component.tsx","../../../node_modules/@lexical/react/LexicalComposerContext.prod.mjs","../../../node_modules/@lexical/react/LexicalComposer.prod.mjs","../../../node_modules/@lexical/react/LexicalOnChangePlugin.prod.mjs","../src/components/advanced/editor/themes/editor-theme.ts","../src/components/shadcn-ui/tooltip.tsx","../src/components/advanced/editor/nodes.ts","../../../node_modules/react-error-boundary/dist/react-error-boundary.js","../../../node_modules/@lexical/react/LexicalErrorBoundary.prod.mjs","../../../node_modules/@lexical/react/useLexicalEditable.prod.mjs","../../../node_modules/@lexical/selection/LexicalSelection.prod.mjs","../../../node_modules/@lexical/utils/LexicalUtils.prod.mjs","../../../node_modules/@lexical/extension/LexicalExtension.prod.mjs","../../../node_modules/@lexical/react/LexicalReactProviderExtension.prod.mjs","../../../node_modules/@lexical/text/LexicalText.prod.mjs","../../../node_modules/@lexical/dragon/LexicalDragon.prod.mjs","../../../node_modules/@lexical/react/LexicalRichTextPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalAutoFocusPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalClearEditorPlugin.prod.mjs","../../../node_modules/@lexical/react/LexicalContentEditable.prod.mjs","../src/components/advanced/editor/editor-ui/content-editable.tsx","../src/components/advanced/editor/context/toolbar-context.tsx","../src/components/advanced/editor/editor-hooks/use-modal.tsx","../src/components/advanced/editor/plugins/toolbar/toolbar-plugin.tsx","../src/components/advanced/editor/editor-hooks/use-update-toolbar.ts","../src/components/shadcn-ui/toggle.tsx","../src/components/shadcn-ui/toggle-group.tsx","../src/components/advanced/editor/plugins/toolbar/font-format-toolbar-plugin.tsx","../src/components/advanced/editor/plugins.tsx","../src/components/advanced/editor/editor.tsx","../../../node_modules/@lexical/html/LexicalHtml.prod.mjs","../src/components/advanced/editor/editor-utils.ts","../src/components/shadcn-ui/separator.tsx","../src/components/shadcn-ui/button-group.tsx","../src/components/basics/cancel-accept-buttons.component.tsx","../src/components/advanced/comment-list/comment-list.utils.ts","../src/components/advanced/comment-editor/comment-editor.component.tsx","../src/components/advanced/comment-editor/comment-editor.types.ts","../src/components/advanced/comment-list/comment-list.types.ts","../src/hooks/listbox-keyboard-navigation.hook.ts","../src/components/shadcn-ui/badge.tsx","../src/components/shadcn-ui/card.tsx","../src/components/shadcn-ui/avatar.tsx","../src/context/menu.context.ts","../src/components/shadcn-ui/dropdown-menu.tsx","../src/components/advanced/comment-list/comment-item.component.tsx","../src/components/advanced/comment-list/comment-thread.component.tsx","../src/components/advanced/comment-list/comment-list.component.tsx","../src/components/advanced/data-table/data-table-column-toggle.component.tsx","../src/components/shadcn-ui/select.tsx","../src/components/advanced/data-table/data-table-pagination.component.tsx","../src/utils/focus.util.ts","../src/components/shadcn-ui/table.tsx","../src/components/shadcn-ui/skeleton.tsx","../src/components/advanced/data-table/data-table.component.tsx","../src/components/advanced/project-selector/project-selector.rows.ts","../src/components/advanced/project-selector/project-selector.component.tsx","../src/components/advanced/extension-marketplace/markdown-renderer.component.tsx","../src/components/basics/error-dump.component.tsx","../src/components/advanced/error-popover.component.tsx","../src/components/advanced/extension-marketplace/filter-dropdown.component.tsx","../src/components/advanced/extension-marketplace/more-info.component.tsx","../src/components/advanced/extension-marketplace/version-history.component.tsx","../src/components/advanced/extension-marketplace/footer.component.tsx","../src/components/advanced/multi-select-combo-box.component.tsx","../src/components/advanced/filter.component.tsx","../src/components/shadcn-ui/kbd.tsx","../src/components/basics/undo-redo-buttons.component.tsx","../src/components/basics/editor-keyboard-shortcuts.component.tsx","../src/components/advanced/footnote-editor/footnote-caller-dropdown.component.tsx","../src/components/advanced/footnote-editor/footnote-type-dropdown.component.tsx","../src/components/advanced/marker-menu.component.tsx","../src/components/advanced/footnote-editor/footnote-editor.utils.ts","../src/components/advanced/footnote-editor/footnote-editor.component.tsx","../src/components/advanced/footnote-editor/footnote-editor.types.ts","../src/components/advanced/footnotes/footnote-item.component.tsx","../src/components/advanced/footnotes/footnote-list.component.tsx","../src/components/advanced/inventory/occurrences-table.component.tsx","../src/components/shadcn-ui/checkbox.tsx","../src/components/advanced/inventory/inventory-columns.tsx","../src/components/advanced/inventory/inventory-utils.ts","../src/components/advanced/inventory/inventory.component.tsx","../src/components/shadcn-ui/sidebar.tsx","../src/components/advanced/settings-components/settings-sidebar.component.tsx","../src/components/basics/search-bar.component.tsx","../src/components/advanced/settings-components/settings-sidebar-content-search.component.tsx","../src/components/advanced/scripture-results-viewer/scripture-results-viewer.component.tsx","../src/components/advanced/scope-selector/scope-selector.utils.ts","../src/components/advanced/scope-selector/section-button.component.tsx","../src/components/advanced/scope-selector/book-selector.component.tsx","../src/components/advanced/scope-selector/scope-selector.component.tsx","../src/components/advanced/scroll-group-selector.component.tsx","../src/components/advanced/settings-components/settings-list.component.tsx","../src/components/advanced/menus/menu.util.ts","../src/components/advanced/menus/menu-icon.component.tsx","../src/components/advanced/menus/tab-dropdown-menu.component.tsx","../src/components/advanced/tab-toolbar/tab-toolbar-container.component.tsx","../src/components/advanced/tab-toolbar/tab-toolbar.component.tsx","../src/components/advanced/tab-toolbar/tab-floating-menu.component.tsx","../src/components/basics/tabs-vertical.tsx","../src/components/advanced/tab-navigation-content-search.component.tsx","../src/components/shadcn-ui/menubar.tsx","../src/components/advanced/menus/platform-menubar.component.tsx","../src/components/advanced/toolbar.component.tsx","../src/components/advanced/ui-language-selector.component.tsx","../src/components/basics/smart-label.component.tsx","../src/components/basics/checklist.component.tsx","../src/components/basics/linked-scr-ref-button.component.tsx","../src/components/basics/results-card.component.tsx","../src/components/basics/spinner.component.tsx","../src/components/basics/text-field.component.tsx","../src/components/shadcn-ui/alert.tsx","../src/components/shadcn-ui/context-menu.tsx","../src/components/shadcn-ui/drawer.tsx","../src/components/shadcn-ui/progress.tsx","../src/components/shadcn-ui/resizable.tsx","../src/components/shadcn-ui/sonner.tsx","../src/components/shadcn-ui/slider.tsx","../src/components/shadcn-ui/switch.tsx","../src/components/shadcn-ui/tabs.tsx","../src/hooks/use-event.hook.ts","../src/hooks/use-promise.hook.ts","../src/hooks/use-event-async.hook.ts","../src/hooks/use-stylesheet.hook.ts"],"sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { extendTailwindMerge, twMerge } from 'tailwind-merge';\n\n// Used only on the all-TW4 fast path in `cn` below. Configured with `prefix:\n// 'tw'` so it understands the `tw` prefix the shadcn classes are authored\n// with. The slow path keeps using plain `twMerge` because it first normalizes\n// tokens to the `tw:` modifier form.\nconst twMergeWithTwPrefix = extendTailwindMerge({ prefix: 'tw' });\n\n// --- Internal types ---\n\ntype PrefixInfo = {\n /** The class normalized to tw: (TW4) format for tailwind-merge */\n normalized: string;\n /** The original class string as authored */\n original: string;\n};\n\n// --- Helpers ---\n\n/**\n * Split a class string on `:` but respect brackets so that arbitrary values like `[color:red]` are\n * not split.\n */\nfunction splitClassSegments(cls: string): string[] {\n const segments: string[] = [];\n let current = '';\n let bracketDepth = 0;\n\n for (let i = 0; i < cls.length; i++) {\n const char = cls[i];\n if (char === '[') bracketDepth += 1;\n else if (char === ']') bracketDepth -= 1;\n\n if (char === ':' && bracketDepth === 0) {\n segments.push(current);\n current = '';\n } else {\n current += char;\n }\n }\n segments.push(current);\n return segments;\n}\n\n/**\n * Normalize a TW3-style (`tw-*`) class to TW4-style (`tw:*`) so that tailwind-merge (which treats\n * `tw` as a modifier) can deduplicate across both prefix formats.\n *\n * Handles four TW3 input forms:\n *\n * - `tw-utility` → `tw:utility`\n * - `tw--utility` (negative form A) → `tw:-utility`\n * - `-tw-utility` (negative form B, with optional variant prefixes) → `tw:-utility` (variants moved\n * before `tw:`)\n * - `!tw-utility` (important form, with optional variant prefixes) → `tw:!utility` (variants moved\n * before `tw:`)\n *\n * TW4-style classes (`tw:*`) pass through unchanged.\n */\nfunction normalizeTw3ToTw4(token: string): PrefixInfo {\n // Already TW4 format — pass through\n if (token.startsWith('tw:')) {\n return { normalized: token, original: token };\n }\n\n const segments = splitClassSegments(token);\n\n // Negative form B: variants may precede `-tw-utility`\n // e.g. `hover:-tw-mt-4` → segments = ['hover', '-tw-mt-4']\n const negFormBIndex = segments.findIndex((s) => s.startsWith('-tw-'));\n if (negFormBIndex !== -1) {\n const utility = segments[negFormBIndex].slice(4); // strip `-tw-`\n const variants = segments.filter((_, i) => i !== negFormBIndex);\n const normalized = `tw:${[...variants, `-${utility}`].join(':')}`;\n return { normalized, original: token };\n }\n\n // Important form: variants may precede `!tw-utility`\n // e.g. `hover:!tw-p-4` → segments = ['hover', '!tw-p-4']\n const importantFormIndex = segments.findIndex((s) => s.startsWith('!tw-'));\n if (importantFormIndex !== -1) {\n const utility = segments[importantFormIndex].slice(4); // strip `!tw-`\n const variants = segments.filter((_, i) => i !== importantFormIndex);\n const normalized = `tw:${[...variants, `!${utility}`].join(':')}`;\n return { normalized, original: token };\n }\n\n // Standard tw- prefix (last segment) — handles both positive and negative form A (tw--X)\n const lastSegment = segments[segments.length - 1];\n if (lastSegment.startsWith('tw-')) {\n const utility = lastSegment.slice(3); // strip `tw-`\n const variants = segments.slice(0, -1);\n // `tw-mt-4` → utility='mt-4' → `tw:mt-4`\n // `tw--mt-4` → utility='-mt-4' → `tw:-mt-4`\n const normalized = `tw:${[...variants, utility].join(':')}`;\n return { normalized, original: token };\n }\n\n // Not a tw-prefixed class\n return { normalized: token, original: token };\n}\n\n/**\n * Convert a normalized TW4 class (`tw:*`) back to the original TW3 format based on the format of\n * the winning original class.\n */\nfunction restoreToOriginalFormat(normalizedClass: string, originalFormat: string): string {\n // If the winner was already TW4, keep as-is\n if (originalFormat.startsWith('tw:')) {\n return normalizedClass;\n }\n\n const segments = splitClassSegments(normalizedClass);\n // Must start with `tw` modifier to be restorable\n if (segments[0] !== 'tw') return normalizedClass;\n\n const variants = segments.slice(1, -1);\n const utility = segments[segments.length - 1];\n\n // Detect which TW3 form the original used\n const origSegments = splitClassSegments(originalFormat);\n const wasNegFormB = origSegments.some((s) => s.startsWith('-tw-'));\n const wasImportantForm = origSegments.some((s) => s.startsWith('!tw-'));\n\n if (wasNegFormB && utility.startsWith('-')) {\n // `-tw-mt-4` form: strip the leading `-` from utility and wrap with `-tw-`\n const positiveUtility = utility.slice(1);\n return [...variants, `-tw-${positiveUtility}`].join(':');\n }\n\n if (wasImportantForm && utility.startsWith('!')) {\n // `!tw-p-4` form: strip the leading `!` from utility and wrap with `!tw-`\n const bareUtility = utility.slice(1);\n return [...variants, `!tw-${bareUtility}`].join(':');\n }\n\n // Standard `tw-` form (covers both positive and negative form A `tw--mt-4`)\n return [...variants, `tw-${utility}`].join(':');\n}\n\n// --- Public API ---\n\n/**\n * Tailwind and CSS class application helper function. Uses\n * [`clsx`](https://www.npmjs.com/package/clsx) to make it easy to apply classes conditionally using\n * object syntax, and uses [`tailwind-merge`](https://www.npmjs.com/package/tailwind-merge) to make\n * it easy to merge/overwrite Tailwind classes in a programmer-logic-friendly way.\n *\n * Supports both TW3 (`tw-*`) and TW4 (`tw:*`) prefix formats. When classes using different prefix\n * formats conflict (e.g. `tw-p-4` vs `tw:p-4`), the last one specified wins (standard\n * tailwind-merge behavior), and the result preserves the winning class's original prefix format.\n *\n * This backwards compatibility allows extensions still using TW3's `tw-` prefix to interoperate\n * with PBR components that have migrated to TW4's `tw:` prefix.\n *\n * This function was popularized by\n * [shadcn/ui](https://ui.shadcn.com/docs/installation/manual#add-a-cn-helper). See [ByteGrad's\n * explanation video](https://www.youtube.com/watch?v=re2JFITR7TI) for more information.\n *\n * @example\n *\n * ```typescript\n * const borderShouldBeBlue = true;\n * const textShouldBeRed = true;\n * const heightShouldBe20 = false;\n * const classString = cn(\n * 'tw:bg-primary tw:h-10 tw:text-primary-foreground',\n * 'tw:bg-secondary',\n * {\n * 'tw:border-blue-500': borderShouldBeBlue,\n * 'tw:text-red-500': textShouldBeRed,\n * 'tw:h-20': heightShouldBe20,\n * },\n * 'some-class',\n * );\n * ```\n *\n * The resulting `classString` is `'tw:h-10 tw:bg-secondary tw:border-blue-500 tw:text-red-500\n * some-class'`\n *\n * - Notice that `'tw:bg-secondary'`, specified later, overwrote `'tw:bg-primary'`, specified earlier,\n * because they are Tailwind classes that affect the same css property\n * - Notice that `'tw:text-red-500'`, specified later, overwrote `'tw:text-primary-foreground'`,\n * specified earlier, because they are Tailwind classes that affect the same css property\n * - Notice that `'tw:h-20'`, specified later, did not overwrite `'tw:h-10'`, specified earlier,\n * because `'tw:h-20'` is part of a conditional class object and its value evaluated to `false`;\n * therefore it was not applied\n * - Notice that `'some-class'` was applied. This function is not limited only to Tailwind classes.\n *\n *\n * @param inputs Class strings or `clsx` conditional class objects to merge. Tailwind classes\n * specified later in the arguments overwrite similar Tailwind classes specified earlier in the\n * arguments\n * @returns Class string containing all applicable classes from the arguments based on the rules\n * described above\n */\nexport function cn(...inputs: ClassValue[]) {\n const resolved = clsx(inputs);\n if (!resolved) return resolved;\n\n // Fast path: when no TW3 (`tw-`) prefix appears anywhere in the resolved\n // string, skip the per-token normalize/restore round-trip. All four TW3\n // forms (`tw-X`, `-tw-X`, `!tw-X`, `tw--X`) contain the substring `tw-`,\n // so a single `indexOf` rules them all out. This brings the common\n // all-TW4 case to within ~1.5x of plain twMerge instead of ~8x.\n if (resolved.indexOf('tw-') === -1) return twMergeWithTwPrefix(resolved);\n\n const tokens = resolved.split(' ').filter(Boolean);\n\n // Track the last-seen original form for each normalized class so we can restore after merge\n const lastSeenOriginal = new Map();\n const normalizedTokens: string[] = [];\n\n tokens.forEach((token) => {\n const info = normalizeTw3ToTw4(token);\n lastSeenOriginal.set(info.normalized, info.original);\n normalizedTokens.push(info.normalized);\n });\n\n // twMerge (no prefix config) treats `tw:` as a modifier — dedup works correctly\n const merged = twMerge(normalizedTokens.join(' '));\n\n // Restore surviving tokens to their original prefix format\n const mergedTokens = merged.split(' ').filter(Boolean);\n const restored = mergedTokens.map((mergedToken) => {\n const original = lastSeenOriginal.get(mergedToken);\n if (!original) return mergedToken;\n return restoreToOriginalFormat(mergedToken, original);\n });\n\n return restored.join(' ');\n}\n","// Z-INDEX SCALE — see also src/renderer/styles/_vars.scss for SCSS consumers\n// rc-dock floating tabs manage their own z-index up to ~200\n\n/** Z-index for elements that need to appear above rc-dock floating tabs (~200) */\nexport const Z_INDEX_ABOVE_DOCK = 250;\n/** Z-index for the footnote editor layer */\nexport const Z_INDEX_FOOTNOTE_EDITOR = 300;\n/** Z-index for overlay popovers and context menus */\nexport const Z_INDEX_OVERLAY = 400;\n/** Z-index for the semi-transparent backdrop behind modal dialogs */\nexport const Z_INDEX_MODAL_BACKDROP = 450;\n/** Z-index for modal dialog content */\nexport const Z_INDEX_MODAL = 500;\n/**\n * Z-index for tooltips — must render above modal dialogs since tooltips can be triggered from\n * elements inside a modal (e.g. help icons in form fields).\n */\nexport const Z_INDEX_TOOLTIP = 550;\n","import React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { Slot } from 'radix-ui';\n\nimport { cn } from '@/utils/shadcn-ui/utils';\n\n/**\n * Style variants for the Button component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\n// CUSTOM: Added TSDoc comment with link to upstream shadcn/ui documentation.\nconst buttonVariants = cva(\n // CUSTOM: Added 'pr-twp' at the front of the base class string to apply Platform.Bible's\n // Tailwind CSS scope isolation. All Button instances inherit this via buttonVariants.\n 'pr-twp tw:group/button tw:inline-flex tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-transparent tw:bg-clip-padding tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:active:not-aria-[haspopup]:translate-y-px tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4',\n {\n variants: {\n variant: {\n default: 'tw:bg-primary tw:text-primary-foreground tw:[a]:hover:bg-primary/80',\n outline:\n 'tw:border-border tw:bg-background tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:border-input tw:dark:bg-input/30 tw:dark:hover:bg-input/50',\n secondary:\n 'tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-secondary/80 tw:aria-expanded:bg-secondary tw:aria-expanded:text-secondary-foreground',\n ghost:\n 'tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:hover:bg-muted/50',\n destructive:\n 'tw:bg-destructive/10 tw:text-destructive tw:hover:bg-destructive/20 tw:focus-visible:border-destructive/40 tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:hover:bg-destructive/30 tw:dark:focus-visible:ring-destructive/40',\n link: 'tw:text-primary tw:underline-offset-4 tw:hover:underline',\n },\n size: {\n default:\n 'tw:h-8 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2',\n xs: 'tw:h-6 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),10px)] tw:px-2 tw:text-xs tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3',\n sm: 'tw:h-7 tw:gap-1 tw:rounded-[min(var(--tw-radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pe-1.5 tw:has-data-[icon=inline-start]:ps-1.5 tw:[&_svg:not([class*=size-])]:size-3.5',\n lg: 'tw:h-9 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pe-2 tw:has-data-[icon=inline-start]:ps-2',\n icon: 'tw:size-8',\n 'icon-xs':\n 'tw:size-6 tw:rounded-[min(var(--tw-radius-md),10px)] tw:in-data-[slot=button-group]:rounded-lg tw:[&_svg:not([class*=size-])]:size-3',\n 'icon-sm':\n 'tw:size-7 tw:rounded-[min(var(--tw-radius-md),12px)] tw:in-data-[slot=button-group]:rounded-lg',\n 'icon-lg': 'tw:size-9',\n },\n },\n defaultVariants: {\n variant: 'default',\n size: 'default',\n },\n },\n);\n\n/**\n * Props for the Button component.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\n// CUSTOM: Added ButtonProps interface exporting the combined props type so callers can type\n// button-related props without importing VariantProps directly.\nexport interface ButtonProps\n extends React.ComponentProps<'button'>,\n VariantProps {\n asChild?: boolean;\n}\n\n/**\n * The Button component displays a button or a component that looks like a button. The component is\n * built and styled by Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/button}\n */\n// CUSTOM: Added TSDoc comment with link to upstream shadcn/ui documentation.\nfunction Button({\n className,\n variant = 'default',\n size = 'default',\n asChild = false,\n ...props\n}: ButtonProps) {\n const Comp = asChild ? Slot.Root : 'button';\n\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n","/** Text and layout direction */\nexport type Direction = 'rtl' | 'ltr';\n\nconst STORAGE_KEY: string = 'layoutDirection';\n\n/** Read layout direction from localStorage or return 'ltr' */\nexport function readDirection(): Direction {\n const retrieved = localStorage.getItem(STORAGE_KEY);\n if (retrieved === 'rtl') {\n return retrieved;\n }\n return 'ltr';\n}\n\n/** Write layout direction to localStorage */\nexport function persistDirection(dir: Direction): void {\n localStorage.setItem(STORAGE_KEY, dir);\n}\n","import React from 'react';\nimport { Dialog as DialogPrimitive } from 'radix-ui';\n\n// CUSTOM: Import shared z-index constants so modals stack above rc-dock and other overlay layers\nimport { Z_INDEX_MODAL, Z_INDEX_MODAL_BACKDROP } from '@/components/z-index';\nimport { cn } from '@/utils/shadcn-ui/utils';\nimport { Button } from '@/components/shadcn-ui/button';\n// CUSTOM: Import readDirection for RTL support\nimport { readDirection } from '@/utils/dir-helper.util';\nimport { IconX } from '@tabler/icons-react';\n\n/**\n * The Dialog component displays a modal dialog window. Built on Radix UI's Dialog primitive and\n * styled by Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction Dialog({ ...props }: React.ComponentProps) {\n return ;\n}\n\n/**\n * Button or element that opens the dialog when clicked.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogTrigger({ ...props }: React.ComponentProps) {\n return ;\n}\n\n/**\n * Portals the dialog content into `document.body` to avoid z-index and overflow issues.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogPortal({ ...props }: React.ComponentProps) {\n return ;\n}\n\n/**\n * Button or element that closes the dialog when clicked.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogClose({ ...props }: React.ComponentProps) {\n return ;\n}\n\n/**\n * Semi-transparent backdrop rendered behind the dialog content. Animates on open/close.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogOverlay({\n className,\n // CUSTOM: Destructure style to allow merging with shared z-index constant\n style,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\n/**\n * Props for {@link DialogContent}. Extends the Radix UI Dialog.Content props with an optional\n * `overlayClassName` for per-instance backdrop styling and `showCloseButton` to control the close\n * button visibility.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\n// CUSTOM: Extend DialogContentProps with overlayClassName prop to allow per-call backdrop styling\nexport type DialogContentProps = React.ComponentProps & {\n /**\n * Additional CSS classes for the backdrop (`DialogOverlay`). Use when one dialog needs different\n * overlay styling than the default.\n */\n overlayClassName?: string;\n showCloseButton?: boolean;\n};\n\n/**\n * Main container for dialog content. Renders inside a portal with an overlay backdrop, centered on\n * screen. Includes an optional close button in the top corner.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n // CUSTOM: Destructure overlayClassName to forward to DialogOverlay for per-call backdrop styling\n overlayClassName,\n // CUSTOM: Destructure style to allow merging with shared z-index constant\n style,\n ...props\n}: DialogContentProps) {\n // CUSTOM: Use readDirection for RTL support — sets dir on dialog content so text direction is correct\n const dir = readDirection();\n return (\n \n {/* CUSTOM: Pass overlayClassName to DialogOverlay for per-call backdrop styling */}\n \n \n {children}\n {showCloseButton && (\n \n \n \n )}\n \n \n );\n}\n\n/**\n * Container for the dialog's header area. Stacks title and description vertically.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n \n );\n}\n\n/**\n * Container for the dialog's footer area. Lays out action buttons in a row on larger screens.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogFooter({\n className,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<'div'> & {\n showCloseButton?: boolean;\n}) {\n return (\n \n {children}\n {showCloseButton && (\n \n \n \n )}\n
\n );\n}\n\n/**\n * Renders the dialog's title as a styled heading. Used inside DialogHeader.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogTitle({ className, ...props }: React.ComponentProps) {\n return (\n \n );\n}\n\n/**\n * Renders the dialog's description text in a muted style. Used inside DialogHeader.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/dialog}\n * @see Radix UI Documentation: {@link https://www.radix-ui.com/primitives/docs/components/dialog}\n */\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps) {\n return (\n \n );\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import React from 'react';\n\nimport { cn } from '@/utils/shadcn-ui/utils';\n\n/**\n * Input component displays a form input field or a component that looks like an input field. Built\n * and styled with Shadcn UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction Input({ className, type, ...props }: React.ComponentProps<'input'>) {\n return (\n \n );\n}\n\nexport { Input };\n","import React from 'react';\n\nimport { cn } from '@/utils/shadcn-ui/utils';\n\n// CUSTOM: Added TSDoc with link to shadcn/ui documentation for this component\n/**\n * Displays a form textarea or a component that looks like a textarea. This component is from Shadcn\n * UI.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/textarea}\n */\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n return (\n \n );\n}\n\nexport { Textarea };\n","import React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/utils/shadcn-ui/utils';\nimport { Button } from '@/components/shadcn-ui/button';\nimport { Input } from '@/components/shadcn-ui/input';\nimport { Textarea } from '@/components/shadcn-ui/textarea';\n\n/**\n * A compound input group component that wraps an input with optional addons, buttons, or text.\n * Provides focus-ring coordination and layout management for inline input decorations.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction InputGroup({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n [data-align=block-end]]:h-auto tw:has-[>[data-align=block-end]]:flex-col tw:has-[>[data-align=block-start]]:h-auto tw:has-[>[data-align=block-start]]:flex-col tw:has-[>textarea]:h-auto tw:dark:bg-input/30 tw:dark:has-disabled:bg-input/80 tw:dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 tw:has-[>[data-align=block-end]]:[&>input]:pt-3 tw:has-[>[data-align=block-start]]:[&>input]:pb-3 tw:has-[>[data-align=inline-end]]:[&>input]:pe-1.5 tw:has-[>[data-align=inline-start]]:[&>input]:ps-1.5',\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Variants for the {@link InputGroupAddon} component controlling its inline or block placement.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nconst inputGroupAddonVariants = cva(\n 'tw:flex tw:h-auto tw:cursor-text tw:items-center tw:justify-center tw:gap-2 tw:py-1.5 tw:text-sm tw:font-medium tw:text-muted-foreground tw:select-none tw:group-data-[disabled=true]/input-group:opacity-50 tw:[&>kbd]:rounded-[calc(var(--radius)-5px)] tw:[&>svg:not([class*=size-])]:size-4',\n {\n variants: {\n align: {\n 'inline-start':\n 'tw:order-first tw:ps-2 tw:has-[>button]:ms-[-0.3rem] tw:has-[>kbd]:ms-[-0.15rem]',\n 'inline-end':\n 'tw:order-last tw:pe-2 tw:has-[>button]:me-[-0.3rem] tw:has-[>kbd]:me-[-0.15rem]',\n 'block-start':\n 'tw:order-first tw:w-full tw:justify-start tw:px-2.5 tw:pt-2 tw:group-has-[>input]/input-group:pt-2 tw:[.border-b]:pb-2',\n 'block-end':\n 'tw:order-last tw:w-full tw:justify-start tw:px-2.5 tw:pb-2 tw:group-has-[>input]/input-group:pb-2 tw:[.border-t]:pt-2',\n },\n },\n defaultVariants: {\n align: 'inline-start',\n },\n },\n);\n\n/**\n * An addon placed inside an {@link InputGroup}, used to display icons, buttons, or text adjacent to\n * the input. Clicking the addon area proxies focus to the associated input.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction InputGroupAddon({\n className,\n align = 'inline-start',\n ...props\n}: React.ComponentProps<'div'> & VariantProps) {\n return (\n // CUSTOM: Clicking anywhere in the addon area proxies focus to the associated input — a\n // deliberate UX enhancement. The a11y rules flag a non-interactive role=\"group\" element having\n // a click handler, but removing the handler would degrade the UX. Keyboard focus on the input\n // itself is still accessible and not affected by this handler.\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions\n {\n // CUSTOM: Use instanceof guard instead of 'as HTMLElement' type assertion to safely access .closest()\n if (e.target instanceof HTMLElement && e.target.closest('button')) {\n return;\n }\n e.currentTarget.parentElement?.querySelector('input')?.focus();\n }}\n {...props}\n />\n );\n}\n\n/**\n * Variants for the {@link InputGroupButton} component controlling size.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nconst inputGroupButtonVariants = cva('tw:flex tw:items-center tw:gap-2 tw:text-sm tw:shadow-none', {\n variants: {\n size: {\n xs: 'tw:h-6 tw:gap-1 tw:rounded-[calc(var(--radius)-3px)] tw:px-1.5 tw:[&>svg:not([class*=size-])]:size-3.5',\n sm: 'tw:',\n 'icon-xs': 'tw:size-6 tw:rounded-[calc(var(--radius)-3px)] tw:p-0 tw:has-[>svg]:p-0',\n 'icon-sm': 'tw:size-8 tw:p-0 tw:has-[>svg]:p-0',\n },\n },\n defaultVariants: {\n size: 'xs',\n },\n});\n\n/**\n * A ghost button sized to fit inside an {@link InputGroup}.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction InputGroupButton({\n className,\n type = 'button',\n variant = 'ghost',\n size = 'xs',\n ...props\n}: Omit, 'size'> &\n VariantProps) {\n return (\n \n );\n}\n\n/**\n * A plain text span styled to fit inline inside an {@link InputGroup}.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction InputGroupText({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n \n );\n}\n\n/**\n * An `` styled to occupy its slot inside an {@link InputGroup}, with borders and rings\n * suppressed so the group provides the visual boundary.\n *\n * @see Shadcn UI Documentation: {@link https://ui.shadcn.com/docs/components/input}\n */\nfunction InputGroupInput({ className, ...props }: React.ComponentProps<'input'>) {\n return (\n \n );\n}\n\n/**\n * A `