diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 01d89a0..2150fe3 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,7 +21,7 @@ concurrency: cancel-in-progress: false env: - EDITOR_MANIFEST_SHA256: 3e0510de27b93c5aed5018058b55bd82798ad7ed535fd1c25812aef5a0aadc03 + EDITOR_MANIFEST_SHA256: e972d1c779cca709ff1a1c8e0b0c543b855d82d7e6fa26fbf14414f84c6fb1d0 ZOLA_VERSION: "0.22.1" CF_PROJECT: terraphim-ai OP_API_TOKEN: op://TerraphimPlatform/terraphim-md-book-cloudflare/workers-api-token diff --git a/content/capabilities/terraphim-editor.md b/content/capabilities/terraphim-editor.md index b73668a..65f9408 100644 --- a/content/capabilities/terraphim-editor.md +++ b/content/capabilities/terraphim-editor.md @@ -22,7 +22,7 @@ Terraphim Editor brings knowledge-graph alternative suggestions into your writin ## Keep your decisions and alternatives -Attach alternatives to words, sentences and paragraphs. Save an annotated Markdown document and reopen it with those choices preserved. Newly authored alternatives register as document-scoped synonyms using `synonyms::` syntax; they do not silently update the shared graph used by other tools. +Attach alternatives to words, sentences and paragraphs. Save an annotated Markdown document and reopen it with those choices preserved. Newly authored alternatives register as document-scoped synonyms using `synonyms::` syntax; download them through Knowledge graph to use that exact vocabulary in terraphim-grep. Sharing is explicit and does not silently alter your active role. Ghost passages to retain them outside clean export, set notes aside in Overflow, and inspect possible cuts in the Lab. KG-protected Trim preserves matched concepts and their original spelling, including supported Unicode normalization. @@ -30,7 +30,9 @@ The editor runs as WebAssembly in your browser. Editing with the bundled graph d ## See actual saved outputs -[Watch the captioned editor and terraphim-grep walkthroughs](/how-tos/knowledge-work-editor/): Chrome, Safari, Firefox and the existing Zed extension, followed by search with the same shared KG, with and without synthesis. Each video identifies its recorded scope; the current browser build includes the later Unicode normalization repair. +[Watch the 80-second alternative-to-search story](/how-tos/synonym-search/): create Concern as an alternative to Risk, share the graph and find related decisions, with and without synthesis. + +[Earlier captioned browser and extension walkthroughs](/how-tos/knowledge-work-editor/): Chrome, Safari, Firefox and the existing Zed extension, followed by search with the same shared KG, with and without synthesis. Each video identifies its recorded scope; the current browser build includes the later Unicode normalization repair. This is a preview. Complete acceptance against all 41 source-video requirements remains open, including Safari dragging and some visual/native cases. The existing Zed extension demonstrates its supported capabilities; the separate Zed fork is outside this publication. diff --git a/content/how-tos/knowledge-work-editor.md b/content/how-tos/knowledge-work-editor.md index 6a10824..258a3df 100644 --- a/content/how-tos/knowledge-work-editor.md +++ b/content/how-tos/knowledge-work-editor.md @@ -4,6 +4,10 @@ description = "Try Terraphim Editor, then inspect search results with and withou date = 2026-10-11 +++ +[Watch the new 80-second story: create a synonym, share it, then search](/how-tos/synonym-search/) + +These earlier host recordings remain available as versioned evidence. The new story above follows the current alternative-to-search flow. + [Try Terraphim Editor](/editor/) Keep alternatives attached to words, sentences and paragraphs. Ghost a passage without losing it, stash notes in Overflow, and use the Lab to inspect possible cuts. The editor runs as WebAssembly in your browser with a bundled local knowledge graph. diff --git a/content/how-tos/synonym-search.md b/content/how-tos/synonym-search.md new file mode 100644 index 0000000..32bf89a --- /dev/null +++ b/content/how-tos/synonym-search.md @@ -0,0 +1,62 @@ ++++ +title = "Different words. The same idea." +description = "Create a wording alternative, share its knowledge graph, and find related decisions with terraphim-grep." +date = 2026-10-11 +[taxonomies] +categories = ["How-tos"] +tags = ["editor", "knowledge-graph", "search"] ++++ + +Your project brief says **risk**. Later, you search for **concern**. Terraphim connects the two so a change of wording does not hide the decisions you need. + + + +[Try Terraphim Editor](/editor/) · [Read the narration](/demos/synonym-search/transcript.txt) + +## Choose the words that fit + +In our community garden brief, timber might arrive late. The team has decided to keep a backup supplier. Add **Concern** as an alternative to **Risk**, then swap between them. The other wording stays attached to the document. + +## Share what those words mean + +Open **Knowledge graph** from the Markdown export menu. The editor shows **Risk ↔ Concern** and its actual definition: + +```text +synonyms:: Risk, Concern +``` + +Download the graph and the clean text. This explicitly shares the document's authored synonym groups as a Terraphim thesaurus; it does not silently change your active role. The saved example contains **Risk**, with no literal occurrence of **Concern**. + +## Find the decisions using either word + +Use the graph downloaded from that editing session: + +```sh +terraphim-grep concern --thesaurus knowledge-graph.json --paths corpus -C 2 --json +``` + +The actual result contains three risk passages and their related decisions, with file and line locations. No generated answer is requested. + +Add `--answer` to the same command for synthesis: + +```sh +terraphim-grep concern --thesaurus knowledge-graph.json --paths corpus -C 2 --json --answer +``` + +The video shows the generated summary and its references to the same three passages. Decisions remain decisions: the source does not establish whether they have been carried out. Check the evidence alongside any generated answer. + +Editing and local search use your local data. Optional synthesis sends the retrieved passages to your configured model provider. + +## Try the exact files + +- [The graph downloaded in the recording](/demos/synonym-search/knowledge-graph.json) +- [The clean project brief](/demos/synonym-search/project-brief.md) — place it in a `corpus` directory +- [Saved Markdown with alternatives](/demos/synonym-search/annotated.md) +- [Actual result without synthesis](/demos/synonym-search/without-synthesis.json) +- [Actual result with synthesis](/demos/synonym-search/with-synthesis.json) + +The community garden brief is authored public sample input. The alternatives, downloaded graph, retrieved passages and generated answer are actual outputs. The command-line section displays those JSON fields in a readable presentation; it is not a separate Terraphim interface. The voice is synthetic British English (Emma). Setup is omitted, and the recorded interaction runs at its original speed. [Recording details and file identities](/demos/synonym-search/manifest.json). + +This is a preview of the new export and search flow. It uses the [editor graph-sharing candidate](https://git.terraphim.cloud/terraphim/terraphim-editor/pulls/131) and the [grep synonym/synthesis candidate](https://git.terraphim.cloud/terraphim/terraphim-clients/pulls/385); older installed binaries may differ. A fresh synthesis run may produce different wording. + +[Earlier browser and Zed host walkthroughs](/how-tos/knowledge-work-editor/) retain their own recorded versions and limitations. This focused story does not qualify all 41 source-video requirements. The Zed fork remains separate. diff --git a/docs/editor-publication.md b/docs/editor-publication.md index e4520a9..2c8cc7c 100644 --- a/docs/editor-publication.md +++ b/docs/editor-publication.md @@ -6,18 +6,18 @@ Before deployment, verify both source assets and Zola output with that independe ```bash python3 -m unittest discover -s scripts -p test_import_editor.py -v -python3 scripts/import-editor.py --verify --destination static/editor --manifest-sha256 3e0510de27b93c5aed5018058b55bd82798ad7ed535fd1c25812aef5a0aadc03 +python3 scripts/import-editor.py --verify --destination static/editor --manifest-sha256 e972d1c779cca709ff1a1c8e0b0c543b855d82d7e6fa26fbf14414f84c6fb1d0 zola build -python3 scripts/import-editor.py --verify --destination public/editor --manifest-sha256 3e0510de27b93c5aed5018058b55bd82798ad7ed535fd1c25812aef5a0aadc03 +python3 scripts/import-editor.py --verify --destination public/editor --manifest-sha256 e972d1c779cca709ff1a1c8e0b0c543b855d82d7e6fa26fbf14414f84c6fb1d0 ``` Use Zola 0.22.1, matching deployment. A different installed Zola version is not the deployment build. Import a replacement into a fresh directory using `scripts/import-editor.py --source BUILD --manifest MANIFEST --manifest-sha256 APPROVED_DIGEST --destination NEW_DIRECTORY`. Review source identity, runtime closure and browser output evidence before changing the independent pin. Never derive the expected digest from the mutable deployed receipt during verification. -The current bundle was staged from clean editor commit `949b5252e89814ed294c1a238cef726663873a76`, including the independently reviewed Unicode KG-protected Trim repair. Confirm that this source is admitted to the editor release chain before promoting this website candidate. Deployment and HTTPS/browser acceptance are separate gates. Safari dragging and complete 41-requirement acceptance remain unresolved. A stock Zed capability preview is not custom visual parity; the fork is outside this change. +The current bundle was staged from clean editor commit `4050ac9a35e1255ebc3f1d7cd50eb0dd97059730`, including the Unicode KG-protected Trim repair and reviewed document-synonym export. Confirm that this source is admitted to the editor release chain before promoting this website candidate. Deployment and HTTPS/browser acceptance are separate gates. Safari dragging and complete 41-requirement acceptance remain unresolved. A stock Zed capability preview is not custom visual parity; the fork is outside this change. -The how-to uses authored public input and an existing shared KG. New alternatives are document-scoped synonyms. The paired grep synthesis demonstration requires the clients candidate with synonym retrieval, semantic coverage and explicit `--answer` fixes; an older installed binary may behave differently. Configured provider synthesis may send public retrieved text outside the device. +The new synonym-search how-to uses authored public input, then creates Concern as an alternative to Risk and downloads the actual document synonym graph. The CLI consumes those exact downloaded bytes. The older host walkthroughs retain their existing shared-KG demonstration and recorded limitations. The paired grep synthesis demonstration requires the clients candidate with synonym retrieval, semantic coverage and explicit `--answer` fixes; an older installed binary may behave differently. Configured provider synthesis may send public retrieved text outside the device. ## Public walkthrough media @@ -28,3 +28,7 @@ The recorded browser candidate remains `e2ee3ff9f6133cd67cd15533109c8020c20a0cdf Unicode repair verification:110 native library tests, six original protection tests, eight Unicode regressions, strict native/WASM Clippy, real Chrome Trim reduction, Done ghosts, exact annotated save/reopen and undo/redo. Existing Terraphim normalized term and matcher types are reused; NFC and original grapheme boundary mapping are added. Greek sigma variants are equivalent; general full case folding and Straße/STRASSE transliteration are not claimed. Exact original spelling is retained. The paced-v2 media edition adds burned-in explanatory text outside the app image and replaces idle-heavy walkthroughs with disclosed chronological cuts and1.5–3× speed changes. Overview77s, Chrome44s, Firefox49s, Safari45s, Zed60s, grep21s. Original footage, source identity and qualification limits remain unchanged. The manifest records every retained source range and output caption interval. + +## Focused synonym-search story + +The 80-second story at `/how-tos/synonym-search/` supersedes the previous overview as the homepage demo. It records editor `4050ac9a35e1255ebc3f1d7cd50eb0dd97059730` and clients `f0410af`, with natural-rate British female synthetic narration. Editor-created graph and clean text are actual downloads; identical chunks are verified with synthesis off/on. CLI JSON fields are presented readably with original strings retained. See `/demos/synonym-search/manifest.json` for the media and artifact identities. diff --git a/scripts/test_publication_media.py b/scripts/test_publication_media.py index 766c0d8..536e15a 100644 --- a/scripts/test_publication_media.py +++ b/scripts/test_publication_media.py @@ -75,8 +75,8 @@ def test_walkthrough_links_and_disclosures(self): for text in ['41 requirements', 'zero cuts', 'overstates', 'document-scoped synonym', 'Zed fork is separate', 'normalization repair']: self.assertIn(text, page) home = (ROOT / 'themes/WarpDrive/templates/index.html').read_text() - self.assertIn("get_url(path='editor/')", home) - self.assertIn("get_url(path='@/how-tos/knowledge-work-editor.md')", home) + self.assertIn('href="/editor/"', home) + self.assertIn('href="/how-tos/synonym-search/"', home) if __name__ == '__main__': diff --git a/scripts/test_synonym_story.py b/scripts/test_synonym_story.py new file mode 100644 index 0000000..05bcc30 --- /dev/null +++ b/scripts/test_synonym_story.py @@ -0,0 +1,57 @@ +"""Verify actual editor exports, CLI results and publication media identities.""" +import hashlib +import json +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[1] +MEDIA = ROOT / 'static/demos/synonym-search' + + +class SynonymStoryTests(unittest.TestCase): + def test_export_is_used_for_search_and_synthesis(self): + graph = json.loads((MEDIA / 'knowledge-graph.json').read_text()) + self.assertEqual(graph['data']['risk']['id'], graph['data']['concern']['id']) + self.assertNotIn('concern', (MEDIA / 'project-brief.md').read_text().lower()) + off = json.loads((MEDIA / 'without-synthesis.json').read_text()) + on = json.loads((MEDIA / 'with-synthesis.json').read_text()) + self.assertIsNone(off['answer']) + self.assertEqual(off['chunks'], on['chunks']) + self.assertEqual(len(off['chunks']), 3) + lines = (MEDIA / 'project-brief.md').read_text().splitlines() + for chunk in off['chunks']: + self.assertEqual(chunk['source'], 'project-brief.md') + self.assertEqual(chunk['content'], '\n'.join(lines[chunk['line_start'] - 1:chunk['line_end']])) + for citation in on['answer']['citations']: + self.assertEqual(citation['source'], 'project-brief.md') + self.assertEqual(citation['excerpt'], lines[citation['line'] - 1]) + self.assertTrue(on['answer']['answer']) + self.assertEqual(len(on['answer']['citations']), 3) + + def test_media_and_outputs_match_reviewed_manifest(self): + manifest = json.loads((MEDIA / 'manifest.json').read_text()) + self.assertGreaterEqual(manifest['durationSeconds'], 60) + self.assertLessEqual(manifest['durationSeconds'], 90) + self.assertTrue(manifest['voice']['synthetic']) + self.assertTrue(manifest['voice']['speechRateUnchanged']) + for entry in manifest['files']: + data = (MEDIA / entry['file']).read_bytes() + self.assertEqual(hashlib.sha256(data).hexdigest(), entry['sha256']) + self.assertEqual(len(data), entry['bytes']) + self.assertEqual(manifest['checks']['pageErrors'], []) + self.assertTrue(manifest['checks']['sameRetrievedChunks']) + self.assertTrue(manifest['checks']['sameGraphAndTextAsPriorVerifiedRun']) + + def test_main_story_is_linked_and_scope_is_clear(self): + page = (ROOT / 'content/how-tos/synonym-search.md').read_text() + home = (ROOT / 'themes/WarpDrive/templates/index.html').read_text() + self.assertIn('href="/how-tos/synonym-search/"', home) + for text in ['synonyms:: Risk, Concern', '--answer', 'authored public sample input', + 'synthetic British English', 'does not qualify all 41', 'Zed fork remains separate']: + self.assertIn(text, page) + for file in ['synonym-search-story.mp4', 'knowledge-graph.json', 'project-brief.md', 'without-synthesis.json', 'with-synthesis.json']: + self.assertIn('/demos/synonym-search/' + file, page) + + +if __name__ == '__main__': + unittest.main() diff --git a/static/demos/synonym-search/annotated.md b/static/demos/synonym-search/annotated.md new file mode 100644 index 0000000..a2a7756 --- /dev/null +++ b/static/demos/synonym-search/annotated.md @@ -0,0 +1,44 @@ +# Community garden launch + + +Goal: Open the garden for spring planting. + + +Risk: Timber may arrive late. Decision: Keep a backup supplier. + + +Risk: Rain may delay planting. Decision: Reserve a covered area. + + +Risk: Volunteers may cancel. Decision: Keep a reserve list. + +```terraphim-alternatives +{ + "version": 1, + "spans": [ + { + "id": "s1", + "kind": "word", + "anchor": { + "start": 73, + "end": 77, + "text": "Risk", + "before": " garden for spring planting.\n\n\n", + "after": ": Timber may arrive late. " + }, + "active": 0, + "alts": [ + { + "text": "Risk", + "source": "original" + }, + { + "text": "Concern", + "source": "human" + } + ] + } + ], + "ghosts": [] +} +``` diff --git a/static/demos/synonym-search/captions.vtt b/static/demos/synonym-search/captions.vtt new file mode 100644 index 0000000..d6ca1d2 --- /dev/null +++ b/static/demos/synonym-search/captions.vtt @@ -0,0 +1,58 @@ +WEBVTT + +00:00:00.250 --> 00:00:04.443 +When people use different words for the same idea, useful decisions can + +00:00:04.443 --> 00:00:06.190 +become surprisingly hard to find. + +00:00:07.250 --> 00:00:10.852 +Here is our community garden brief. It records three risks, and the + +00:00:10.852 --> 00:00:13.553 +decisions we have made to deal with each one. + +00:00:15.250 --> 00:00:18.996 +I add concern as an alternative to risk. Now I can swap + +00:00:18.996 --> 00:00:22.742 +between the two words, choosing the wording that fits without losing the + +00:00:22.742 --> 00:00:23.366 +other option. + +00:00:26.250 --> 00:00:30.840 +The editor connects those alternatives to one idea in the knowledge graph. + +00:00:30.840 --> 00:00:35.047 +Here is the actual synonym definition: risk and concern belong together. + +00:00:37.250 --> 00:00:41.059 +I download that graph and the finished text. Notice that I leave + +00:00:41.059 --> 00:00:43.915 +risk in the document. The word concern is absent. + +00:00:46.250 --> 00:00:50.497 +Now I search for concern using the graph I just downloaded. It + +00:00:50.497 --> 00:00:54.744 +finds all three risk passages, including the related decisions, even though the + +00:00:54.744 --> 00:00:55.805 +wording is different. + +00:00:58.250 --> 00:01:02.632 +Without synthesis, I get the original passages and their locations. Adding the + +00:01:02.632 --> 00:01:07.014 +answer flag gives me a summary from the same evidence, with source + +00:01:07.014 --> 00:01:07.379 +references. + +00:01:10.250 --> 00:01:14.735 +The summary keeps decisions distinct from completed actions. From choosing a word + +00:01:14.735 --> 00:01:19.219 +to finding a decision, the same knowledge graph keeps the meaning connected. diff --git a/static/demos/synonym-search/knowledge-graph.json b/static/demos/synonym-search/knowledge-graph.json new file mode 100644 index 0000000..f7b3213 --- /dev/null +++ b/static/demos/synonym-search/knowledge-graph.json @@ -0,0 +1,17 @@ +{ + "data": { + "concern": { + "display_value": "Risk", + "id": 1, + "nterm": "risk", + "pinned": false + }, + "risk": { + "display_value": "Risk", + "id": 1, + "nterm": "risk", + "pinned": false + } + }, + "name": "Document alternatives" +} diff --git a/static/demos/synonym-search/manifest.json b/static/demos/synonym-search/manifest.json new file mode 100644 index 0000000..927bcfc --- /dev/null +++ b/static/demos/synonym-search/manifest.json @@ -0,0 +1,170 @@ +{ + "schema": 1, + "edition": "synonym-search-story-v1", + "durationSeconds": 80.0, + "editorSourceHead": "4050ac9a35e1255ebc3f1d7cd50eb0dd97059730", + "clientsSourceHead": "f0410af", + "voice": { + "name": "Emma", + "engine": "Kokoro v1.0", + "voiceId": "bf_emma", + "language": "en-GB", + "synthetic": true, + "speechRateUnchanged": true + }, + "checks": { + "pageErrors": [], + "sameRetrievedChunks": true, + "sameGraphAndTextAsPriorVerifiedRun": true, + "finalMuxAsrWordSimilarity": 1.0, + "decodedAudioPeak": 0.7943115234375 + }, + "inputs": { + "graphSha256": "f8648b16470e764bdff5c95064078419d71e8d41d2ca30d4b1508017c166a45d", + "textSha256": "ea78a26e4d5a1105891bed67288a924cbbd5656defc95de4c511f4302b8d0d8f", + "binarySha256": "d4d29232e81b098e8ee7e4c939cd4fd35b3fa06efd1e197d83b2542940f3c63b" + }, + "commands": [ + { + "argv": [ + "terraphim-grep", + "concern", + "--thesaurus", + "knowledge-graph.json", + "--paths", + "corpus", + "-C", + "2", + "--json" + ], + "exitCode": 0, + "seconds": 0.047 + }, + { + "argv": [ + "terraphim-grep", + "concern", + "--thesaurus", + "knowledge-graph.json", + "--paths", + "corpus", + "-C", + "2", + "--json", + "--answer" + ], + "exitCode": 0, + "seconds": 5.991 + } + ], + "edits": { + "introSeconds": 7, + "sourceSetupOmittedSeconds": 0.237, + "retainedCaptureSeconds": 73, + "speed": 1, + "captionsOutsideApp": true, + "cliPresentation": "Readable presentation of real subprocess JSON fields; source strings unchanged.", + "events": [ + { + "name": "brief_ready", + "seconds": 0 + }, + { + "name": "concern_added", + "seconds": 9.262 + }, + { + "name": "swapped_to_risk", + "seconds": 13.012 + }, + { + "name": "swapped_to_concern", + "seconds": 15.013 + }, + { + "name": "risk_retained", + "seconds": 17.025 + }, + { + "name": "graph_visible", + "seconds": 19.093 + }, + { + "name": "synonyms_syntax_visible", + "seconds": 24.045 + }, + { + "name": "graph_downloaded", + "seconds": 30.05 + }, + { + "name": "text_downloaded", + "seconds": 32.029 + }, + { + "name": "search_results_visible", + "seconds": 39.102 + }, + { + "name": "synthesis_started", + "seconds": 51.016 + }, + { + "name": "synthesis_visible", + "seconds": 57.024 + }, + { + "name": "end", + "seconds": 73.002 + } + ] + }, + "qualification": "Public preview of one finite browser workflow. Does not qualify all 41 requirements or the existing extensions. Zed fork separate.", + "files": [ + { + "file": "annotated.md", + "bytes": 793, + "sha256": "036d8e65b847fc03c68814b610be94e2b7a27573188947451348f9f674150e6e" + }, + { + "file": "captions.vtt", + "bytes": 1704, + "sha256": "8c1c3013e992032bcc8fa93c72718303bdc9e492142feaede595c70d2292cf28" + }, + { + "file": "knowledge-graph.json", + "bytes": 281, + "sha256": "f8648b16470e764bdff5c95064078419d71e8d41d2ca30d4b1508017c166a45d" + }, + { + "file": "poster.jpg", + "bytes": 96969, + "sha256": "121b5e01129e4c9ad3f99dd173f89394835e0e717902e48be6efa110eabcd9f6" + }, + { + "file": "project-brief.md", + "bytes": 265, + "sha256": "ea78a26e4d5a1105891bed67288a924cbbd5656defc95de4c511f4302b8d0d8f" + }, + { + "file": "synonym-search-story.mp4", + "bytes": 2499530, + "sha256": "97e88cc7522d5734deedefc07827ebe985ec6e636a2f41ae574b3e4d7e0ac02c" + }, + { + "file": "transcript.txt", + "bytes": 1540, + "sha256": "1eb1999a04add460c1474a2889e7fb12238b0cafd07a77ef6246ad97ce07c9b4" + }, + { + "file": "with-synthesis.json", + "bytes": 2288, + "sha256": "d6381390ae7ce09b91cf406195b01ade90a753aad72594a7c3a1676443bf5b82" + }, + { + "file": "without-synthesis.json", + "bytes": 1248, + "sha256": "302eb12c12e9f8a68daca280ee783c152221214969d630929d4666c5236c5365" + } + ] +} diff --git a/static/demos/synonym-search/poster.jpg b/static/demos/synonym-search/poster.jpg new file mode 100644 index 0000000..d363e88 Binary files /dev/null and b/static/demos/synonym-search/poster.jpg differ diff --git a/static/demos/synonym-search/project-brief.md b/static/demos/synonym-search/project-brief.md new file mode 100644 index 0000000..07c11d2 --- /dev/null +++ b/static/demos/synonym-search/project-brief.md @@ -0,0 +1,13 @@ +# Community garden launch + + +Goal: Open the garden for spring planting. + + +Risk: Timber may arrive late. Decision: Keep a backup supplier. + + +Risk: Rain may delay planting. Decision: Reserve a covered area. + + +Risk: Volunteers may cancel. Decision: Keep a reserve list. \ No newline at end of file diff --git a/static/demos/synonym-search/synonym-search-story.mp4 b/static/demos/synonym-search/synonym-search-story.mp4 new file mode 100644 index 0000000..672813c Binary files /dev/null and b/static/demos/synonym-search/synonym-search-story.mp4 differ diff --git a/static/demos/synonym-search/transcript.txt b/static/demos/synonym-search/transcript.txt new file mode 100644 index 0000000..98d530b --- /dev/null +++ b/static/demos/synonym-search/transcript.txt @@ -0,0 +1,26 @@ +Different words. The same idea. +British English synthetic voice: Emma (Kokoro bf_emma). + +00–07s | One idea. Different words. +When people use different words for the same idea, useful decisions can become surprisingly hard to find. + +07–15s | Start with a project brief +Here is our community garden brief. It records three risks, and the decisions we have made to deal with each one. + +15–26s | Risk ↔ Concern +I add concern as an alternative to risk. Now I can swap between the two words, choosing the wording that fits without losing the other option. + +26–37s | Your alternatives build the graph +The editor connects those alternatives to one idea in the knowledge graph. Here is the actual synonym definition: risk and concern belong together. + +37–46s | Share the graph and the text +I download that graph and the finished text. Notice that I leave risk in the document. The word concern is absent. + +46–58s | Search by the word you remember +Now I search for concern using the graph I just downloaded. It finds all three risk passages, including the related decisions, even though the wording is different. + +58–70s | Evidence first. Synthesis when you want it. +Without synthesis, I get the original passages and their locations. Adding the answer flag gives me a summary from the same evidence, with source references. + +70–80s | Write in your words. Find it in mine. +The summary keeps decisions distinct from completed actions. From choosing a word to finding a decision, the same knowledge graph keeps the meaning connected. diff --git a/static/demos/synonym-search/with-synthesis.json b/static/demos/synonym-search/with-synthesis.json new file mode 100644 index 0000000..a3a278a --- /dev/null +++ b/static/demos/synonym-search/with-synthesis.json @@ -0,0 +1,71 @@ +{ + "chunks": [ + { + "content": "Risk: Timber may arrive late. Decision: Keep a backup supplier.", + "source": "project-brief.md", + "line_start": 7, + "line_end": 7, + "relevance_score": 2.0, + "haystack": "code" + }, + { + "content": "Risk: Rain may delay planting. Decision: Reserve a covered area.", + "source": "project-brief.md", + "line_start": 10, + "line_end": 10, + "relevance_score": 2.0, + "haystack": "code" + }, + { + "content": "Risk: Volunteers may cancel. Decision: Keep a reserve list.", + "source": "project-brief.md", + "line_start": 13, + "line_end": 13, + "relevance_score": 2.0, + "haystack": "code" + } + ], + "answer": { + "answer": "The retrieved context lists three concerns, each framed as a risk paired with a decision. The decisions are planned responses, not confirmed actions: timber may arrive late, with a decision to keep a backup supplier; rain may delay planting, with a decision to reserve a covered area; and volunteers may cancel, with a decision to keep a reserve list. There is no information confirming whether any of these decisions have been implemented or completed, so their implementation status is not established.", + "citations": [ + { + "source": "project-brief.md", + "line": 7, + "excerpt": "Risk: Timber may arrive late. Decision: Keep a backup supplier." + }, + { + "source": "project-brief.md", + "line": 10, + "excerpt": "Risk: Rain may delay planting. Decision: Reserve a covered area." + }, + { + "source": "project-brief.md", + "line": 13, + "excerpt": "Risk: Volunteers may cancel. Decision: Keep a reserve list." + } + ], + "confidence": 0.95 + }, + "concepts": [ + { + "id": 0, + "name": "concern", + "display_value": null, + "score": 1.0 + }, + { + "id": 0, + "name": "risk", + "display_value": null, + "score": 1.0 + } + ], + "sufficiency": "RlmSynthesis", + "sufficiency_explanation": "Found 3 chunks (coverage 1.00, KG confidence 1.00); the answer was synthesised by the LLM in 5936ms.", + "stats": { + "search_latency_ms": 5951, + "rlm_latency_ms": 5936, + "chunks_returned": 3, + "kg_hits": 2 + } +} diff --git a/static/demos/synonym-search/without-synthesis.json b/static/demos/synonym-search/without-synthesis.json new file mode 100644 index 0000000..03d2a97 --- /dev/null +++ b/static/demos/synonym-search/without-synthesis.json @@ -0,0 +1,51 @@ +{ + "chunks": [ + { + "content": "Risk: Timber may arrive late. Decision: Keep a backup supplier.", + "source": "project-brief.md", + "line_start": 7, + "line_end": 7, + "relevance_score": 2.0, + "haystack": "code" + }, + { + "content": "Risk: Rain may delay planting. Decision: Reserve a covered area.", + "source": "project-brief.md", + "line_start": 10, + "line_end": 10, + "relevance_score": 2.0, + "haystack": "code" + }, + { + "content": "Risk: Volunteers may cancel. Decision: Keep a reserve list.", + "source": "project-brief.md", + "line_start": 13, + "line_end": 13, + "relevance_score": 2.0, + "haystack": "code" + } + ], + "answer": null, + "concepts": [ + { + "id": 0, + "name": "concern", + "display_value": null, + "score": 1.0 + }, + { + "id": 0, + "name": "risk", + "display_value": null, + "score": 1.0 + } + ], + "sufficiency": "SearchOnly", + "sufficiency_explanation": "Found 3 chunks but diversity 1 < 2; returning search results only (pass --answer or --force-rlm to synthesise).", + "stats": { + "search_latency_ms": 14, + "rlm_latency_ms": null, + "chunks_returned": 3, + "kg_hits": 2 + } +} diff --git a/static/editor/assets-manifest.json b/static/editor/assets-manifest.json index 5977400..bf7f1f2 100644 --- a/static/editor/assets-manifest.json +++ b/static/editor/assets-manifest.json @@ -1,15 +1,15 @@ { "schema": 1, - "sourceHead": "949b5252e89814ed294c1a238cef726663873a76", + "sourceHead": "4050ac9a35e1255ebc3f1d7cd50eb0dd97059730", "assets": { "alternatives-panel-f7a68a7707d04dfa.css": "a99a407d3f6aa52f48e3013bf865ef4b44bd0b9e4028d9e68c1a6dd32f8a68a8", "blocks-b2480c8f9f3d63b3.css": "383c0e9197f015d651363e3abd31fdf67b56a6b4bde72ebe764a7b1e417c5b4c", "example.html": "fda475be8b87ff7a0789f167b4906357c3d8d7e7d1fcd0348f8c908d65e731d3", - "index.html": "2bc7bbe7cfa67a414162f594e9eaa07e6328afd716cebfb0a4c0a199a5ca6f30", + "index.html": "4b95359d3d9dd26ee40dc67a1157078e3709cebd8ad2f82c86e3605fc0ab497c", "js/rolegraph-worker.js": "8240803c0aef05acd66b6ec91eaee16e53a556252aa48c8487a4c3bd9a8924d9", "lab-3fee6e89acf44fa1.css": "1fe0441a83177e9713ac112fac85336ad3315b3ac755ec8a625e008c4ca1f967", "overflow-cc1be2ca3843f438.css": "8fa1a611df91541508b1532d3628addffecec02b2516a3f7a2c98bc385ada218", - "persistence-cb27cfbe192754a.css": "da87a8b790fadebdc4b451dce2535eb829291c799d7199a6734ce9cf19f68ffd", + "persistence-c4310be97ce197ef.css": "2d9825e27391a177e65032952923eb03f505fa95f86590f98e24f753fa2ba660", "rolegraph/rolegraph-worker.js": "8240803c0aef05acd66b6ec91eaee16e53a556252aa48c8487a4c3bd9a8924d9", "rolegraph/terraphim_rolegraph_browser.d.ts": "95094622b3805fed54e96cdcbb1c732d1244fbecceadb24dd13e0f28c1d8f7e2", "rolegraph/terraphim_rolegraph_browser.js": "0f155f7ebe48ffd9958266c153f965447f98e26724e8fa994d1e18d879a9f4dc", @@ -17,12 +17,12 @@ "rolegraph/terraphim_rolegraph_browser_bg.wasm.d.ts": "f6cc0427ac07ca31fc3882c5f9487347717f887a0c9ebba69a1323eda3a822ea", "roles/default.json": "e850422ec72faa06aae0d6c2893a9f1d2e36d60a5aef79bd8f863daa2d71b015", "selection-menu-317cedafb0e3952e.css": "7d0790ed1bf0c6e3955e5305d31ace2bd67f6df5f5f2b7be9692f9c79843891e", - "terraphim-editor-257aa4f4cf8c0c34.js": "ead25002c035e02fba7936c7f043475cc573c62b5b6a0d76dba952f2ee5b64eb", - "terraphim-editor-257aa4f4cf8c0c34_bg.wasm": "c861c569dbb87eb098e8d5e1614d9579b9d6de3297cda72575693c86144a76ec", - "terraphim-editor.min.css": "0a6c2cb03d65b43b844ab3cd80614716c362136c1347423861d780ac4e0461a9", - "terraphim-editor.min.js": "711819be5ab76ea6daea66352ff252b3c93535054da0140cfa664cfcb2b609d9", - "terraphim_editor.js": "ead25002c035e02fba7936c7f043475cc573c62b5b6a0d76dba952f2ee5b64eb", - "terraphim_editor_bg.wasm": "c861c569dbb87eb098e8d5e1614d9579b9d6de3297cda72575693c86144a76ec", + "terraphim-editor-438d353659f8cd7.js": "6f55bfce550053ab0cd8a1f4ffd101436f9de3e2610702269ed53bf0dc2ca7cd", + "terraphim-editor-438d353659f8cd7_bg.wasm": "f167a40fb0934703269a3bfe34adcfbc28994933f939f03bab7bf59425021dc6", + "terraphim-editor.min.css": "147cd5ce172f00873b002ac215b004631c7c17aaadf23fb7c786a90628dfe1f3", + "terraphim-editor.min.js": "a650ab15ac36d0868268099175d60da1e228283fc0f2a46b61dd1fa752ef4fba", + "terraphim_editor.js": "6f55bfce550053ab0cd8a1f4ffd101436f9de3e2610702269ed53bf0dc2ca7cd", + "terraphim_editor_bg.wasm": "f167a40fb0934703269a3bfe34adcfbc28994933f939f03bab7bf59425021dc6", "tokens-36540e1e0e83eb54.css": "71e359d23d7ec44c9c838f74cf77f6e33aa3c33c57757cfc4cfb96e22a09de55", "trim-a64aceacb4246fba.css": "fe79acb70b9906e404d30e19b7d5e1cf6589bc0aa155f1c1dc93ed18e6dee81d", "vendor/fontawesome/6.5.2/LICENSE.txt": "9b914eae88817d63b576eab5aafde7068c7a1abae125d7cdfb034f1da43a9afc", @@ -134,7 +134,5 @@ "vendor/shoelace/2.12.0/third-party-notices/lit_node_modules_lit_reactive-element/LICENSE": "45d31799d0db956cc3eb5469346abbd9b7025babc5ff29fab10d7095da992ef1", "vendor/shoelace/2.12.0/third-party-notices/shoelace-style_localize/LICENSE.md": "ac9ffacbfc877b6f4253a481b3c0edbad0c019ac3506a3b51415b336f4a0c054", "write-on-6a8fd9449457dd74.css": "bd3b7471c8a7ba32c38927d47eefe26e5680ffa6ec96262380f4cf383340674c" - }, - "qualification": "Hash-admitted Unicode Trim preview; not full41 parity", - "fullParityAccepted": false + } } diff --git a/static/editor/index.html b/static/editor/index.html index dc2baab..fad6798 100644 --- a/static/editor/index.html +++ b/static/editor/index.html @@ -7700,6 +7700,7 @@ button('open', 'fa-regular fa-folder-open', 'Open document', 'Open (Ctrl+O)', () => this.open()); this.toolbarSave = button('save', 'fa-solid fa-floppy-disk', 'Save document', 'Save (Ctrl+S)', () => this.save()); button('markdown', 'fa-brands fa-markdown', 'Markdown export', 'Markdown export', () => this.showMarkdown()); + button('knowledge-graph', 'fa-solid fa-share-nodes', 'Knowledge graph', 'Share your alternatives as a knowledge graph', () => this.showKnowledgeGraph()); const divider = document.createElement('sl-divider'); divider.setAttribute('vertical', ''); divider.className = 'te-files-toolbar-divider'; @@ -8361,8 +8362,8 @@ } /** Download `text` as `name` through a Blob and an . */ - download(text, name) { - const blob = new Blob([text], { type: 'text/markdown;charset=utf-8' }); + download(text, name, type = 'text/markdown;charset=utf-8') { + const blob = new Blob([text], { type }); const url = URL.createObjectURL(blob); this.objectUrls.add(url); const a = document.createElement('a'); @@ -8504,6 +8505,7 @@ this.dialogs = this.dialogs.filter((n) => n !== dialog); if (this.replaceDialog === dialog) this.replaceDialog = null; if (this.markdownDialog === dialog) this.markdownDialog = null; + if (this.knowledgeGraphDialog === dialog) this.knowledgeGraphDialog = null; } // --------------------------------------------------------------------- @@ -8574,6 +8576,9 @@ }); }, }); + this.dialogButton(footer, 'Knowledge graph', { + name: 'knowledge-graph', onClick: () => { dialog.dataset.teReplaced = 'true'; dialog.close(); this.showKnowledgeGraph(); }, + }); dialog.append(note, area, footer); this.on(dialog, 'close', () => this.removeDialog(dialog), { once: true }); this.markdownDialog = dialog; @@ -8581,6 +8586,80 @@ return dialog; } + /** Explain and explicitly share the real document-derived synonym graph. */ + showKnowledgeGraph() { + if (this.destroyed) return null; + if (this.knowledgeGraphDialog) { + const old = this.knowledgeGraphDialog; + old.dataset.teReplaced = 'true'; + old.close(); + this.removeDialog(old); + } + const { dialog, header } = this.makeDialog('te-files-graph', 'Knowledge graph'); + const close = this.dialogButton(header, '×', { + name: 'close', className: 'te-files-close', ariaLabel: 'Close knowledge graph', + onClick: () => dialog.close(), + }); + close.querySelector('span').setAttribute('aria-hidden', 'true'); + const note = document.createElement('p'); + note.className = 'te-files-dialog-text'; + note.textContent = 'Your alternatives connect different words to the same idea. Share these connections so search understands either word.'; + const list = document.createElement('ul'); + list.className = 'te-files-concepts'; + list.setAttribute('aria-label', 'Connected alternatives'); + const footer = document.createElement('div'); + footer.className = 'te-files-dialog-footer'; + const status = document.createElement('span'); + status.className = 'te-files-status'; + status.setAttribute('role', 'status'); + footer.appendChild(status); + dialog.append(note, list); + try { + const graph = this.editor.exportKnowledgeGraph(); + const groups = (this.editor.annotations().synonymGroups || []).filter((g) => g.attached && g.synonyms.length); + if (!Object.keys(JSON.parse(graph).data).length) { + status.textContent = 'Add an alternative to a word, then return here to share its connection.'; + } else { + for (const group of groups) { + const row = document.createElement('li'); + row.textContent = [...new Set(group.alternatives.map((a) => a.text).filter((t) => t.trim()))].join(' ↔ '); + list.appendChild(row); + } + const details = document.createElement('details'); + const summary = document.createElement('summary'); + summary.textContent = 'View synonym definitions'; + const definitions = document.createElement('pre'); + definitions.className = 'te-files-synonym-definitions'; + definitions.textContent = groups.map((g) => g.markdown + (g.typedSynonyms.length ? 'Exact typed alternatives: ' + JSON.stringify(g.typedSynonyms) + '\n' : '')).join('\n'); + details.append(summary, definitions); + dialog.appendChild(details); + const clean = this.exportText(); + const name = teExportFileName(this.suggestedName()); + this.dialogButton(footer, 'Download graph', { + name: 'download-graph', className: 'te-files-primary', onClick: () => { + const result = this.download(graph + '\n', 'knowledge-graph.json', 'application/json;charset=utf-8'); + status.textContent = 'Graph downloaded. Use it with terraphim-grep --thesaurus knowledge-graph.json.'; + this.emit('te:graph-exported', { ...result, text: graph }); + }, + }); + this.dialogButton(footer, 'Download text', { + name: 'download-graph-text', onClick: () => { + const result = this.download(clean, name); + status.textContent = `Downloaded ${result.name}. Search this text using your graph.`; + this.emit('te:written', { ...result, text: clean, export: true }); + }, + }); + } + } catch (error) { + status.textContent = String(error.message || error); + } + dialog.appendChild(footer); + this.on(dialog, 'close', () => this.removeDialog(dialog), { once: true }); + this.knowledgeGraphDialog = dialog; + this.openDialog(dialog, close); + return dialog; + } + /** Copy the export: the Clipboard API, else select and execCommand. */ async copyExport(area, status) { const copied = 'Copied to the clipboard.'; @@ -11794,6 +11873,14 @@ return api.export_document(); } + /** Portable graph of committed document alternatives, using Rust synonym types. */ + exportKnowledgeGraph() { + const api = this.requireDocumentApi(); + this.alignDocumentModel(api); + if (typeof api.export_knowledge_graph !== 'function') throw new Error('Knowledge graph export needs an updated editor build.'); + return api.export_knowledge_graph(); + } + /** { words, chars } of the body, ghosted text included. */ counts() { const api = this.requireDocumentApi(); @@ -12083,7 +12170,7 @@ - + - +