Skip to content

Export grammar and texts for AI analysis - #1070

Open
johnml1135 wants to merge 2 commits into
mainfrom
grammar-text-export
Open

Export grammar and texts for AI analysis#1070
johnml1135 wants to merge 2 commits into
mainfrom
grammar-text-export

Conversation

@johnml1135

@johnml1135 johnml1135 commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Adds "Export Grammar and Texts for AI Analysis" to the Export dialog: pick some texts, pick a folder, and FLEx writes the project's HermitCrab grammar (HCGrammar.xml) plus one .flextext file per text there, ready to hand an LLM for linguistic analysis.

The diff is bigger than the feature sounds because of one real constraint: InterlinVc/InterlinearExporter (needed to write .flextext) live in ITextDll, which already depends on xWorks (where the Export dialog lives) -- a reference the other way is a build-breaking cycle. So the text half goes through a new Publisher/Subscriber event answered by a listener registered in Main.xml, reusing the exact idiom AreaListener already uses for GetContentControlParameters rather than inventing something new. The grammar half is simpler: a new xWorks -> ParserCore reference (verified acyclic) reuses the same HCLoader/XmlLanguageWriter pipeline the existing GenerateHCConfig console tool already uses.

Where to look:

  • xWorks -> ParserCore: no cycle back (ParserCore doesn't reference xWorks; only ParserUI, one layer up, references both).
  • The pub/sub text-export path: FlexTextAIExportListener (in ITextDll) answering ExportTextsAsFlexText, registered in Main.xml.
  • An unhandled HCLoader exception aborts the whole export, not just the grammar half -- deliberate, and now has a direct test.
  • The summary MessageBox is shown by the UI thread after RunTask returns, not from the background task itself -- a real cross-thread bug avoided during implementation.

Deliberately not here: no guard against the live in-app parser running concurrently with this export (see the accordion below -- every sibling export has the same exposure today); no per-text format customization; no zip/incremental re-export.

Build/tests: full ./test.ps1 managed suite, clean except 12 pre-existing RenderComparisonTests pixel-baseline diffs and one native TestViews timing issue, both unrelated to any file this branch touches. gitlint clean. Manually verified end-to-end against the Sena 3 sample project.


Reading this a year from now -- start here

This branch had a design spec and a step-by-step implementation plan
(Docs/superpowers/specs/2026-08-15-grammar-text-export-design.md and
Docs/superpowers/plans/2026-08-15-grammar-text-export.md) while the work
was in progress. Both were deleted before merge -- the plan was a
task-by-task TDD checklist with no lasting value once the code exists, and
the spec's conclusions are either already expressed in code comments (where
the fieldworks-code-commenting standard permits it) or preserved here,
since that standard specifically bans .md file/section pointers from code
comments. This record is deliberately the only place some of this reasoning
survives -- if you're trying to understand why something here looks the
way it does, this is where to look, not the deleted files.

The layer cake
ExportDialog (xWorks)
  -> GrammarAndTextsAIExportSelectionDlg (WinForms picker: checkbox + Words/Analyses per text)
  -> FolderBrowserDialogAdapter (same idiom the existing LIFT export uses)
  -> ExportGrammarAndTextsForAI (background task, via ProgressDialogWithTask.RunTask)
       |
       +--> HCLoader.Load(cache) -> Language -> XmlLanguageWriter.Save -> HCGrammar.xml
       |    (same pipeline GenerateHCConfig.exe already uses)
       |
       +--> Publisher.Publish(EventConstants.ExportTextsAsFlexText, request)
              -> FlexTextAIExportListener (ITextDll, globally registered in Main.xml)
                   -> InterlinVc + InterlinLineChoices.DefaultChoices + InterlinearExporter
                        -> one <title>.flextext file per selected text

The grammar half never leaves xWorks; the text half necessarily crosses a
DLL boundary that a direct reference can't cross, which is the reason the
publish/subscribe hop exists at all.

