diff --git a/demo/chains-of-thought/index.html b/demo/chains-of-thought/index.html
new file mode 100644
index 0000000..555c66e
--- /dev/null
+++ b/demo/chains-of-thought/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ Chains of Thought
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/chains-of-thought/main.ts b/demo/chains-of-thought/main.ts
new file mode 100644
index 0000000..a2019f1
--- /dev/null
+++ b/demo/chains-of-thought/main.ts
@@ -0,0 +1,92 @@
+import { SpatialGeometry } from '../../src/canvas/spatial-geometry.ts';
+import { SpatialConnection } from '../../src/arrows/spatial-connection.ts';
+import { FileSaver } from '../../src/persistence/file.ts';
+
+SpatialGeometry.register();
+SpatialConnection.register();
+
+interface Thought {
+ id: string;
+ text: string;
+ x: number;
+ y: number;
+}
+
+interface Connection {
+ sourceId: string;
+ targetId: string;
+}
+
+interface ChainOfThought {
+ thoughts: Thought[];
+ connections: Connection[];
+}
+
+const html = String.raw;
+
+function renderThought(thought: Thought) {
+ return html`
+
+ `;
+}
+
+function renderConnection({ sourceId, targetId }: Connection) {
+ return html``;
+}
+
+function renderChainOfThought({ thoughts, connections }: ChainOfThought) {
+ return html`${thoughts.map(renderThought).join('')}${connections.map(renderConnection).join('')}`;
+}
+
+function parseChainOfThought(): ChainOfThought {
+ return {
+ thoughts: Array.from(document.querySelectorAll('spatial-geometry')).map((el) => ({
+ id: el.dataset.id || '',
+ text: el.querySelector('textarea')?.value || '',
+ x: el.x,
+ y: el.y,
+ })),
+ connections: Array.from(document.querySelectorAll('spatial-connection')).map((el) => ({
+ sourceId: (el.sourceElement as SpatialGeometry).dataset.id || '',
+ targetId: (el.targetElement as SpatialGeometry).dataset.id || '',
+ })),
+ };
+}
+
+const openButton = document.querySelector('button[name="open"]')!;
+const saveButton = document.querySelector('button[name="save"]')!;
+const saveAsButton = document.querySelector('button[name="save-as"]')!;
+const main = document.querySelector('main')!;
+const fileSaver = new FileSaver('chains-of-thought', 'json', 'application/json');
+
+async function openFile() {
+ try {
+ const text = await fileSaver.open();
+ const json = JSON.parse(text);
+ main.innerHTML = renderChainOfThought(json);
+ } catch (e) {
+ // No file handler was persisted or the file is invalid JSON.
+ console.error(e);
+ }
+}
+
+async function saveFile(promptNewFile = false) {
+ fileSaver.save(JSON.stringify(parseChainOfThought(), null, 2), promptNewFile);
+}
+
+openButton.addEventListener('click', () => {
+ openFile();
+});
+
+saveButton.addEventListener('click', () => {
+ saveFile();
+});
+
+saveAsButton.addEventListener('click', () => {
+ saveFile(true);
+});
+
+openFile();
diff --git a/demo/index.html b/demo/index.html
index 089a513..d0b7399 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -27,6 +27,7 @@
Arrow
Canvasify
Spreadsheet
+ Chains of thought