@iyulab/lowline-form 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/bundle/lowline-form.bundle.js +1631 -1555
- package/dist/client.d.ts +5 -0
- package/dist/draft.d.ts +46 -5
- package/dist/draft.js +70 -6
- package/dist/lowline-form.d.ts +4 -0
- package/dist/lowline-form.js +16 -1
- package/dist/session.d.ts +16 -1
- package/dist/session.js +49 -7
- package/package.json +4 -4
package/dist/client.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface ThresholdChoice {
|
|
|
24
24
|
*/
|
|
25
25
|
export interface FieldReport {
|
|
26
26
|
field: string;
|
|
27
|
+
/**
|
|
28
|
+
* How many settled records the thresholds were chosen on — the records the report speaks for. Records settled since
|
|
29
|
+
* are answered from, but not in the replay until the thresholds are chosen again.
|
|
30
|
+
*/
|
|
31
|
+
chosenOn: number;
|
|
27
32
|
lookups: number;
|
|
28
33
|
/** The threshold at which values settled alongside answer; absent while none was right often enough. */
|
|
29
34
|
key?: ThresholdChoice;
|
package/dist/draft.d.ts
CHANGED
|
@@ -8,10 +8,21 @@ export interface Suggestion {
|
|
|
8
8
|
origin?: 'decider' | 'other';
|
|
9
9
|
fit?: 'exact' | 'prefix';
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/** The layer of the decider an answer came from. */
|
|
12
|
+
export type Layer = 'key' | 'similar' | 'judgement';
|
|
13
|
+
/** What the host knows of the answer it shows or holds back, beyond its value: the layer that gave it and what it promised. */
|
|
14
|
+
export interface Reported {
|
|
15
|
+
layer?: Layer | null;
|
|
16
|
+
promise?: number;
|
|
17
|
+
}
|
|
18
|
+
interface Track extends Reported {
|
|
12
19
|
shown?: Suggestion;
|
|
13
20
|
declined?: boolean;
|
|
14
21
|
held?: number;
|
|
22
|
+
/** The answer held back for a check, kept so the record can say what the person was not shown. */
|
|
23
|
+
withheld?: Suggestion;
|
|
24
|
+
/** When the person reached the field, or its suggestion came into view — the first — on the session's clock, in milliseconds. */
|
|
25
|
+
at?: number;
|
|
15
26
|
}
|
|
16
27
|
/**
|
|
17
28
|
* The account of one record while it is written: the values it was opened with, and for each field the suggestion
|
|
@@ -19,17 +30,42 @@ interface Track {
|
|
|
19
30
|
* turns it, with the saved values, into what each decided field teaches — so a value taken from a suggestion is never
|
|
20
31
|
* counted as one typed. It is only bookkeeping: nothing is counted here.
|
|
21
32
|
*
|
|
22
|
-
* Written as the engine reads it: `{ opened, fields: { field: { shown, declined, held } } }
|
|
33
|
+
* Written as the engine reads it: `{ opened, fields: { field: { shown, declined, held } } }`, and — only what was
|
|
34
|
+
* reported — `opened_at`, `saved_at` (milliseconds on one steady clock), `arrival` (the fields in the order their values
|
|
35
|
+
* arrived), `by` and `batch`, and in a field `layer`, `promise`, `at` and `withheld`.
|
|
23
36
|
*/
|
|
24
37
|
export declare class Draft {
|
|
25
38
|
readonly opened: Values;
|
|
26
39
|
readonly fields: Record<string, Track>;
|
|
40
|
+
private openedAt?;
|
|
41
|
+
private savedAt?;
|
|
42
|
+
private arrival;
|
|
43
|
+
private by?;
|
|
44
|
+
private batch?;
|
|
27
45
|
/** A record opened holding `values` — none for a new one. Empty values are left out. */
|
|
28
46
|
constructor(values?: Values);
|
|
29
47
|
/** A suggestion came into view for the field. A suggestion turned down earlier stays turned down only if it is this. */
|
|
30
|
-
show(field: string, suggestion: Suggestion): void;
|
|
31
|
-
/**
|
|
32
|
-
|
|
48
|
+
show(field: string, suggestion: Suggestion, reported?: Reported): void;
|
|
49
|
+
/**
|
|
50
|
+
* The field's answer is held back for a check, drawn with `probability`: what the person settles is checked.
|
|
51
|
+
* `withheld` is the answer not shown, with its layer and promise, for the record to say what was held back.
|
|
52
|
+
*/
|
|
53
|
+
hold(field: string, probability: number, withheld?: {
|
|
54
|
+
suggestion: Suggestion;
|
|
55
|
+
} & Reported): void;
|
|
56
|
+
/** The record was opened at `ms` on the session's clock (a steady clock in milliseconds: only differences are read). */
|
|
57
|
+
openAt(ms: number): void;
|
|
58
|
+
/** The record is being saved at `ms` on the same clock. */
|
|
59
|
+
saveAt(ms: number): void;
|
|
60
|
+
/** The person reached the field, or its suggestion came into view, at `ms`. Only the first report for a field counts. */
|
|
61
|
+
reach(field: string, ms: number): void;
|
|
62
|
+
/** The field's current value arrived now: it moves to the end of the order values arrived in. */
|
|
63
|
+
arrived(field: string): void;
|
|
64
|
+
/** The record is settled by the person `id`: an opaque id, one person, one id; not a name. */
|
|
65
|
+
settledBy(id: string): void;
|
|
66
|
+
/** The record is settled in the batch `id`, with the records that came at once from the same source. */
|
|
67
|
+
inBatch(id: string): void;
|
|
68
|
+
private report;
|
|
33
69
|
/** The person turned down the suggestion in view; false, changing nothing, when none was shown. */
|
|
34
70
|
decline(field: string): boolean;
|
|
35
71
|
/** Whether `suggestion` is the one in view for the field and the person turned it down. */
|
|
@@ -39,6 +75,11 @@ export declare class Draft {
|
|
|
39
75
|
toJSON(): {
|
|
40
76
|
opened: Values;
|
|
41
77
|
fields: Record<string, Track>;
|
|
78
|
+
opened_at?: number;
|
|
79
|
+
saved_at?: number;
|
|
80
|
+
arrival?: string[];
|
|
81
|
+
by?: string;
|
|
82
|
+
batch?: string;
|
|
42
83
|
};
|
|
43
84
|
}
|
|
44
85
|
export declare function isEmpty(value: Value | undefined): boolean;
|
package/dist/draft.js
CHANGED
|
@@ -4,27 +4,79 @@
|
|
|
4
4
|
* turns it, with the saved values, into what each decided field teaches — so a value taken from a suggestion is never
|
|
5
5
|
* counted as one typed. It is only bookkeeping: nothing is counted here.
|
|
6
6
|
*
|
|
7
|
-
* Written as the engine reads it: `{ opened, fields: { field: { shown, declined, held } } }
|
|
7
|
+
* Written as the engine reads it: `{ opened, fields: { field: { shown, declined, held } } }`, and — only what was
|
|
8
|
+
* reported — `opened_at`, `saved_at` (milliseconds on one steady clock), `arrival` (the fields in the order their values
|
|
9
|
+
* arrived), `by` and `batch`, and in a field `layer`, `promise`, `at` and `withheld`.
|
|
8
10
|
*/
|
|
9
11
|
export class Draft {
|
|
10
12
|
/** A record opened holding `values` — none for a new one. Empty values are left out. */
|
|
11
13
|
constructor(values = {}) {
|
|
12
14
|
this.fields = {};
|
|
15
|
+
this.arrival = [];
|
|
13
16
|
this.opened = Object.fromEntries(Object.entries(values).filter(([, v]) => !isEmpty(v)));
|
|
14
17
|
}
|
|
15
18
|
/** A suggestion came into view for the field. A suggestion turned down earlier stays turned down only if it is this. */
|
|
16
|
-
show(field, suggestion) {
|
|
19
|
+
show(field, suggestion, reported = {}) {
|
|
17
20
|
const track = (this.fields[field] ??= {});
|
|
18
21
|
if (!sameSuggestion(track.shown, suggestion))
|
|
19
22
|
track.declined = false;
|
|
20
23
|
track.shown = { ...suggestion };
|
|
21
24
|
delete track.held;
|
|
25
|
+
delete track.withheld;
|
|
26
|
+
this.report(track, reported);
|
|
22
27
|
}
|
|
23
|
-
/**
|
|
24
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The field's answer is held back for a check, drawn with `probability`: what the person settles is checked.
|
|
30
|
+
* `withheld` is the answer not shown, with its layer and promise, for the record to say what was held back.
|
|
31
|
+
*/
|
|
32
|
+
hold(field, probability, withheld) {
|
|
25
33
|
if (!(probability > 0 && probability <= 1))
|
|
26
34
|
throw new RangeError('a check is drawn with a probability above 0 and at most 1');
|
|
27
|
-
this.fields[field]
|
|
35
|
+
const at = this.fields[field]?.at;
|
|
36
|
+
const track = { held: probability };
|
|
37
|
+
if (at !== undefined)
|
|
38
|
+
track.at = at;
|
|
39
|
+
if (withheld) {
|
|
40
|
+
track.withheld = { ...withheld.suggestion };
|
|
41
|
+
this.report(track, withheld);
|
|
42
|
+
}
|
|
43
|
+
this.fields[field] = track;
|
|
44
|
+
}
|
|
45
|
+
/** The record was opened at `ms` on the session's clock (a steady clock in milliseconds: only differences are read). */
|
|
46
|
+
openAt(ms) {
|
|
47
|
+
this.openedAt = whole(ms);
|
|
48
|
+
}
|
|
49
|
+
/** The record is being saved at `ms` on the same clock. */
|
|
50
|
+
saveAt(ms) {
|
|
51
|
+
this.savedAt = whole(ms);
|
|
52
|
+
}
|
|
53
|
+
/** The person reached the field, or its suggestion came into view, at `ms`. Only the first report for a field counts. */
|
|
54
|
+
reach(field, ms) {
|
|
55
|
+
const track = (this.fields[field] ??= {});
|
|
56
|
+
track.at ??= whole(ms);
|
|
57
|
+
}
|
|
58
|
+
/** The field's current value arrived now: it moves to the end of the order values arrived in. */
|
|
59
|
+
arrived(field) {
|
|
60
|
+
this.arrival = this.arrival.filter((f) => f !== field);
|
|
61
|
+
this.arrival.push(field);
|
|
62
|
+
}
|
|
63
|
+
/** The record is settled by the person `id`: an opaque id, one person, one id; not a name. */
|
|
64
|
+
settledBy(id) {
|
|
65
|
+
this.by = id;
|
|
66
|
+
}
|
|
67
|
+
/** The record is settled in the batch `id`, with the records that came at once from the same source. */
|
|
68
|
+
inBatch(id) {
|
|
69
|
+
this.batch = id;
|
|
70
|
+
}
|
|
71
|
+
report(track, { layer, promise }) {
|
|
72
|
+
if (layer)
|
|
73
|
+
track.layer = layer;
|
|
74
|
+
else
|
|
75
|
+
delete track.layer;
|
|
76
|
+
if (promise !== undefined)
|
|
77
|
+
track.promise = promise;
|
|
78
|
+
else
|
|
79
|
+
delete track.promise;
|
|
28
80
|
}
|
|
29
81
|
/** The person turned down the suggestion in view; false, changing nothing, when none was shown. */
|
|
30
82
|
decline(field) {
|
|
@@ -46,9 +98,21 @@ export class Draft {
|
|
|
46
98
|
return this.fields[field]?.held;
|
|
47
99
|
}
|
|
48
100
|
toJSON() {
|
|
49
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
opened: this.opened,
|
|
103
|
+
fields: this.fields,
|
|
104
|
+
...(this.openedAt !== undefined ? { opened_at: this.openedAt } : {}),
|
|
105
|
+
...(this.savedAt !== undefined ? { saved_at: this.savedAt } : {}),
|
|
106
|
+
...(this.arrival.length > 0 ? { arrival: this.arrival } : {}),
|
|
107
|
+
...(this.by !== undefined ? { by: this.by } : {}),
|
|
108
|
+
...(this.batch !== undefined ? { batch: this.batch } : {}),
|
|
109
|
+
};
|
|
50
110
|
}
|
|
51
111
|
}
|
|
112
|
+
/** A time the engine reads: whole milliseconds, never negative. */
|
|
113
|
+
function whole(ms) {
|
|
114
|
+
return Math.max(0, Math.round(ms));
|
|
115
|
+
}
|
|
52
116
|
function sameSuggestion(a, b) {
|
|
53
117
|
return a !== undefined && a.value === b.value && (a.origin ?? 'decider') === (b.origin ?? 'decider') && (a.fit ?? 'exact') === (b.fit ?? 'exact');
|
|
54
118
|
}
|
package/dist/lowline-form.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export declare class LowlineForm extends LitElement {
|
|
|
29
29
|
rate: number;
|
|
30
30
|
/** The words the form shows, in the host's language. */
|
|
31
31
|
strings: Strings;
|
|
32
|
+
/** Who is writing, as an opaque id the host gives — one person, one id; not a name. Sent with the save when set. */
|
|
33
|
+
settledBy: string;
|
|
34
|
+
/** The batch the record is written in, when several come at once from one source. Sent with the save when set. */
|
|
35
|
+
batch: string;
|
|
32
36
|
private formdown?;
|
|
33
37
|
private session?;
|
|
34
38
|
private asking?;
|
package/dist/lowline-form.js
CHANGED
|
@@ -41,11 +41,16 @@ let LowlineForm = class LowlineForm extends LitElement {
|
|
|
41
41
|
this.rate = 0.05;
|
|
42
42
|
/** The words the form shows, in the host's language. */
|
|
43
43
|
this.strings = englishStrings;
|
|
44
|
+
/** Who is writing, as an opaque id the host gives — one person, one id; not a name. Sent with the save when set. */
|
|
45
|
+
this.settledBy = '';
|
|
46
|
+
/** The batch the record is written in, when several come at once from one source. Sent with the save when set. */
|
|
47
|
+
this.batch = '';
|
|
44
48
|
this.client = new LowlineClient(this.src);
|
|
45
49
|
this.fieldStates = {};
|
|
46
50
|
/** Whether the host judges what the records could not answer — until its `judge` route says it has no model. */
|
|
47
51
|
this.canJudge = true;
|
|
48
52
|
this.changed = () => {
|
|
53
|
+
this.session?.took(this.values());
|
|
49
54
|
clearTimeout(this.asking);
|
|
50
55
|
this.asking = setTimeout(() => void this.ask(), ASK_DELAY_MS);
|
|
51
56
|
};
|
|
@@ -114,6 +119,8 @@ let LowlineForm = class LowlineForm extends LitElement {
|
|
|
114
119
|
if (!this.session || !this.form || !this.record)
|
|
115
120
|
throw new Error('lowline-form needs a form and a record to save');
|
|
116
121
|
const values = this.values();
|
|
122
|
+
this.session.took(values);
|
|
123
|
+
this.session.beforeSave(this.settledBy || undefined, this.batch || undefined);
|
|
117
124
|
const saved = await this.client.settle(this.form, this.record, this.session.draft, sendValues ? values : undefined);
|
|
118
125
|
this.session.saved(values);
|
|
119
126
|
this.dispatchEvent(new CustomEvent('lowline-saved', { detail: { ...saved, values }, bubbles: true, composed: true }));
|
|
@@ -157,8 +164,10 @@ let LowlineForm = class LowlineForm extends LitElement {
|
|
|
157
164
|
const formdown = this.formdown;
|
|
158
165
|
if (!formdown)
|
|
159
166
|
return;
|
|
160
|
-
if (Object.keys(round.prefill).length > 0)
|
|
167
|
+
if (Object.keys(round.prefill).length > 0) {
|
|
161
168
|
formdown.updateData({ ...formdown.data, ...round.prefill });
|
|
169
|
+
session.took(this.values());
|
|
170
|
+
}
|
|
162
171
|
formdown.fieldStates = this.fieldStates;
|
|
163
172
|
if (this.canJudge)
|
|
164
173
|
void this.judge(session, values, shown);
|
|
@@ -213,6 +222,12 @@ __decorate([
|
|
|
213
222
|
__decorate([
|
|
214
223
|
property({ attribute: false })
|
|
215
224
|
], LowlineForm.prototype, "strings", void 0);
|
|
225
|
+
__decorate([
|
|
226
|
+
property({ attribute: 'settled-by' })
|
|
227
|
+
], LowlineForm.prototype, "settledBy", void 0);
|
|
228
|
+
__decorate([
|
|
229
|
+
property()
|
|
230
|
+
], LowlineForm.prototype, "batch", void 0);
|
|
216
231
|
__decorate([
|
|
217
232
|
query('formdown-ui')
|
|
218
233
|
], LowlineForm.prototype, "formdown", void 0);
|
package/dist/session.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface Proposal {
|
|
|
23
23
|
asks?: [string, string][];
|
|
24
24
|
/** For `together`: the fields of its group, still open, that are shown no suggestion. */
|
|
25
25
|
with?: string[];
|
|
26
|
+
/** The group of fields settled together the field is in, if any: a field held back for a check holds its group's back. */
|
|
27
|
+
together?: string | null;
|
|
26
28
|
/**
|
|
27
29
|
* How often an answer of this kind for this field has been right — the replay's precision at the threshold the
|
|
28
30
|
* suggestion was held to. Absent with no suggestion, for a judgement or a value from elsewhere, and before thresholds
|
|
@@ -66,6 +68,8 @@ export declare class Session {
|
|
|
66
68
|
readonly rate: number;
|
|
67
69
|
private readonly random;
|
|
68
70
|
private readonly strings;
|
|
71
|
+
/** A steady clock in milliseconds (`performance.now()` by default): the record's times are read from it. */
|
|
72
|
+
private readonly clock;
|
|
69
73
|
draft: Draft;
|
|
70
74
|
private drawn;
|
|
71
75
|
/** Fields whose value the form put in unasked, from the suggestion still shown. */
|
|
@@ -74,7 +78,18 @@ export declare class Session {
|
|
|
74
78
|
private written;
|
|
75
79
|
/** The judgement asked for each field, by what it reads; `null` while the host is still judging. */
|
|
76
80
|
private judgements;
|
|
77
|
-
|
|
81
|
+
/** The values the form held when it was last looked at, to tell which fields' values arrived since. */
|
|
82
|
+
private seen;
|
|
83
|
+
constructor(id: string, opened: Values, rate?: number, random?: () => number, strings?: Strings,
|
|
84
|
+
/** A steady clock in milliseconds (`performance.now()` by default): the record's times are read from it. */
|
|
85
|
+
clock?: () => number);
|
|
86
|
+
/**
|
|
87
|
+
* The form's values are `values` now: each field whose value is new since the last look arrived, and the person has
|
|
88
|
+
* reached it. A value the form put in unasked arrives too — it is when the record came to hold it.
|
|
89
|
+
*/
|
|
90
|
+
took(values: Values): void;
|
|
91
|
+
/** The record is about to be saved by `by`, in `batch`: the draft says so, with the time. */
|
|
92
|
+
beforeSave(by?: string, batch?: string): void;
|
|
78
93
|
/**
|
|
79
94
|
* Takes a round of proposals for the form's `values` now. A field offered a value from the person's writing keeps it
|
|
80
95
|
* in view until the person picks it or turns it down.
|
package/dist/session.js
CHANGED
|
@@ -12,11 +12,14 @@ export const englishStrings = {
|
|
|
12
12
|
* is looked at, and only for a value the decider suggests.
|
|
13
13
|
*/
|
|
14
14
|
export class Session {
|
|
15
|
-
constructor(id, opened, rate = 0.05, random = Math.random, strings = englishStrings
|
|
15
|
+
constructor(id, opened, rate = 0.05, random = Math.random, strings = englishStrings,
|
|
16
|
+
/** A steady clock in milliseconds (`performance.now()` by default): the record's times are read from it. */
|
|
17
|
+
clock = () => performance.now()) {
|
|
16
18
|
this.id = id;
|
|
17
19
|
this.rate = rate;
|
|
18
20
|
this.random = random;
|
|
19
21
|
this.strings = strings;
|
|
22
|
+
this.clock = clock;
|
|
20
23
|
this.drawn = new Set();
|
|
21
24
|
/** Fields whose value the form put in unasked, from the suggestion still shown. */
|
|
22
25
|
this.filled = new Set();
|
|
@@ -25,6 +28,30 @@ export class Session {
|
|
|
25
28
|
/** The judgement asked for each field, by what it reads; `null` while the host is still judging. */
|
|
26
29
|
this.judgements = new Map();
|
|
27
30
|
this.draft = new Draft(opened);
|
|
31
|
+
this.draft.openAt(this.clock());
|
|
32
|
+
this.seen = { ...this.draft.opened };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The form's values are `values` now: each field whose value is new since the last look arrived, and the person has
|
|
36
|
+
* reached it. A value the form put in unasked arrives too — it is when the record came to hold it.
|
|
37
|
+
*/
|
|
38
|
+
took(values) {
|
|
39
|
+
const now = this.clock();
|
|
40
|
+
for (const [field, value] of Object.entries(values)) {
|
|
41
|
+
if (isEmpty(value) || sameValue(this.seen[field], value))
|
|
42
|
+
continue;
|
|
43
|
+
this.draft.arrived(field);
|
|
44
|
+
this.draft.reach(field, now);
|
|
45
|
+
}
|
|
46
|
+
this.seen = { ...values };
|
|
47
|
+
}
|
|
48
|
+
/** The record is about to be saved by `by`, in `batch`: the draft says so, with the time. */
|
|
49
|
+
beforeSave(by, batch) {
|
|
50
|
+
this.draft.saveAt(this.clock());
|
|
51
|
+
if (by)
|
|
52
|
+
this.draft.settledBy(by);
|
|
53
|
+
if (batch)
|
|
54
|
+
this.draft.inBatch(batch);
|
|
28
55
|
}
|
|
29
56
|
/**
|
|
30
57
|
* Takes a round of proposals for the form's `values` now. A field offered a value from the person's writing keeps it
|
|
@@ -32,18 +59,25 @@ export class Session {
|
|
|
32
59
|
*/
|
|
33
60
|
apply(proposals, values) {
|
|
34
61
|
const round = { fieldStates: Object.fromEntries(this.written), prefill: {} };
|
|
62
|
+
for (const proposal of proposals) {
|
|
63
|
+
if (proposal.suggestion && !this.written.has(proposal.field))
|
|
64
|
+
this.draw(proposal);
|
|
65
|
+
}
|
|
66
|
+
// A field held back for a check holds back the open fields of its group: the person settling it with nothing in view
|
|
67
|
+
// is not shown what is suggested alongside it. Only the held field is the check.
|
|
68
|
+
const held = proposals.filter((p) => this.draft.held(p.field) !== undefined);
|
|
69
|
+
const hidden = (p) => held.some((h) => h.field === p.field || (h.together != null && h.together === p.together));
|
|
35
70
|
for (const proposal of proposals) {
|
|
36
71
|
const { field, suggestion } = proposal;
|
|
37
72
|
if (!suggestion || this.written.has(field))
|
|
38
73
|
continue;
|
|
39
|
-
if (
|
|
40
|
-
continue;
|
|
41
|
-
if (this.draft.held(field) !== undefined)
|
|
74
|
+
if (hidden(proposal))
|
|
42
75
|
continue;
|
|
43
76
|
// Turned down, it stays down for this record: neither shown again nor put back in.
|
|
44
77
|
if (this.draft.declined(field, suggestion))
|
|
45
78
|
continue;
|
|
46
|
-
this.draft.show(field, suggestion);
|
|
79
|
+
this.draft.show(field, suggestion, proposal);
|
|
80
|
+
this.draft.reach(field, this.clock());
|
|
47
81
|
if (proposal.prefill && isEmpty(values[field])) {
|
|
48
82
|
round.prefill[field] = suggestion.value;
|
|
49
83
|
this.filled.add(field);
|
|
@@ -133,13 +167,18 @@ export class Session {
|
|
|
133
167
|
/** The record was saved holding `values`: what is written next starts from them. */
|
|
134
168
|
saved(values) {
|
|
135
169
|
this.draft = new Draft(values);
|
|
170
|
+
this.draft.openAt(this.clock());
|
|
171
|
+
this.seen = { ...this.draft.opened };
|
|
136
172
|
this.drawn.clear();
|
|
137
173
|
this.filled.clear();
|
|
138
174
|
this.written.clear();
|
|
139
175
|
this.judgements.clear();
|
|
140
176
|
}
|
|
141
177
|
/** Whether the field's answer is held back now — drawn the first time a checkable value is proposed for it. */
|
|
142
|
-
draw(
|
|
178
|
+
draw(proposal) {
|
|
179
|
+
const { field, suggestion } = proposal;
|
|
180
|
+
if (!suggestion)
|
|
181
|
+
return false;
|
|
143
182
|
if (this.drawn.has(field))
|
|
144
183
|
return this.draft.held(field) !== undefined;
|
|
145
184
|
const checkable = (suggestion.origin ?? 'decider') === 'decider' && (suggestion.fit ?? 'exact') === 'exact';
|
|
@@ -147,7 +186,7 @@ export class Session {
|
|
|
147
186
|
return false;
|
|
148
187
|
this.drawn.add(field);
|
|
149
188
|
if (this.rate > 0 && this.rate <= 1 && this.random() < this.rate) {
|
|
150
|
-
this.draft.hold(field, this.rate);
|
|
189
|
+
this.draft.hold(field, this.rate, { ...proposal, suggestion });
|
|
151
190
|
return true;
|
|
152
191
|
}
|
|
153
192
|
return false;
|
|
@@ -167,6 +206,9 @@ export class Session {
|
|
|
167
206
|
return undefined;
|
|
168
207
|
}
|
|
169
208
|
}
|
|
209
|
+
function sameValue(a, b) {
|
|
210
|
+
return JSON.stringify(a ?? '') === JSON.stringify(b);
|
|
211
|
+
}
|
|
170
212
|
/** What a proposal asking for a judgement reads, as one text. */
|
|
171
213
|
function reads(proposal) {
|
|
172
214
|
return JSON.stringify(proposal.asks ?? []);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/lowline-form",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The Lowline form element: a formdown form whose decided fields are suggested from the records people confirmed, talking to a Lowline host's routes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"bundle": "vite build --config vite.bundle.config.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@formdown/ui": "^0.
|
|
43
|
+
"@formdown/ui": "^0.26.0",
|
|
44
44
|
"lit": "^3.3.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"typescript": "^6.0.3",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
48
|
+
"vite": "^8.3.1",
|
|
49
|
+
"vitest": "^5.0.2"
|
|
50
50
|
}
|
|
51
51
|
}
|