Decisions, and why
  • Pub/sub instead of a direct reference for the text half. ITextDll
    already has a ProjectReference to xWorks (it subclasses ExportDialog
    for InterlinearExportDialog). Adding the reverse reference to reach
    InterlinVc/InterlinearExporter from xWorks would be a genuine
    build-breaking cycle, not a style preference. The fix reuses the exact
    idiom ExportDialog.EnsureViewInfo() already uses for
    EventConstants.GetContentControlParameters (answered by AreaListener,
    registered globally in Main.xml) instead of inventing a new mechanism.
  • An unhandled HCLoader/XmlLanguageWriter exception aborts the whole
    export; a per-text FLExText failure only skips that one text.

    HCLoader already catches per-item linguistic problems internally (bad
    phonemes, bad affix processes, etc.) and routes them to its
    IHCLoadErrorLogger argument, so anything that escapes indicates a real
    bug, not messy grammar data -- worth failing loudly for, unlike a single
    text's export failing independently of the others.
  • Flat output folder, not a Texts/ subfolder. HCGrammar.xml and every
    .flextext file land directly in the chosen folder. Extensions already
    disambiguate them, and a flat folder was the simpler, explicitly requested
    layout.
  • Two separate counts in the text picker -- Words and Analyses -- rather
    than one. Words counts every word-token occurrence regardless of whether
    it's been analyzed (a raw "how much text is this" signal); Analyses counts
    only the subset with a real IWfiAnalysis/IWfiGloss attached, whether by
    a human or an unreviewed parser guess (a "how much have I actually analyzed"
    signal). Conflating them would have hidden exactly the distinction someone
    picking texts for analysis actually cares about.
  • No guard against the live in-app parser running concurrently with this
    export.
    Traced the actual mechanism: ParserConnection/ParserScheduler
    runs in-process against the same LcmCache, processing its queue via an
    IdleQueue tied to the UI thread's Application.Idle event -- and a modal
    ShowDialog() (which is what blocks the UI during this export) still
    raises Application.Idle in WinForms, so the parser can keep mutating
    wordform analyses while this export's background thread reads the same
    cache. This exposure already exists, unmitigated, in every sibling export
    (LIFT, Phonology, Grammar Sketch, the existing per-text FLExText export) --
    adding a bespoke guard to only this one would be inconsistent with that
    precedent and out of proportion to a risk the codebase has apparently
    tolerated for a long time. A deliberate choice, not an oversight.
  • HCGrammar.xml, not Grammar.xml. "Grammar" is already overloaded
    three ways in this codebase: the Grammar Area (one of five top-level UI
    areas), the existing "Grammar Sketch" export (an unrelated human-readable
    linguistic-description document), and this HermitCrab-format grammar. The
    filename is self-disambiguating even sitting alone in a folder of
    .flextext files; CONTEXT.md now canonicalizes "HC grammar" as the term
    for the third one.
Paths not taken
  • A per-form .resx for the picker dialog, matching some sibling dialogs'
    pattern of resources.ApplyResources(control, "control"). Used the shared
    xWorksStrings.resx instead (adding ksAIExportColumn*, ksOK,
    ksCancel, etc.), matching InterlinearExportDialog's simpler existing
    precedent of pulling column text from a shared strings class directly in
    code. Caught during review that this file's OK/Cancel buttons had
    initially been hardcoded rather than pulled from either -- fixed to use
    the shared resx.
  • Reflection-based access to InterlinVc/InterlinearExporter via the
    same DynamicLoader.CreateObject(assemblyPath, className) pattern
    ExportDialog.EnsureViewInfo() already uses to reflectively load an
    Interlinear-area control for the existing "Grammar Sketch" export. Rejected
    because every actual method call and property access on those types would
    then need reflection too (not just construction), which is far messier
    than the pub/sub hop for a type this deeply used (InterlinVc.LineChoices,
    InterlinearExporter.Create/WriteBeginDocument/ExportDisplay/WriteEndDocument).
  • Reimplementing FLExText writing directly in xWorks from
    IStText.ParagraphsOS/Segment.Analyses, avoiding the cross-DLL problem
    entirely. Rejected as substantial, error-prone duplication of exactly the
    logic InterlinearExporter already gets right (headwords, morpheme
    breakdowns, gloss lines, multiple writing systems).
