From 98e035771c49c2cc452a6aba1fad3a39d06d4485 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 21:24:02 +0300 Subject: [PATCH 01/21] Initial commit with task details for issue #18 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/deep-assistant/human-language/issues/18 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7dccab0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/deep-assistant/human-language/issues/18 +Your prepared branch: issue-18-ef8b353c +Your prepared working directory: /tmp/gh-issue-solver-1757528639431 + +Proceed. \ No newline at end of file From 98a281916543e9d861b972b7827249f33963608a Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 21:24:20 +0300 Subject: [PATCH 02/21] Remove CLAUDE.md - PR created successfully --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 7dccab0..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/deep-assistant/human-language/issues/18 -Your prepared branch: issue-18-ef8b353c -Your prepared working directory: /tmp/gh-issue-solver-1757528639431 - -Proceed. \ No newline at end of file From 56003b7f1bb0d83b115ae1fd8d4f6ef660787def Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Sep 2025 21:31:02 +0300 Subject: [PATCH 03/21] feat: Add comprehensive Abstract Wikipedia analysis and lessons learned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Research Abstract Wikipedia's technical approach and development challenges - Identify key lessons for human-language project including modular architecture, lexicographic integration, and context-aware transformation - Provide specific implementation recommendations for enhanced n-gram processing, semantic validation, and multi-language generation - Document risk mitigation strategies and performance optimization patterns - Create actionable roadmap based on Abstract Wikipedia's successes and pitfalls 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- research/abstract-wikipedia-analysis.md | 268 ++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 research/abstract-wikipedia-analysis.md diff --git a/research/abstract-wikipedia-analysis.md b/research/abstract-wikipedia-analysis.md new file mode 100644 index 0000000..d2a166d --- /dev/null +++ b/research/abstract-wikipedia-analysis.md @@ -0,0 +1,268 @@ +# Learning from Abstract Wikipedia Development + +## Executive Summary + +Abstract Wikipedia, launched with Wikifunctions in July 2023, offers valuable lessons for the human-language project. This analysis identifies key improvements and strategies that could accelerate development or enhance the semantic transformation approach. + +## Abstract Wikipedia Project Overview + +### Core Architecture +- **Wikifunctions**: Computational functions for data processing +- **Wikidata**: Structured semantic data repository +- **Abstract Content**: Language-independent article representations +- **Natural Language Generation**: Functions to render content in multiple languages + +### Technical Approach +``` +Abstract Content → Wikifunctions → Language-Specific Rendering +(Structured Data) (Processing) (Human-Readable Text) +``` + +## Key Lessons for Human-Language Project + +### 1. Structured Content Representation + +**Abstract Wikipedia Strategy:** +- Uses Wikidata QIDs to represent predicates, participants, and modifiers +- Creates nested list-like structures for complex semantic relationships +- Enables language-agnostic content representation + +**Application to Human-Language:** +```javascript +// Current approach in text-to-qp-transformer.js +"Einstein was born in Germany" → ["Q937", "P19", "Q183"] + +// Enhanced approach inspired by Abstract Wikipedia +{ + predicate: "P569", // date of birth + subject: "Q937", // Einstein + object: { + date: "1879-03-14", + location: "Q183" // Germany + }, + context: { + precision: "day", + calendar: "Q1985727" // proleptic Gregorian calendar + } +} +``` + +### 2. Modular Function Architecture + +**Lesson:** Break complex transformations into reusable, composable functions. + +**Recommended Implementation:** +```javascript +// Inspired by Wikifunctions approach +class SemanticFunctionLibrary { + constructor() { + this.functions = { + 'temporal_relation': this.handleTemporalRelations, + 'spatial_relation': this.handleSpatialRelations, + 'causal_relation': this.handleCausalRelations, + 'identity_relation': this.handleIdentityRelations + }; + } + + // Compose functions for complex semantic structures + compose(functions, input) { + return functions.reduce((result, fn) => this.functions[fn](result), input); + } +} +``` + +### 3. Lexicographic Integration + +**Abstract Wikipedia Focus:** Extensive work with Wikidata Lexemes for linguistic accuracy. + +**Recommendation for Human-Language:** +- Integrate Wikidata Lexemes API for better word sense disambiguation +- Use lexicographic data to improve n-gram matching accuracy +- Handle morphological variations and inflected forms + +```javascript +// Enhanced search with lexeme support +class EnhancedSearchUtility extends WikidataSearchUtility { + async searchWithLexemes(term, language = 'en') { + const [entityResults, lexemeResults] = await Promise.all([ + this.searchEntities(term), + this.searchLexemes(term, language) + ]); + + return this.mergeAndRankResults(entityResults, lexemeResults); + } +} +``` + +### 4. Context-Aware Transformation + +**Challenge Identified:** Current system struggles with context-dependent meanings. + +**Solution Inspired by Abstract Wikipedia:** +```javascript +// Context-aware transformation with participant roles +class ContextualTransformer { + async transform(text, context = {}) { + const parseTree = this.parseSemanticStructure(text); + const enrichedTree = await this.enrichWithContext(parseTree, context); + return this.generateQPSequence(enrichedTree); + } + + parseSemanticStructure(text) { + // Extract predicates, subjects, objects with their roles + // Similar to Abstract Wikipedia's participant role management + } +} +``` + +## Identified Challenges and Solutions + +### 1. Language Bias and Equity + +**Challenge:** Abstract Wikipedia faces criticism for Indo-European language bias. + +**Mitigation Strategy:** +- Prioritize support for diverse language families early in development +- Use Universal Dependencies for syntactic parsing across languages +- Implement cultural context awareness in semantic interpretation + +### 2. Community Understanding + +**Challenge:** Users confused about machine translation vs. structured generation. + +**Solution for Human-Language:** +- Clear documentation distinguishing Q/P transformation from translation +- Interactive demos showing semantic preservation across languages +- Educational materials explaining the "language of meaning" concept + +### 3. Technical Integration Complexity + +**Challenge:** Complex integration with existing systems and workflows. + +**Approach:** +- Start with simple, high-value use cases +- Build backwards-compatible APIs +- Provide clear migration paths from existing text processing + +## Specific Improvements for Human-Language + +### 1. Enhanced N-gram Processing + +```javascript +// Inspired by Abstract Wikipedia's modular approach +class AdvancedNgramProcessor { + constructor() { + this.processors = [ + new TemporalNgramProcessor(), + new SpatialNgramProcessor(), + new RelationalNgramProcessor() + ]; + } + + async processNgrams(ngrams) { + const results = await Promise.all( + this.processors.map(p => p.process(ngrams)) + ); + return this.mergeResults(results); + } +} +``` + +### 2. Semantic Validation System + +```javascript +// Quality assurance inspired by Wikifunctions testing +class SemanticValidator { + async validateTransformation(originalText, qpSequence) { + const checks = [ + this.validateEntityConsistency(qpSequence), + this.validatePropertyUsage(qpSequence), + this.validateSemanticCoherence(originalText, qpSequence) + ]; + + return Promise.all(checks); + } +} +``` + +### 3. Multi-Language Generation + +```javascript +// Generate human-readable text from Q/P sequences +class LanguageGenerator { + async generateFromQP(sequence, targetLanguage) { + const template = await this.getLanguageTemplate(targetLanguage); + const enrichedData = await this.enrichWithLabels(sequence, targetLanguage); + return this.renderTemplate(template, enrichedData); + } +} +``` + +## Implementation Roadmap + +### Phase 1: Foundation (Immediate) +- [ ] Implement modular function architecture +- [ ] Add lexeme-based disambiguation +- [ ] Create semantic validation framework + +### Phase 2: Enhancement (3-6 months) +- [ ] Context-aware transformation system +- [ ] Multi-language generation capabilities +- [ ] Advanced semantic structure parsing + +### Phase 3: Integration (6-12 months) +- [ ] Wikidata contribution pipeline +- [ ] Community validation system +- [ ] Performance optimization at scale + +## Performance Optimization Lessons + +### 1. Caching Strategy +Abstract Wikipedia's approach suggests: +- Cache function results aggressively +- Use incremental updates for dynamic data +- Implement multi-tier caching (browser + server) + +### 2. Batch Processing +```javascript +// Optimize API calls like Wikifunctions +class BatchProcessor { + async processBatch(items, batchSize = 50) { + const batches = this.chunk(items, batchSize); + return Promise.all(batches.map(batch => this.processItems(batch))); + } +} +``` + +## Risk Mitigation + +### Technical Risks +1. **Scalability**: Use proven caching and batch processing patterns +2. **Accuracy**: Implement comprehensive validation and testing +3. **Complexity**: Start simple and iterate based on user feedback + +### Community Risks +1. **Adoption**: Focus on clear value propositions and ease of use +2. **Understanding**: Invest in documentation and educational content +3. **Contribution**: Design systems for community validation and improvement + +## Conclusion + +Abstract Wikipedia's development provides a roadmap for semantic language projects. Key takeaways: + +1. **Start with strong foundations**: Modular architecture and semantic validation +2. **Think globally**: Design for multiple languages from the beginning +3. **Engage communities**: Clear communication and gradual integration +4. **Iterate carefully**: Build on proven patterns while innovating thoughtfully + +The human-language project is well-positioned to learn from Abstract Wikipedia's successes and challenges, potentially achieving faster development and better outcomes through these insights. + +## Recommended Next Steps + +1. Implement lexeme-based disambiguation in the current transformer +2. Add semantic validation to ensure transformation quality +3. Create educational content explaining the semantic transformation approach +4. Design community feedback mechanisms for continuous improvement +5. Plan for multi-language support architecture from the outset + +These improvements could significantly accelerate the project's development while avoiding pitfalls encountered by Abstract Wikipedia. \ No newline at end of file From e13c141dcf71844ebf4047f6714591aa723ccfda Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 9 Jun 2026 22:58:03 +0000 Subject: [PATCH 04/21] docs: Add fresh 2025-2026 Abstract Wikipedia research and feature-gap analysis - Update abstract-wikipedia-analysis.md with current (June 2026) status: Wikifunctions metrics, 2025 NLG semantic fragments, NLG SIG, published roadmap (late-2025 sentence functions, 2026 soft launch), the 6-stage NLG architecture, Ninai/Udiron, and the Google.org risk evaluation. Added Sources. - Add missing-features-and-improvements.md: feature-gap map (reverse Q/P->text generation, lexeme integration, typed constructors, UD parsing, multi-language rendering) and concrete per-service quality improvements grounded in repo files. --- research/abstract-wikipedia-analysis.md | 55 +++++++- research/missing-features-and-improvements.md | 125 ++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 research/missing-features-and-improvements.md diff --git a/research/abstract-wikipedia-analysis.md b/research/abstract-wikipedia-analysis.md index d2a166d..a2713e5 100644 --- a/research/abstract-wikipedia-analysis.md +++ b/research/abstract-wikipedia-analysis.md @@ -4,6 +4,46 @@ Abstract Wikipedia, launched with Wikifunctions in July 2023, offers valuable lessons for the human-language project. This analysis identifies key improvements and strategies that could accelerate development or enhance the semantic transformation approach. +> **Companion document:** For the concrete, actionable output of this analysis — a feature-gap map and per-service quality improvements — see [`missing-features-and-improvements.md`](./missing-features-and-improvements.md). + +## 2025–2026 Status Update (Fresh Research) + +*This section reflects research gathered in June 2026 and supersedes the older training-data-based notes below where they conflict. Full source links are listed in [Sources](#sources).* + +### Where the project stands now + +- **Wikifunctions** went live to the public on **26 July 2023** — the first new Wikimedia project since 2012. By 2025 contributors from **50+ countries** had created **2,400+ functions**, with names and descriptions in **100+ languages**. +- **2025 focus is the technical foundation for Natural Language Generation (NLG).** The team is building "**semantic fragments**" — small, reusable functions that turn Wikidata data plus a language code into a sentence (e.g. function `Z26039` renders *"Berlin is a city" / "Berlín es una ciudad" / "Flughafen Berlin Brandenburg ist ein Flughafen"*). Progress is tracked across the **"UN 6" languages**: English, Arabic, Spanish, French, Russian, and Chinese. +- A **Natural Language Generation Special Interest Group (NLG SIG)** now meets roughly monthly (4th meeting July 2025, 5th in Sept 2025, continuing into late 2025) to converge on how grammatical features are represented. + +### Published roadmap + +| Date | Milestone | +|------|-----------| +| Aug 2022 | Wikifunctions Beta | +| Jul 2023 | Wikifunctions in production | +| Apr 2025 | Embedded function-call results enabled on select wikis | +| **Late 2025** | First **live functions for generating simple sentences** in natural languages | +| **2026** | **Soft launch** of Abstract Wikipedia | + +### The NLG architecture (the part most relevant to us) + +Abstract Wikipedia separates **language-independent meaning** from **language-specific rendering** — the same goal as this project's Q/P sequences, but with a fuller pipeline: + +1. **Constructors** — language-agnostic containers of arguments (e.g. `Existence(subject)`); they carry no logic and are community-extensible. Roughly analogous to our Q/P sequence, but **typed and role-labelled** rather than a flat list. +2. **Renderers** — one **templatic renderer per constructor-per-language**, written by the community. Templates mix static text with slots filled by arguments, Wikidata **lexemes**, or other renderers' output. +3. **Syntactic tree** — renderers emit **Universal Dependencies** (or Surface-Syntactic UD) trees of *non-inflected lemmas* with morphological constraints. +4. **Morphological inflection** — grammar specifications inflect lemmas using **Wikidata lexeme** forms / inflection tables; dependency relations use **feature unification** (e.g. a subject relation unifies noun–verb number and person agreement). +5. **Phonotactics** — language-specific sandhi (English *a/an*, French *de + le → du*). +6. **Text assembler** — final spacing, capitalization, and punctuation. + +The reference implementation pair is **Ninai** (high-level constructors and item→sense resolution) and **Udiron** (low-level, per-language text manipulation), with grammar logic written in **Lua**. + +### Documented risks and criticism (lessons to internalize) + +- A **January 2023 Google.org Fellows evaluation** rated the project at **"substantial risk of failure"** for thin technical planning, and recommended (a) decoupling Abstract Wikipedia from Wikifunctions, (b) refining **Lua** rather than inventing new languages, and (c) converging on an existing NLG framework. The Wikimedia Foundation **rejected** these recommendations. +- WMF declined existing frameworks such as **Grammatical Framework**, arguing they under-serve **Niger-Congo B** languages and risk replicating an "English-focused Western-thinking" bias. The trade-off chosen — community-editable templates that **prioritize accessibility over guaranteed grammaticality** — means poorly designed templates *can* emit ungrammatical text. This is a direct, real-world data point on the equity-vs-correctness tension we also face. + ## Abstract Wikipedia Project Overview ### Core Architecture @@ -265,4 +305,17 @@ The human-language project is well-positioned to learn from Abstract Wikipedia's 4. Design community feedback mechanisms for continuous improvement 5. Plan for multi-language support architecture from the outset -These improvements could significantly accelerate the project's development while avoiding pitfalls encountered by Abstract Wikipedia. \ No newline at end of file +These improvements could significantly accelerate the project's development while avoiding pitfalls encountered by Abstract Wikipedia. + +## Sources + +Fresh research gathered June 2026: + +- [Abstract Wikipedia — Meta-Wiki](https://meta.wikimedia.org/wiki/Abstract_Wikipedia) (roadmap, timeline, project goals) +- [Abstract Wikipedia — Wikipedia](https://en.wikipedia.org/wiki/Abstract_Wikipedia) (launch, architecture, criticism) +- [Natural language generation system architecture proposal — Meta-Wiki](https://meta.wikimedia.org/wiki/Abstract_Wikipedia/Natural_language_generation_system_architecture_proposal) (6-stage NLG pipeline) +- [Wikifunctions:Abstract Wikipedia/2025 fragment experiments](https://www.wikifunctions.org/wiki/Wikifunctions:Abstract_Wikipedia/2025_fragment_experiments) (semantic fragments, `Z26039`, UN 6 languages) +- [Wikifunctions:NLG SIG](https://www.wikifunctions.org/wiki/Wikifunctions:NLG_SIG) (Natural Language Generation Special Interest Group) +- [Using Wikidata Lexemes and Items to Generate Text from Abstract Representations — Mahir Morshed, 2024 (Semantic Web Journal)](https://content.iospress.com/articles/semantic-web/sw243564) (Ninai/Udiron, constructors, renderers) +- [Abstract Wikipedia/Google.org Fellows evaluation — Meta-Wiki](https://meta.wikimedia.org/wiki/Abstract_Wikipedia/Google.org_Fellows_evaluation) (risk-of-failure assessment) +- [Wikifunctions status updates (2025)](https://www.wikifunctions.org/wiki/Wikifunctions:Status_updates/2025-06-21) \ No newline at end of file diff --git a/research/missing-features-and-improvements.md b/research/missing-features-and-improvements.md new file mode 100644 index 0000000..2ccdd35 --- /dev/null +++ b/research/missing-features-and-improvements.md @@ -0,0 +1,125 @@ +# Missing Features & Service-Quality Improvements + +*Derived from fresh (June 2026) research on Abstract Wikipedia / Wikifunctions — see +[`abstract-wikipedia-analysis.md`](./abstract-wikipedia-analysis.md) for the full background and +[Sources](./abstract-wikipedia-analysis.md#sources).* + +This document answers two practical questions: + +1. **What features does Abstract Wikipedia have that we are missing — and could support?** +2. **How can we improve the quality of each of our existing services?** + +It is intentionally concrete and mapped to files in this repository. + +--- + +## Part 1 — Feature gaps we can close + +Abstract Wikipedia and the human-language project share the same core idea: separate +**language-independent meaning** from **language-specific text**. The difference is direction and +depth. We currently do **text → Q/P** (analysis only); Abstract Wikipedia invests most heavily in +**meaning → text** (generation), with a typed, grammar-aware pipeline. The biggest opportunities sit +in the parts of their pipeline we have no equivalent for. + +| # | Missing feature | What Abstract Wikipedia does | Where it would live here | Priority | +|---|-----------------|------------------------------|--------------------------|----------| +| 1 | **Reverse generation (Q/P → text)** | Renderers turn a constructor into a sentence in any language (`Z26039` → "Berlin is a city" / "Berlín es una ciudad") | New `generation/qp-to-text.js` service | **High** | +| 2 | **Wikidata Lexeme integration** | Lexemes drive disambiguation and morphological inflection | `wikidata-api.js` (`searchLexemes`), used by transformer + search | **High** | +| 3 | **Typed, role-labelled representation** | Constructors are typed containers with named argument roles, not flat lists | Output schema of `text-to-qp-transformer.js` | **High** | +| 4 | **Universal Dependencies parsing** | Renderers emit/consume UD (or Surface-Syntactic UD) trees | New parsing layer feeding the transformer | Medium | +| 5 | **Multi-language rendering (UN 6)** | Same meaning rendered in English, Arabic, Spanish, French, Russian, Chinese | Generation service templates | Medium | +| 6 | **Modular function composition** | Wikifunctions composes small reusable functions; one templatic renderer per constructor-per-language | `SemanticFunctionLibrary` (sketched in analysis doc) | Medium | +| 7 | **Grammatical features + phonotactics** | Feature unification for agreement; sandhi rules (English *a/an*, French *de+le→du*) | Generation service | Medium | +| 8 | **Embedded live function results** | Function-call results embedded in wiki pages (since Apr 2025) | Demo pages (`entities.html`, etc.) | Low | +| 9 | **Community validation process** | NLG SIG + community-editable templates/grammar | Contribution docs + validation hooks | Low | + +### The single highest-leverage gap: reverse generation + +We can already parse text into Q/P. We cannot yet render Q/P back into a human sentence in any +language — which is exactly the capability the README's vision ("Zero-Cost Translation", "Language of +Meaning", "LLM Translation Pipeline") depends on. A minimal renderer that closes the loop, mirroring +Abstract Wikipedia's `Z26039` "is-a" fragment: + +```javascript +// generation/qp-to-text.js (sketch) +// Constructor: { type: 'instance_of', subject: 'Q64', object: 'Q515' } // Berlin, city +// Renders: "Berlin is a city" | "Berlín es una ciudad" | "Берлин — город" +class QPRenderer { + async render(constructor, lang = 'en') { + const template = TEMPLATES[constructor.type]?.[lang]; + if (!template) throw new Error(`No renderer for ${constructor.type}/${lang}`); + const labels = await this.api.getLabels( + [constructor.subject, constructor.object], lang + ); + return this.applyPhonotactics(this.fill(template, labels, constructor), lang); + } +} +``` + +Starting with one constructor (`instance_of`) across the **UN 6** languages is a small, demonstrable +deliverable that proves the round-trip `text → Q/P → text` and directly de-risks the README roadmap. + +--- + +## Part 2 — Per-service quality improvements + +### 1. Text-to-Q/P Transformer (`transformation/text-to-qp-transformer.js`) + +The transformer currently emits a **flat list** of Q/P ids and skips meaning-bearing words. The +documented limitations in `limitations-found.json` (12/31 cases failing) cluster exactly where +Abstract Wikipedia invests: + +- **Negation** ("Einstein did *not* discover gravity" → loses the *not*). Abstract Wikipedia models + this structurally via constructors; we should attach a `negated: true` flag to the relation rather + than dropping the token. Today `stopWords`/`propertyIndicators` (lines 30–40) silently discard such + words. +- **Questions / tenses / pronouns** — also failing. A typed representation (gap #3) with a tense and + modality slot is the structural fix, not more keyword lists. +- **Lexeme-based disambiguation (gap #2)** would replace the hand-maintained `propertyIndicators` + array with morphological lookup, handling inflected forms ("wrote" → P50/author) generically. + +**Quality win:** change the output from `["Q937", "P19", "Q183"]` to a typed constructor with roles, +preserving negation/tense — turning today's silent failures into representable structure. + +### 2. Search & Disambiguation (`wikidata-api.js`, `SEARCH_README.md`) + +- Add **`searchLexemes(term, lang)`** alongside entity search and merge/rank results (Abstract + Wikipedia treats lexemes as first-class). The codebase only touches lexemes as a claim *datatype* + (`wikidata-api.js:424`) — there is no lexeme *search*. +- Use lexeme forms to **normalize morphology before matching**, improving fuzzy search for inflected + inputs. + +### 3. Entity & Property Viewer (`entities.html`, `properties.html`, `statements.jsx`) + +- Surface **lexeme forms and senses** for an entity, and show an **auto-generated example sentence** + (using the new renderer) so users see meaning rendered, not just ids. +- Adopt Abstract Wikipedia's terminology hint: render **per-language** example output for the UN 6 set. + +### 4. Caching System (`unified-cache.js`, `persistent-cache.js`) + +- Abstract Wikipedia caches function results aggressively. Add **HTTP `ETag` / conditional-request** + support and **incremental invalidation** so cached labels/lexemes refresh without full re-fetch. +- Add **batch fetching** (`getLabels([...])`) to cut round-trips for multi-entity renders — required + by the generation service anyway. + +### 5. New service — Multi-language Generation (`generation/`) + +This is the missing counterpart to the transformer (gap #1). It should: + +- Live in a new `generation/` folder mirroring `transformation/` (templates + tests + demo HTML). +- Start with the `instance_of` constructor across the UN 6 languages. +- Reuse the cache and the new batch label fetch. +- Include phonotactic post-processing (English *a/an*) as the first grammatical feature. + +--- + +## Suggested sequencing + +1. **Add `searchLexemes` + batch `getLabels`** (foundation, low risk, helps every service). +2. **Switch transformer output to a typed constructor** preserving negation/tense (fixes documented limitations). +3. **Ship a minimal `generation/qp-to-text.js`** for `instance_of` across UN 6 (proves the round-trip). +4. **Surface example sentences** in the entity/property viewers. +5. **Layer in caching (ETag/incremental) and UD parsing** as the corpus grows. + +Each step is independently shippable and testable, following Abstract Wikipedia's own lesson: +**start with simple, high-value fragments and grow the grammar incrementally.** From 5d434f3888f2fca22c2dfdc57d446970d2f2e2b9 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Jun 2026 00:18:11 +0000 Subject: [PATCH 05/21] feat(wikidata): add batch getLabels and searchLexemes to the API client Batch label resolution (getLabels) feeds the new generation renderer; searchLexemes adds Wikidata Lexeme lookup (Abstract-Wikipedia gap #2). Mirrored in both the Node and browser clients. --- js/src/wikidata-api-browser.js | 66 ++++++++++++++++++++++++++++++++ js/src/wikidata-api.js | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/js/src/wikidata-api-browser.js b/js/src/wikidata-api-browser.js index c5718f9..b4bc6cf 100644 --- a/js/src/wikidata-api-browser.js +++ b/js/src/wikidata-api-browser.js @@ -95,6 +95,72 @@ class WikidataAPIClient { return data.entities || {}; } + /** + * Fetch entity/property labels in batch and flatten them into a simple + * `{ id: label }` map for the requested language — the round-trip + * counterpart used by the multi-language generation service. + * + * @param {Array|string} ids - One or more Q/P ids + * @param {string} language - A single language code (e.g. 'en', 'es') + * @returns {Promise} - Map of `{ id: label }`; ids without a label + * in the requested language fall back to the id + */ + async getLabels(ids, language = 'en') { + const idList = Array.isArray(ids) ? ids : [ids]; + const unique = [...new Set(idList.filter(Boolean))]; + const out = {}; + if (unique.length === 0) { + return out; + } + + const entities = await this.fetchLabels(unique, language); + for (const id of unique) { + const entity = entities[id]; + const label = entity && entity.labels && entity.labels[language] + ? entity.labels[language].value + : null; + out[id] = label || id; + } + return out; + } + + /** + * Search Wikidata Lexemes (L-ids) by a search term — the lexeme-search + * counterpart of entity/property search, used for morphology-aware + * disambiguation and generation. + * + * @param {string} term - Search term (a word/lemma) + * @param {string} language - Language to search in (default: 'en') + * @param {number} limit - Maximum number of results (default: 10) + * @returns {Promise} - Array of `{ id, label, description }` lexeme matches + */ + async searchLexemes(term, language = 'en', limit = 10) { + if (!term) { + return []; + } + + const url = this.buildApiUrl({ + action: 'wbsearchentities', + search: term, + language: language, + type: 'lexeme', + limit: limit, + format: 'json' + }); + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return (data.search || []).map((item) => ({ + id: item.id, + label: item.label, + description: item.description || '' + })); + } + /** * Fetch property data * @param {string} propertyId - Property ID (e.g., 'P31') diff --git a/js/src/wikidata-api.js b/js/src/wikidata-api.js index 8d0fb24..eacaad8 100644 --- a/js/src/wikidata-api.js +++ b/js/src/wikidata-api.js @@ -96,6 +96,76 @@ class WikidataAPIClient { return data.entities || {}; } + /** + * Fetch entity/property labels in batch and flatten them into a simple + * `{ id: label }` map for the requested language. This is the round-trip + * counterpart used by the multi-language generation service, which needs + * to turn a list of Q/P ids into human-readable words with a single API + * round-trip rather than one request per id. + * + * @param {Array|string} ids - One or more Q/P ids + * @param {string} language - A single language code (e.g. 'en', 'es') + * @returns {Promise} - Map of `{ id: label }`; ids without a label + * in the requested language fall back to the id + */ + async getLabels(ids, language = 'en') { + const idList = Array.isArray(ids) ? ids : [ids]; + const unique = [...new Set(idList.filter(Boolean))]; + const out = {}; + if (unique.length === 0) { + return out; + } + + const entities = await this.fetchLabels(unique, language); + for (const id of unique) { + const entity = entities[id]; + const label = entity && entity.labels && entity.labels[language] + ? entity.labels[language].value + : null; + out[id] = label || id; + } + return out; + } + + /** + * Search Wikidata Lexemes (L-ids) by a search term. Lexemes are + * first-class lexical units in Wikidata — distinct from items (Q) and + * properties (P) — and carry the morphological forms and senses that the + * generation service and morphology-aware search rely on. The existing + * search only covers items and properties; this closes that gap. + * + * @param {string} term - Search term (a word/lemma) + * @param {string} language - Language to search in (default: 'en') + * @param {number} limit - Maximum number of results (default: 10) + * @returns {Promise} - Array of `{ id, label, description }` lexeme matches + */ + async searchLexemes(term, language = 'en', limit = 10) { + if (!term) { + return []; + } + + const url = this.buildApiUrl({ + action: 'wbsearchentities', + search: term, + language: language, + type: 'lexeme', + limit: limit, + format: 'json' + }); + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return (data.search || []).map((item) => ({ + id: item.id, + label: item.label, + description: item.description || '' + })); + } + /** * Fetch property data * @param {string} propertyId - Property ID (e.g., 'P31') From bfd32cc778311f13a62c86cc92ea23c5ff6c87b3 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Jun 2026 00:18:11 +0000 Subject: [PATCH 06/21] feat(generation): add Q/P -> text reverse renderer (Abstract Wikipedia) Typed constructors with named roles + one templatic renderer per constructor-per-language across the six official UN languages, plus English a/an phonotactics and negation. Pure, offline-testable data layer (constructors.js) with a thin orchestration renderer (QPRenderer). --- js/src/generation/constructors.js | 185 ++++++++++++++++++++++++++++++ js/src/generation/qp-to-text.js | 136 ++++++++++++++++++++++ 2 files changed, 321 insertions(+) create mode 100644 js/src/generation/constructors.js create mode 100644 js/src/generation/qp-to-text.js diff --git a/js/src/generation/constructors.js b/js/src/generation/constructors.js new file mode 100644 index 0000000..942b7ce --- /dev/null +++ b/js/src/generation/constructors.js @@ -0,0 +1,185 @@ +// Typed constructors and multi-language templates for the reverse +// generation service (Q/P → text). +// +// This is the data layer that mirrors Abstract Wikipedia's "constructors" +// (typed containers with named argument roles) and the per-constructor, +// per-language "templatic renderers" that turn one constructor into a +// sentence. We deliberately start small — a handful of high-value +// constructors across the six official UN languages — following Abstract +// Wikipedia's own lesson: start with simple, high-value fragments and grow +// the grammar incrementally. +// +// Everything here is pure and dependency-free so it can be unit-tested +// offline (no Wikidata round-trip) and reused in both Node and the browser. + +/** + * The six official languages of the United Nations — the same demonstration + * set Abstract Wikipedia uses for its first natural-language-generation + * "semantic fragments". + */ +export const UN6_LANGUAGES = ['en', 'ar', 'es', 'fr', 'ru', 'zh']; + +/** Human-readable names for the supported languages. */ +export const LANGUAGE_NAMES = { + en: 'English', + ar: 'Arabic', + es: 'Spanish', + fr: 'French', + ru: 'Russian', + zh: 'Chinese', +}; + +/** + * English indefinite-article phonotactics (a / an). This is the first + * grammatical feature of the renderer. It is a documented heuristic — it + * keys off the first letter and so does not yet handle silent-h ("an hour") + * or vowel-letter/consonant-sound words ("a university"). Abstract + * Wikipedia models the full phonological rule set; this is the seed. + * + * @param {string} word - The word the article precedes + * @returns {string} - 'a' or 'an' + */ +export function englishIndefiniteArticle(word) { + if (!word) return 'a'; + const first = word.trim().charAt(0).toLowerCase(); + return 'aeiou'.includes(first) ? 'an' : 'a'; +} + +/** + * Constructor catalogue. Each constructor is a typed container with named + * argument roles and one templatic renderer per supported language. + * + * Template placeholders: + * {subject} {predicate} {object} - replaced by the role's resolved label + * {article} - replaced by the language's indefinite + * article (English a/an phonotactics) + * + * A template provides a `positive` pattern and, where the language differs, + * a `negative` pattern used when the constructor carries `negated: true`. + */ +export const CONSTRUCTORS = { + // X is an instance of Y — Wikidata P31. Mirrors Abstract Wikipedia's + // Z26039 "Berlin is a city" semantic fragment. + instance_of: { + roles: ['subject', 'object'], + description: 'X is an instance of Y (Wikidata P31)', + templates: { + en: { positive: '{subject} is {article} {object}', negative: '{subject} is not {article} {object}', article: 'en-indefinite' }, + es: { positive: '{subject} es un {object}', negative: '{subject} no es un {object}' }, + fr: { positive: '{subject} est un {object}', negative: "{subject} n'est pas un {object}" }, + ru: { positive: '{subject} — {object}', negative: '{subject} — не {object}' }, + zh: { positive: '{subject}是{object}', negative: '{subject}不是{object}' }, + ar: { positive: '{subject} {object}', negative: '{subject} ليس {object}' }, + }, + }, + + // X is located in Y — Wikidata P131 / P276. + located_in: { + roles: ['subject', 'object'], + description: 'X is located in Y (Wikidata P131/P276)', + templates: { + en: { positive: '{subject} is in {object}', negative: '{subject} is not in {object}' }, + es: { positive: '{subject} está en {object}', negative: '{subject} no está en {object}' }, + fr: { positive: '{subject} est en {object}', negative: "{subject} n'est pas en {object}" }, + ru: { positive: '{subject} находится в {object}', negative: '{subject} не находится в {object}' }, + zh: { positive: '{subject}在{object}', negative: '{subject}不在{object}' }, + ar: { positive: '{subject} في {object}', negative: '{subject} ليس في {object}' }, + }, + }, + + // Generic fallback: subject — predicate — object, where the predicate is + // itself a (property) label. This lets the round-trip render *any* + // text → Q/P result, not only the two specialised constructors above. + relation: { + roles: ['subject', 'predicate', 'object'], + description: 'Generic subject–predicate–object relation', + templates: { + en: { positive: '{subject} {predicate} {object}', negative: '{subject} does not {predicate} {object}' }, + es: { positive: '{subject} {predicate} {object}', negative: '{subject} no {predicate} {object}' }, + fr: { positive: '{subject} {predicate} {object}', negative: '{subject} ne {predicate} pas {object}' }, + ru: { positive: '{subject} {predicate} {object}', negative: '{subject} не {predicate} {object}' }, + zh: { positive: '{subject}{predicate}{object}', negative: '{subject}不{predicate}{object}' }, + ar: { positive: '{subject} {predicate} {object}', negative: '{subject} لا {predicate} {object}' }, + }, + }, +}; + +/** + * Build a typed constructor object. + * + * @param {string} type - A key of CONSTRUCTORS (e.g. 'instance_of') + * @param {Object} roles - Role → value map, e.g. { subject: 'Q64', object: 'Q515' } + * @param {Object} [modifiers] - Optional flags, e.g. { negated: true, tense: 'past' } + * @returns {Object} - The constructor + */ +export function buildConstructor(type, roles = {}, modifiers = {}) { + return { type, ...roles, ...modifiers }; +} + +/** + * Validate a constructor against the catalogue. + * + * @param {Object} constructor + * @throws {Error} if the type is unknown or a required role is missing + */ +export function validateConstructor(constructor) { + if (!constructor || typeof constructor !== 'object') { + throw new Error('Constructor must be an object'); + } + const spec = CONSTRUCTORS[constructor.type]; + if (!spec) { + const known = Object.keys(CONSTRUCTORS).join(', '); + throw new Error(`Unknown constructor type "${constructor.type}" (known: ${known})`); + } + for (const role of spec.roles) { + if (constructor[role] == null || constructor[role] === '') { + throw new Error(`Constructor "${constructor.type}" is missing role "${role}"`); + } + } + return true; +} + +/** + * Fill a single language template from a constructor and a `{ id: label }` + * map. Pure and synchronous — the async `QPRenderer.render` is a thin + * wrapper that resolves labels first, then calls this. + * + * @param {Object} spec - The constructor spec (with `roles`) + * @param {Object} template - The per-language template (positive/negative/article) + * @param {Object} constructor - The constructor instance + * @param {Object} labels - Map of `{ id: label }` + * @returns {string} + */ +export function fillTemplate(spec, template, constructor, labels) { + const pattern = constructor.negated + ? (template.negative || template.positive) + : template.positive; + + let out = pattern; + for (const role of spec.roles) { + const value = constructor[role]; + const label = (labels && labels[value]) || value || ''; + out = out.split(`{${role}}`).join(label); + } + + if (out.includes('{article}')) { + const objectLabel = (labels && labels[constructor.object]) || constructor.object || ''; + const article = template.article === 'en-indefinite' + ? englishIndefiniteArticle(objectLabel) + : ''; + out = out.split('{article}').join(article); + } + + // Collapse any double spaces an empty article may have left behind. + return out.replace(/\s{2,}/g, ' ').trim(); +} + +export default { + UN6_LANGUAGES, + LANGUAGE_NAMES, + CONSTRUCTORS, + englishIndefiniteArticle, + buildConstructor, + validateConstructor, + fillTemplate, +}; diff --git a/js/src/generation/qp-to-text.js b/js/src/generation/qp-to-text.js new file mode 100644 index 0000000..0321be3 --- /dev/null +++ b/js/src/generation/qp-to-text.js @@ -0,0 +1,136 @@ +// Q/P → Text renderer (reverse generation). +// +// This is the missing counterpart of the Text → Q/P transformer. The +// transformer does *analysis* (text → meaning); this does *generation* +// (meaning → text), closing the round-trip the README vision depends on +// ("Zero-Cost Translation", "Language of Meaning"). It mirrors Abstract +// Wikipedia's renderers: a typed constructor goes in, a sentence in any of +// the six official UN languages comes out. +// +// The renderer is deliberately small and grammar-light — one templatic +// renderer per constructor-per-language, plus English a/an phonotactics as +// the first grammatical feature. It grows by adding constructors and +// templates, exactly the way Abstract Wikipedia grew its grammar. + +import { + CONSTRUCTORS, + UN6_LANGUAGES, + LANGUAGE_NAMES, + validateConstructor, + fillTemplate, +} from './constructors.js'; + +// Pick the right Wikidata API at runtime: the browser version avoids Node's +// `fs`/`path` imports that the file-cache backend pulls in. Mirrors the +// runtime selection in `transformation/text-to-qp-transformer.js`. +let WikidataAPIClient; +async function loadApiClient() { + if (WikidataAPIClient) return WikidataAPIClient; + if (typeof window !== 'undefined') { + ({ WikidataAPIClient } = await import('../wikidata-api-browser.js')); + } else { + ({ WikidataAPIClient } = await import('../wikidata-api.js')); + } + return WikidataAPIClient; +} + +/** + * Renders typed Q/P constructors into natural-language sentences. + */ +class QPRenderer { + /** + * @param {Object} [options] + * @param {Function} [options.labelProvider] - async `(ids, lang) => { id: label }`. + * When supplied, the renderer never touches the network — ideal for + * tests and offline use. + * @param {Object} [options.apiClient] - A pre-built Wikidata client with a + * `getLabels(ids, lang)` method. Defaults to lazily constructing one. + */ + constructor(options = {}) { + this.labelProvider = options.labelProvider || null; + this.apiClient = options.apiClient || null; + } + + /** Languages this renderer can currently target. */ + get languages() { + return [...UN6_LANGUAGES]; + } + + /** Constructor types this renderer knows how to render. */ + get constructorTypes() { + return Object.keys(CONSTRUCTORS); + } + + async _client() { + if (this.apiClient) return this.apiClient; + const Client = await loadApiClient(); + this.apiClient = new Client(); + return this.apiClient; + } + + async _resolveLabels(ids, lang) { + if (this.labelProvider) { + return await this.labelProvider(ids, lang); + } + const client = await this._client(); + return await client.getLabels(ids, lang); + } + + /** + * Render a constructor into text from an already-resolved `{ id: label }` + * map. Pure and synchronous — no network. Use this when you have labels + * in hand (e.g. tests, batch pipelines). + * + * @param {Object} constructor - Typed constructor + * @param {Object} labels - Map of `{ id: label }` + * @param {string} [lang='en'] - Target language code + * @returns {string} + */ + renderWithLabels(constructor, labels, lang = 'en') { + validateConstructor(constructor); + const spec = CONSTRUCTORS[constructor.type]; + const template = spec.templates[lang]; + if (!template) { + throw new Error(`No "${lang}" renderer for constructor "${constructor.type}"`); + } + return fillTemplate(spec, template, constructor, labels || {}); + } + + /** + * Render a constructor into text in one language, resolving labels via + * the label provider / Wikidata client as needed. + * + * @param {Object} constructor - Typed constructor + * @param {string} [lang='en'] - Target language code + * @returns {Promise} + */ + async render(constructor, lang = 'en') { + validateConstructor(constructor); + const spec = CONSTRUCTORS[constructor.type]; + const ids = spec.roles + .map((role) => constructor[role]) + .filter((value) => typeof value === 'string' && /^[QPL]\d+$/.test(value)); + const labels = ids.length ? await this._resolveLabels(ids, lang) : {}; + return this.renderWithLabels(constructor, labels, lang); + } + + /** + * Render a constructor across multiple languages. Labels are fetched + * per-language (Wikidata labels differ by language), which is exactly the + * multi-language render Abstract Wikipedia demonstrates with the UN 6 set. + * + * @param {Object} constructor - Typed constructor + * @param {Array} [langs] - Languages to render (default: UN 6) + * @returns {Promise} - Map of `{ lang: sentence }` + */ + async renderAll(constructor, langs = UN6_LANGUAGES) { + const out = {}; + for (const lang of langs) { + out[lang] = await this.render(constructor, lang); + } + return out; + } +} + +export { QPRenderer, UN6_LANGUAGES, LANGUAGE_NAMES, CONSTRUCTORS }; +export default QPRenderer; From 24033522b255af6c64c7c871a6c917b242d98721 Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Jun 2026 00:18:11 +0000 Subject: [PATCH 07/21] feat(transformer): emit typed constructors with negation and tense transformToConstructor folds the flat Q/P sequence into a typed constructor and extracts negation/tense modifiers, enabling the text -> Q/P -> text round-trip. --- .../transformation/text-to-qp-transformer.js | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/js/src/transformation/text-to-qp-transformer.js b/js/src/transformation/text-to-qp-transformer.js index f836f2b..a7e0ae3 100644 --- a/js/src/transformation/text-to-qp-transformer.js +++ b/js/src/transformation/text-to-qp-transformer.js @@ -2,6 +2,8 @@ // Transforms English text into sequences of Wikidata entities (Q) and properties (P) // with disambiguation support using [Q1 or Q2 or Q3] syntax +import { buildConstructor } from '../generation/constructors.js'; + // Pick the right Wikidata API at runtime: the browser version avoids Node's // `fs`/`path` imports that the file-cache backend pulls in. let WikidataAPIClient, WikidataSearchUtility; @@ -37,6 +39,26 @@ class TextToQPTransformer { // Words to skip entirely this.stopWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by']; + + // Tense markers. Today the flat-list output silently drops these (see + // limitations-found.json), so "discovered" and "discovers" collapse to + // the same sequence. The typed constructor keeps them as a `tense` slot. + this.pastTenseWords = ['was', 'were', 'had', 'did']; + this.futureTenseWords = ['will', 'shall']; + + // A small set of common irregular past-tense verbs the regular `-ed` + // rule cannot catch. Not exhaustive — a documented seed, the same way + // Abstract Wikipedia grows its lexicon incrementally. + this.irregularPastWords = [ + 'wrote', 'built', 'made', 'found', 'bought', 'taught', 'sought', + 'brought', 'came', 'ran', 'went', 'saw', 'took', 'gave', 'led', + 'held', 'drew', 'became', 'won', 'lost', 'met', 'kept', 'sent', + 'spent', 'told', 'sold', 'paid', 'said', 'began', 'wore', 'knew', + ]; + + // `instance of` indicators that map a generic relation onto the + // specialised `instance_of` constructor the renderer knows about. + this.instanceOfWords = ['is', 'was', 'are', 'were', 'instance of']; } /** @@ -409,6 +431,115 @@ class TextToQPTransformer { return alternatives; } + /** + * Extract structural modifiers (negation, tense) from the input text. + * + * The flat Q/P list drops meaning-bearing words like "not" and tense + * markers — exactly the cluster of failures documented in + * `limitations-found.json`. Abstract Wikipedia keeps these structurally + * (in the constructor), so we capture them as explicit flags rather than + * discarding the tokens. + * + * @param {string} text - The original input text + * @returns {{negated: boolean, tense: 'past'|'present'|'future'}} + */ + extractModifiers(text) { + const lower = ` ${(text || '').toLowerCase()} `; + const tokens = this.tokenize(text || '').map((t) => t.toLowerCase()); + + // Negation: "not", contracted "n't", or "never". + const negated = /\bnot\b|n't|\bnever\b/.test(lower); + + // Tense: explicit markers first, then a regular `-ed` past-tense fallback. + let tense = 'present'; + if (tokens.some((t) => this.futureTenseWords.includes(t))) { + tense = 'future'; + } else if ( + tokens.some((t) => this.pastTenseWords.includes(t)) || + tokens.some((t) => this.irregularPastWords.includes(t)) || + tokens.some((t) => /[a-z]{3,}ed$/.test(t)) + ) { + tense = 'past'; + } + + return { negated, tense }; + } + + /** + * Convert a transform() result into a typed, role-labelled constructor — + * the representation the multi-language generation service consumes. This + * is the bridge that makes the round-trip `text → Q/P → text` possible. + * + * It reads the sequence left-to-right, taking the first entity (Q) as the + * subject, the first property (P) as the predicate, and the next entity + * (Q) as the object. A `P31` predicate (or an `is/was` indicator) maps to + * the specialised `instance_of` constructor; anything else becomes the + * generic `relation` constructor. Negation and tense are preserved as + * modifier flags. + * + * @param {Object} result - The return value of `transform` + * @returns {Object|null} - A typed constructor, or null if no subject was found + */ + toConstructor(result) { + if (!result || !Array.isArray(result.sequence)) return null; + + // Collapse the sequence to its primary ids (first candidate of any + // ambiguous match), keeping the textual order. + const items = result.sequence + .filter(Boolean) + .map((item) => { + if (item.type === 'ambiguous' && item.alternatives && item.alternatives.length) { + return { id: item.alternatives[0].id }; + } + return { id: item.id }; + }) + .filter((item) => /^[QP]\d+$/.test(item.id)); + + let subject = null; + let predicate = null; + let object = null; + for (const { id } of items) { + if (id.startsWith('Q')) { + if (subject == null) subject = id; + else if (object == null) object = id; + } else if (id.startsWith('P') && predicate == null) { + predicate = id; + } + } + + if (subject == null) return null; + + const { negated, tense } = this.extractModifiers(result.original || ''); + + const lower = (result.original || '').toLowerCase(); + const isInstanceOf = predicate === 'P31' + || this.instanceOfWords.some((w) => new RegExp(`\\b${w}\\b`).test(lower)); + + if (isInstanceOf && object != null) { + return buildConstructor('instance_of', { subject, object }, { negated, tense }); + } + if (predicate != null && object != null) { + return buildConstructor('relation', { subject, predicate, object }, { negated, tense }); + } + // Not enough structure for a full relation — still surface what we have. + return buildConstructor('relation', { subject, predicate: predicate || null, object: object || null }, { negated, tense }); + } + + /** + * Transform text and additionally return the typed constructor. + * + * @param {string} text - Text to transform + * @param {Object} options - Transformation options + * @returns {Promise} - The transform() result with a `constructor` + * field and a `modifiers` field added. + */ + async transformToConstructor(text, options = {}) { + const result = await this.transform(text, options); + result.modifiers = this.extractModifiers(text); + result.constructor = this.toConstructor(result); + return result; + } + /** * Transform with context for better disambiguation * @param {string} text - Text to transform From 99fe6f3f7c0051c9cb81aba5c19c816913f629bd Mon Sep 17 00:00:00 2001 From: konard Date: Wed, 10 Jun 2026 00:18:18 +0000 Subject: [PATCH 08/21] feat(app): add Generation SPA mode and redirect shell Wires the new QPRenderer into the unified SPA as a 'generation' mode (routing + shell tab + app.html module exposure), adds the generation/index.html legacy redirect shell, and a landing-page card. --- app.html | 10 ++ generation/index.html | 34 +++++ index.html | 8 +- js/src/app/modes/generation.jsx | 249 ++++++++++++++++++++++++++++++++ js/src/app/routing.js | 1 + js/src/app/shell.jsx | 1 + 6 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 generation/index.html create mode 100644 js/src/app/modes/generation.jsx diff --git a/app.html b/app.html index 05f6ede..0d2383a 100644 --- a/app.html +++ b/app.html @@ -40,6 +40,9 @@ import * as ipa from './js/src/app/ipa.js'; import { TextToQPTransformer } from './js/src/transformation/text-to-qp-transformer.js'; import { TextTransformerTest, demonstrateTransformer } from './js/src/transformation/text-transformer-test.js'; + import { QPRenderer } from './js/src/generation/qp-to-text.js'; + import { CONSTRUCTORS, UN6_LANGUAGES, LANGUAGE_NAMES, buildConstructor, validateConstructor } + from './js/src/generation/constructors.js'; // Shared globals expected by the unified shell and legacy components // (statements.jsx, loading.jsx) that we re-mount inside the SPA. @@ -70,6 +73,12 @@ TextToQPTransformer, TextTransformerTest, demonstrateTransformer, + QPRenderer, + CONSTRUCTORS, + UN6_LANGUAGES, + LANGUAGE_NAMES, + buildConstructor, + validateConstructor, modes: {}, }; @@ -88,6 +97,7 @@ +