diff --git a/forum-network/notes/client-or-ui.md b/forum-network/notes/client-or-ui.md
deleted file mode 100644
index ad24afd..0000000
--- a/forum-network/notes/client-or-ui.md
+++ /dev/null
@@ -1,9 +0,0 @@
-## Client/UI
-
-Voting consists of staking operations performed by software operated by owners of EOA.
-
-This software may be referred to as "The UI". It may also be considered "a client".
-
-It will need to be a network-connected application. It will need a certain minimum of RAM,
-and for some features disk storage,
-and for some features uptime .
diff --git a/forum-network/notes/client.md b/forum-network/notes/client.md
index 55df288..ee6db86 100644
--- a/forum-network/notes/client.md
+++ b/forum-network/notes/client.md
@@ -1,5 +1,18 @@
-# Client Operations
+## Client
-Client must communicate with one or more servers.
+Clients play a key role in an MVPR DAO.
-Client must build a local view
+Clients must be operated by reputation holders.
+
+Clients are the agents that submit posts to the forum, initiate validation pools, and vote in validation pools.
+
+We sometimes refer to the client as "the UI".
+
+It will need to be a network-connected application. It will need a certain minimum of RAM,
+and for some features disk storage,
+and for some features uptime .
+
+The behavior of the client constitutes what we refer to as the DAO's "soft protocols".
+
+Malicious actors may freely modify their own client's behavior.
+Therefore honest clients must engage in policing to preserve the integrity of the network.
diff --git a/forum-network/src/classes/dao/client.js b/forum-network/src/classes/dao/client.js
new file mode 100644
index 0000000..366fa7f
--- /dev/null
+++ b/forum-network/src/classes/dao/client.js
@@ -0,0 +1,6 @@
+export class Client {
+ constructor(dao, expert) {
+ this.dao = dao;
+ this.expert = expert;
+ }
+}
diff --git a/forum-network/src/classes/display/box.js b/forum-network/src/classes/display/box.js
index bee1f88..1ed666a 100644
--- a/forum-network/src/classes/display/box.js
+++ b/forum-network/src/classes/display/box.js
@@ -5,7 +5,9 @@ export class Box {
constructor(name, parentEl, options = {}) {
this.name = name;
this.el = document.createElement('div');
- this.el.id = `box_${randomID()}`;
+ this.el.box = this;
+ const id = options.id ?? randomID();
+ this.el.id = `${parentEl.id}_box_${id}`;
this.el.classList.add('box');
if (name) {
this.el.setAttribute('box-name', name);
diff --git a/forum-network/src/classes/display/document.js b/forum-network/src/classes/display/document.js
new file mode 100644
index 0000000..87c6bbf
--- /dev/null
+++ b/forum-network/src/classes/display/document.js
@@ -0,0 +1,30 @@
+import { Box } from './box.js';
+import { Form } from './form.js';
+
+/**
+ * @example
+ * ```typescript
+ * const doc = new Document();
+ * const form1 = doc.form();
+ * ```
+ */
+export class Document extends Box {
+ form() {
+ return this.addElement(new Form(this));
+ }
+
+ remarks(text, opts) {
+ return this.addElement(new Box('Remark', this.el, opts).setInnerHTML(text));
+ }
+
+ addElement(element) {
+ this.elements = this.elements ?? [];
+ this.elements.push(element);
+ return this;
+ }
+
+ get lastElement() {
+ if (!this.elements?.length) return null;
+ return this.elements[this.elements.length - 1];
+ }
+}
diff --git a/forum-network/src/classes/display/form.js b/forum-network/src/classes/display/form.js
new file mode 100644
index 0000000..80b6cb2
--- /dev/null
+++ b/forum-network/src/classes/display/form.js
@@ -0,0 +1,57 @@
+import { randomID } from '../../util/helpers.js';
+import { Box } from './box.js';
+
+const updateValuesOnEventTypes = ['keyup', 'mouseup'];
+
+export class FormElement extends Box {
+ constructor(name, parentEl, opts) {
+ super(name, parentEl, opts);
+ this.id = opts.id ?? name;
+ const { cb } = opts;
+ if (cb) {
+ updateValuesOnEventTypes.forEach((eventType) => this.el.addEventListener(eventType, () => {
+ cb(this);
+ }));
+ cb(this);
+ }
+ }
+}
+
+export class Button extends FormElement { }
+
+export class TextField extends FormElement {
+ constructor(name, parentEl, opts) {
+ super(name, parentEl, opts);
+ this.input = document.createElement('input');
+ this.el.appendChild(this.input);
+ }
+
+ get value() {
+ return this.input?.value;
+ }
+}
+
+export class TextArea extends FormElement { }
+
+export class Form {
+ constructor(document, opts = {}) {
+ this.document = document;
+ this.items = [];
+ this.id = opts.id ?? `form_${randomID()}`;
+ }
+
+ button(opts) {
+ this.items.push(new Button(opts.name, this.document.el, opts));
+ return this;
+ }
+
+ textField(opts) {
+ this.items.push(new TextField(opts.name, this.document.el, opts));
+ return this;
+ }
+
+ textArea(opts) {
+ this.items.push(new TextArea(opts.name, this.document.el, opts));
+ return this;
+ }
+}
diff --git a/forum-network/src/classes/display/scene.js b/forum-network/src/classes/display/scene.js
index 8f62ad1..3b14ec6 100644
--- a/forum-network/src/classes/display/scene.js
+++ b/forum-network/src/classes/display/scene.js
@@ -6,6 +6,7 @@ import { Table } from './table.js';
import { Flowchart } from './flowchart.js';
import { Controls } from './controls.js';
import { Box } from './box.js';
+import { Document } from './document.js';
export class Scene {
constructor(name, rootBox) {
@@ -86,6 +87,24 @@ export class Scene {
return this;
}
+ /**
+ *
+ * @param {string} name
+ * @param {(Document): Document} cb
+ * @returns {Scene}
+ */
+ withDocument(name, cb) {
+ this.documents = this.documents ?? [];
+ const doc = new Document(name, this.middleSection.el);
+ this.documents.push(cb ? cb(doc) : doc);
+ return this;
+ }
+
+ get lastDocument() {
+ if (!this.documents?.length) return null;
+ return this.documents[this.documents.length - 1];
+ }
+
registerActor(actor) {
this.actors.add(actor);
if (actor.options.announce) {
diff --git a/forum-network/src/index.html b/forum-network/src/index.html
index 86d1514..3c3e1fe 100644
--- a/forum-network/src/index.html
+++ b/forum-network/src/index.html
@@ -28,6 +28,12 @@
Multiple posts with overlapping authors
+
@@ -38,6 +44,7 @@
Debounce
Flowchart
Mocha
+ Input
diff --git a/forum-network/src/tests/all.test.html b/forum-network/src/tests/all.test.html
index 067797a..12ca110 100644
--- a/forum-network/src/tests/all.test.html
+++ b/forum-network/src/tests/all.test.html
@@ -37,6 +37,7 @@
+
+
+
+
+
diff --git a/forum-network/src/tests/input.test.html b/forum-network/src/tests/input.test.html
new file mode 100644
index 0000000..b2b35e1
--- /dev/null
+++ b/forum-network/src/tests/input.test.html
@@ -0,0 +1,26 @@
+
+
+
+ Input
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/forum-network/src/tests/scripts/client/client1.test.js b/forum-network/src/tests/scripts/client/client1.test.js
new file mode 100644
index 0000000..b5e7e9c
--- /dev/null
+++ b/forum-network/src/tests/scripts/client/client1.test.js
@@ -0,0 +1,26 @@
+import { mochaRun } from '../../../util/helpers.js';
+import { ForumTest } from '../forum.test-util.js';
+import { Client } from '../../../classes/dao/client.js';
+
+describe('Forum', function tests() {
+ this.timeout(0);
+
+ const forumTest = new ForumTest({ displayAuthors: false });
+ let client;
+
+ before(async () => {
+ await forumTest.setup();
+ await forumTest.newExpert();
+ await forumTest.newExpert();
+
+ client = new Client(forumTest.dao, forumTest.experts[0]);
+ });
+
+ it('Expert can run a client', async () => {
+ client.should.not.be.undefined;
+ client.dao.should.equal(forumTest.dao);
+ client.expert.should.equal(forumTest.experts[0]);
+ });
+});
+
+mochaRun();
diff --git a/forum-network/src/tests/scripts/forum/forum.test-util.js b/forum-network/src/tests/scripts/forum.test-util.js
similarity index 86%
rename from forum-network/src/tests/scripts/forum/forum.test-util.js
rename to forum-network/src/tests/scripts/forum.test-util.js
index 9aa34d5..02e85d5 100644
--- a/forum-network/src/tests/scripts/forum/forum.test-util.js
+++ b/forum-network/src/tests/scripts/forum.test-util.js
@@ -1,9 +1,9 @@
-import { Box } from '../../../classes/display/box.js';
-import { Scene } from '../../../classes/display/scene.js';
-import { Expert } from '../../../classes/actors/expert.js';
-import { PostContent } from '../../../classes/supporting/post-content.js';
-import { DAO } from '../../../classes/dao/dao.js';
-import { delayOrWait } from '../../../classes/display/controls.js';
+import { Box } from '../../classes/display/box.js';
+import { Scene } from '../../classes/display/scene.js';
+import { Expert } from '../../classes/actors/expert.js';
+import { PostContent } from '../../classes/supporting/post-content.js';
+import { DAO } from '../../classes/dao/dao.js';
+import { delayOrWait } from '../../classes/display/controls.js';
export class ForumTest {
constructor(options) {
diff --git a/forum-network/src/tests/scripts/forum/forum1.test.js b/forum-network/src/tests/scripts/forum/forum1.test.js
index b2e8895..38536e1 100644
--- a/forum-network/src/tests/scripts/forum/forum1.test.js
+++ b/forum-network/src/tests/scripts/forum/forum1.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum10.test.js b/forum-network/src/tests/scripts/forum/forum10.test.js
index 6327ae0..21430a3 100644
--- a/forum-network/src/tests/scripts/forum/forum10.test.js
+++ b/forum-network/src/tests/scripts/forum/forum10.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum11.test.js b/forum-network/src/tests/scripts/forum/forum11.test.js
index 0b144cc..04dcc92 100644
--- a/forum-network/src/tests/scripts/forum/forum11.test.js
+++ b/forum-network/src/tests/scripts/forum/forum11.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum2.test.js b/forum-network/src/tests/scripts/forum/forum2.test.js
index 71b1e76..fa98a90 100644
--- a/forum-network/src/tests/scripts/forum/forum2.test.js
+++ b/forum-network/src/tests/scripts/forum/forum2.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum3.test.js b/forum-network/src/tests/scripts/forum/forum3.test.js
index 1fd49e1..529298b 100644
--- a/forum-network/src/tests/scripts/forum/forum3.test.js
+++ b/forum-network/src/tests/scripts/forum/forum3.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum4.test.js b/forum-network/src/tests/scripts/forum/forum4.test.js
index 1a35b13..097d7e2 100644
--- a/forum-network/src/tests/scripts/forum/forum4.test.js
+++ b/forum-network/src/tests/scripts/forum/forum4.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum5.test.js b/forum-network/src/tests/scripts/forum/forum5.test.js
index b6c0a34..5988001 100644
--- a/forum-network/src/tests/scripts/forum/forum5.test.js
+++ b/forum-network/src/tests/scripts/forum/forum5.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum6.test.js b/forum-network/src/tests/scripts/forum/forum6.test.js
index 69ddd39..ab32aba 100644
--- a/forum-network/src/tests/scripts/forum/forum6.test.js
+++ b/forum-network/src/tests/scripts/forum/forum6.test.js
@@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js';
import { EPSILON } from '../../../util/constants.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum7.test.js b/forum-network/src/tests/scripts/forum/forum7.test.js
index 83cbf59..1a0d293 100644
--- a/forum-network/src/tests/scripts/forum/forum7.test.js
+++ b/forum-network/src/tests/scripts/forum/forum7.test.js
@@ -1,5 +1,5 @@
import { mochaRun } from '../../../util/helpers.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum8.test.js b/forum-network/src/tests/scripts/forum/forum8.test.js
index d695782..63da1ed 100644
--- a/forum-network/src/tests/scripts/forum/forum8.test.js
+++ b/forum-network/src/tests/scripts/forum/forum8.test.js
@@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/forum/forum9.test.js b/forum-network/src/tests/scripts/forum/forum9.test.js
index 8fbbf02..76eaf41 100644
--- a/forum-network/src/tests/scripts/forum/forum9.test.js
+++ b/forum-network/src/tests/scripts/forum/forum9.test.js
@@ -1,6 +1,6 @@
import { mochaRun } from '../../../util/helpers.js';
import { INCINERATOR_ADDRESS } from '../../../util/constants.js';
-import { ForumTest } from './forum.test-util.js';
+import { ForumTest } from '../forum.test-util.js';
describe('Forum', function tests() {
this.timeout(0);
diff --git a/forum-network/src/tests/scripts/input.test.js b/forum-network/src/tests/scripts/input.test.js
new file mode 100644
index 0000000..46e286d
--- /dev/null
+++ b/forum-network/src/tests/scripts/input.test.js
@@ -0,0 +1,35 @@
+import { Box } from '../../classes/display/box.js';
+// import { Document } from '../../classes/display/document.js';
+import { Scene } from '../../classes/display/scene.js';
+import { mochaRun } from '../../util/helpers.js';
+
+const rootElement = document.getElementById('scene');
+const rootBox = new Box('rootBox', rootElement).flex();
+const scene = window.scene = new Scene('Input test', rootBox);
+
+scene.withDocument();
+
+describe('Document', () => {
+ it('Exists', () => {
+ scene.withDocument('Document', (doc) => doc.remarks('Hello'));
+ });
+
+ describe('Input', () => {
+ it('Accepts input', () => {
+ scene.withDocument('Document', (doc) => doc.form());
+ const doc = scene.lastDocument;
+ const form1 = doc.lastElement;
+ const dvMap = new Map();
+ const updateFieldValueDisplay = ({ name, value }) => {
+ const dv = dvMap.get(name) ?? scene.addDisplayValue(name);
+ dvMap.set(name, dv);
+ dv.set(value);
+ };
+
+ form1.textField({ id: 'input1', name: 'Input 1', cb: updateFieldValueDisplay });
+ doc.remarks('Hmm...!');
+ });
+ });
+});
+
+mochaRun();