Surprising findings
  • HCLoader.Load has real, undocumented preconditions beyond "the cache
    exists": MorphologicalDataOA.ParserParameters must already be a valid XML
    fragment (XElement.Parse throws ArgumentNullException on a blank
    project's default null), at least one phoneme set must exist
    (PhonologicalDataOA.PhonemeSetsOS[0] is indexed directly), and that
    phoneme set needs morph (+) and word (#) boundary markers
    (LoadCharacterDefinitionTable throws KeyNotFoundException looking one
    up by representation otherwise). None of this is asserted anywhere in
    HCLoader itself; it was discovered by writing a test against a bare
    LcmCache.CreateCacheWithNewBlankLangProj and fixing each crash in turn.
  • A namespace collision between SIL.LCModel.IText and the
    SIL.FieldWorks.IText namespace
    (ITextDll's own root namespace).
    Any test project that references both assemblies hits C#'s
    enclosing-namespace lookup finding the sibling namespace before the using SIL.LCModel; import, so bare IText is CS0118 ("is a namespace")
    wherever the containing code's own namespace nests under SIL.FieldWorks.
    Fixed by qualifying as SIL.LCModel.IText at each use site.
  • InterlinearTestBase's fixture already runs each test inside an ambient
    undo task
    , unlike a bare LcmCache built directly in a test -- wrapping
    object creation in another UndoableUnitOfWorkHelper.Do inside a test that
    inherits from it throws InvalidOperationException: Nested tasks are not supported, matching the pattern already used by
    ComplexConcPatternModelTests (which creates objects with no UnitOfWork
    wrapper at all).
Evidence
  • gitlint --ignore body-is-missing --commits origin/main..HEAD -- clean
    (exit 0) against the single squashed commit.
  • ./test.ps1 (full managed suite, -SkipNative) -- run repeatedly across
    the branch's implementation and again after the rebase onto the current
    origin/main tip and the two review-driven fixes. The only failures in
    any run are the same 12 RenderComparisonTests pixel-baseline diffs
    (complex, custom-heavy, footnote-heavy, many-paragraphs, medium,
    multi-book) and one native TestViews timing/rendering issue every
    time, none of which touch xWorks, ITextDll, FwUtils, or ParserCore
    -- not re-run against a clean origin/main checkout to get a formal
    before/after baseline, but consistent and content-unrelated across every
    run this branch produced.
  • New classes each have focused unit tests: GrammarTextsAIExportHelpers
    (word/analysis counting, filename sanitization), GrammarExportLoadLogger,
    GrammarAndTextsAIExportSelectionDlg (selection persistence),
    ExportDialog.ExportGrammarAndTextsForAI (grammar write, per-text-failure
    recording, exception propagation), FlexTextAIExportListener (actual
    .flextext file production), and ExportTextsAsFlexTextRequest.
  • Manually verified in the running app (fieldworks-winapp skill, Sena 3
    sample project, Legacy/WinForms UI mode): the new option appears in the
    Lexicon area's Export dialog, the picker and folder browser both work, and
    the export produces the expected files.
  • Docs/ai-parser-help/'s 92 relative links and 4 anchors were verified to
    resolve post-copy (a PowerShell link-target-existence pass), so it needed
    no link surgery when moved from the sillsdev/machine repo.

This change is Reviewable

johnml1135 and others added 2 commits August 15, 2026 13:42
Adds "Export Grammar and Texts for AI Analysis" to the Export
dialog: it writes the project's HC grammar (HCGrammar.xml) and one
.flextext file per selected text into a chosen folder, via a
WinForms text picker showing Words/Analyses counts per text and
remembering the last selection.

The text half runs through a new Publisher/Subscriber event
(ExportTextsAsFlexText) answered by a listener registered in
ITextDll, since InterlinVc/InterlinearExporter are only reachable
there without a build-breaking reference cycle back to xWorks. The
grammar half reuses the existing HCLoader/XmlLanguageWriter
pipeline via a new xWorks -> ParserCore project reference.

Bundles docs/ai-parser-help (44 files) from the sillsdev/machine
repo's docs/hc-llm-guide branch into Docs/ai-parser-help, since a
raw grammar/text export is not very interpretable by an LLM without
it. The export's description links to it for both an LLM (raw URL)
and a human reader (github.com/blob URL).

Follows the fieldworks-code-commenting standard (no named
collaborators, "see X" pointers, or provenance framing in doc
comments) and pulls the picker dialog's OK/Cancel button text from
xWorksStrings instead of hardcoding it, matching sibling dialogs.

Also ignores .review/, pr-preflight's scratch directory, which had
no .gitignore entry and kept showing up as an untracked stray.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148xarEFPC5GA38C1Zu17V4
The task-by-task implementation plan and the brainstorming-stage
design spec have no lasting value once the code exists; their
durable reasoning (architecture decisions, the parser-concurrency
rationale, implementation gotchas) now lives in the PR description
instead, since the fieldworks-code-commenting standard already bans
.md file/section pointers from code comments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148xarEFPC5GA38C1Zu17V4
@github-actions

Copy link
Copy Markdown

NUnit Tests

    1 files  ± 0      1 suites  ±0   11m 31s ⏱️ + 1m 7s
5 787 tests +13  5 706 ✅ +13  81 💤 ±0  0 ❌ ±0 
5 796 runs  +13  5 715 ✅ +13  81 💤 ±0  0 ❌ ±0 

Results for commit 3929961. ± Comparison against base commit 99858db.

@codecov-commenter

codecov-commenter commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.35714% with 111 lines in your changes missing coverage. Please review.
✅ Project coverage is 38.07%. Comparing base (99858db) to head (3929961).

Files with missing lines Patch % Lines
Src/xWorks/ExportDialog.cs 30.30% 37 Missing and 9 partials ⚠️
...rc/LexText/Interlinear/FlexTextAIExportListener.cs 45.83% 24 Missing and 2 partials ⚠️
Src/xWorks/GrammarExportLoadLogger.cs 27.77% 25 Missing and 1 partial ⚠️
Src/xWorks/GrammarAndTextsAIExportSelectionDlg.cs 78.78% 6 Missing and 1 partial ⚠️
Src/xWorks/GrammarTextsAIExportHelpers.cs 90.47% 2 Missing and 2 partials ⚠️
...ks/GrammarAndTextsAIExportSelectionDlg.Designer.cs 95.91% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1070      +/-   ##
==========================================
+ Coverage   38.04%   38.07%   +0.03%     
==========================================
  Files        1499     1505       +6     
  Lines      350127   350407     +280     
  Branches    40239    40267      +28     
==========================================
+ Hits       133215   133427     +212     
- Misses     187625   187677      +52     
- Partials    29287    29303      +16     
Files with missing lines Coverage Δ
Src/Common/FwUtils/ExportTextsAsFlexTextRequest.cs 100.00% <100.00%> (ø)
...ks/GrammarAndTextsAIExportSelectionDlg.Designer.cs 95.91% <95.91%> (ø)
Src/xWorks/GrammarTextsAIExportHelpers.cs 90.47% <90.47%> (ø)
Src/xWorks/GrammarAndTextsAIExportSelectionDlg.cs 78.78% <78.78%> (ø)
...rc/LexText/Interlinear/FlexTextAIExportListener.cs 45.83% <45.83%> (ø)
Src/xWorks/GrammarExportLoadLogger.cs 27.77% <27.77%> (ø)
Src/xWorks/ExportDialog.cs 14.63% <30.30%> (+0.74%) ⬆️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants