@bongos/core 1.24.27 → 1.24.28
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/.bongos-core.json +40 -35
- package/docs/copy-inventory.md +175 -175
- package/docs/copy-registry.json +280 -280
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +9 -3
- package/docs/page-readings.json +277 -214
- package/modules/hall-ui/public/my-agents.css +72 -1
- package/modules/hall-ui/public/my-agents.html +30 -6
- package/modules/hall-ui/public/my-agents.js +63 -54
- package/modules/hall-ui/public/my-agents.states.json +18 -0
- package/modules/hall-ui/public/settings.css +2 -132
- package/modules/hall-ui/public/settings.html +8 -48
- package/modules/hall-ui/public/speciality-tools.css +140 -0
- package/modules/hall-ui/public/{settings-specialities.js → speciality-tools.js} +83 -6
- package/modules/hall-ui/records/my-agents.md +4 -2
- package/modules/hall-ui/records/panel-family.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/hall-preview/server.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/hall_loading.mjs +3 -1
- package/tests/hall_my_agents.mjs +104 -2
- package/tests/helpers.mjs +161 -0
- package/tests/speciality_offer_ui.mjs +182 -149
- package/tests/speciality_walkthrough_ui.mjs +180 -161
|
@@ -164,9 +164,12 @@ html[data-theme="dark"] body[data-page="my-agents"] {
|
|
|
164
164
|
.ma-group { margin-top: var(--sp-md); }
|
|
165
165
|
.ma-group:first-child { margin-top: 4px; }
|
|
166
166
|
.ma-rows { list-style: none; margin: 0; padding: 0; }
|
|
167
|
+
/* the fact keeps a floor (task 1005052): a row with more chips than fit beside it
|
|
168
|
+
— session cards, with its way back to the profile — wraps its chips instead of
|
|
169
|
+
squeezing the fact to a word a line */
|
|
167
170
|
.ma-row {
|
|
168
171
|
display: grid;
|
|
169
|
-
grid-template-columns: minmax(118px, 160px) minmax(
|
|
172
|
+
grid-template-columns: minmax(118px, 160px) minmax(132px, 1fr) auto;
|
|
170
173
|
gap: 6px 18px;
|
|
171
174
|
align-items: center;
|
|
172
175
|
min-height: 48px;
|
|
@@ -241,6 +244,74 @@ html[data-theme="dark"] body[data-page="my-agents"] {
|
|
|
241
244
|
.ma-row--sub { min-height: 40px; padding: 4px 0; }
|
|
242
245
|
.ma-row--sub .ma-row__fact { font-size: 13px; }
|
|
243
246
|
|
|
247
|
+
/* ---- what it knows: the speciality tools (task 1005052) ----
|
|
248
|
+
The SAME component Settings mounts (speciality-tools.js; its structure in
|
|
249
|
+
speciality-tools.css, linked before this sheet). Its markup is named in the
|
|
250
|
+
panel family's words (.prow, .btn, .pbtn) and this page has no panel.css, so
|
|
251
|
+
this skin draws those names in this page's own look: a hairline row like the
|
|
252
|
+
desk's, ghost pills, a chosen walkthrough answer as ink on glass. No accent —
|
|
253
|
+
on this page salmon would mean "needs you", and nothing here does. */
|
|
254
|
+
.ma-col { display: flex; flex-direction: column; gap: var(--sp-md); min-width: 0; }
|
|
255
|
+
.ma-know .prows { list-style: none; margin: 4px 0 0; padding: 0; }
|
|
256
|
+
.ma-know .prow { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 8px 18px; padding: 12px 0; border-top: var(--line) solid var(--rule-soft); }
|
|
257
|
+
.ma-know .prows > .prow:first-child { border-top: 0; }
|
|
258
|
+
.ma-know .prow__main { flex: 1 1 280px; min-width: 0; }
|
|
259
|
+
.ma-know .prow__label { display: flex; flex-wrap: wrap; align-items: baseline; margin: 0; font-size: 14.5px; font-weight: 500; letter-spacing: -0.01em; color: var(--ink); }
|
|
260
|
+
.ma-know .prow__hint { margin: 4px 0 0; max-width: var(--measure); font-size: 13px; line-height: 1.5; color: var(--ink-soft); }
|
|
261
|
+
.ma-know .prow__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
|
|
262
|
+
.ma-know .speciality-create__h { margin: 4px 0 0; font-size: 14px; font-weight: 500; color: var(--ink); }
|
|
263
|
+
.ma-know .btn,
|
|
264
|
+
.ma-know .pbtn {
|
|
265
|
+
display: inline-flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
min-height: 30px;
|
|
269
|
+
min-width: 30px;
|
|
270
|
+
padding: 0 14px;
|
|
271
|
+
border: var(--line) solid var(--rule);
|
|
272
|
+
border-radius: var(--radius-pill);
|
|
273
|
+
background: none;
|
|
274
|
+
color: var(--ink-soft);
|
|
275
|
+
font: inherit;
|
|
276
|
+
font-size: 13px;
|
|
277
|
+
font-weight: 400;
|
|
278
|
+
text-transform: lowercase;
|
|
279
|
+
white-space: nowrap;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
}
|
|
282
|
+
.ma-know .btn:hover:not(:disabled),
|
|
283
|
+
.ma-know .pbtn:hover:not(:disabled) { background: none; color: var(--ink); border-color: var(--ink-faint); }
|
|
284
|
+
.ma-know .btn:disabled,
|
|
285
|
+
.ma-know .pbtn:disabled { background: none; color: var(--ink-faint); border-color: var(--rule-soft); cursor: default; }
|
|
286
|
+
/* the walkthrough's answer you gave, and its one commit, read as a chosen chip */
|
|
287
|
+
.ma-know .pbtn[aria-pressed="true"],
|
|
288
|
+
.ma-know .pbtn--save:not(:disabled) { background: var(--glass-strong); color: var(--ink); border-color: var(--ink-soft); }
|
|
289
|
+
/* the 24px floor, drawn in ink: the create form's "for sale" box and the skill picker */
|
|
290
|
+
.ma-know input[type="checkbox"] { width: 24px; height: 24px; margin: 0; flex: none; accent-color: var(--ink); cursor: pointer; }
|
|
291
|
+
.ma-know .speciality-field > span { color: var(--ink-soft); }
|
|
292
|
+
/* "create a speciality": the form folds away until asked for, so the section opens on what you have */
|
|
293
|
+
.ma-know__new { margin-top: 10px; padding-top: 12px; border-top: var(--line) solid var(--rule-soft); }
|
|
294
|
+
.ma-know__new > summary {
|
|
295
|
+
display: inline-flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
min-height: 30px;
|
|
298
|
+
padding: 0 14px;
|
|
299
|
+
border: var(--line) solid var(--rule);
|
|
300
|
+
border-radius: var(--radius-pill);
|
|
301
|
+
font-size: 13px;
|
|
302
|
+
color: var(--ink-soft);
|
|
303
|
+
list-style: none;
|
|
304
|
+
cursor: pointer;
|
|
305
|
+
}
|
|
306
|
+
.ma-know__new > summary::-webkit-details-marker { display: none; }
|
|
307
|
+
.ma-know__new > summary:hover { color: var(--ink); border-color: var(--ink-faint); }
|
|
308
|
+
.ma-know__new[open] > summary { color: var(--ink); background: var(--glass-strong); border-color: var(--ink-soft); }
|
|
309
|
+
.ma-know__new .speciality-create { margin-top: 4px; padding-top: 0; border-top: 0; }
|
|
310
|
+
/* the fold's own label already names it, so the form's "Create one" would say it twice */
|
|
311
|
+
.ma-know__new .speciality-create > .speciality-create__h:first-child { display: none; }
|
|
312
|
+
.ma-know__status { margin: 8px 0 0; font-size: 13px; color: var(--ink-soft); }
|
|
313
|
+
.ma-know__status:empty { display: none; }
|
|
314
|
+
|
|
244
315
|
/* ---- what the agent reads first ---- */
|
|
245
316
|
.ma-read { margin: 0 0 12px; }
|
|
246
317
|
.ma-read--project { padding: 12px 14px; background: var(--ma-law-wash); border-radius: var(--radius-sm); }
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
group head and the foot line (records/oversight-family.md). This page's
|
|
14
14
|
repeated thing is that row at the desk's one-line density. -->
|
|
15
15
|
<link rel="stylesheet" href="/builders/oversight.css">
|
|
16
|
+
<!-- the speciality tools' structural sheet, shared with Settings (task 1005052);
|
|
17
|
+
before this page's own, whose .ma-know skin draws them in this page's look -->
|
|
18
|
+
<link rel="stylesheet" href="/builders/speciality-tools.css">
|
|
16
19
|
<link rel="stylesheet" href="/builders/my-agents.css">
|
|
17
20
|
</head>
|
|
18
21
|
<body data-page="my-agents" data-page-title="My agents">
|
|
@@ -38,11 +41,30 @@
|
|
|
38
41
|
<section class="ma-strict" id="ma-strict" aria-labelledby="ma-strict-h" hidden></section>
|
|
39
42
|
|
|
40
43
|
<div class="ma-grid">
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
<div class="ma-col">
|
|
45
|
+
<section class="scroll ma-desk" id="ma-desk" aria-labelledby="ma-desk-h">
|
|
46
|
+
<h2 class="scroll__h" id="ma-desk-h">Your agents</h2>
|
|
47
|
+
<p class="scroll__lede" id="ma-desk-lede">One line each. Tap to change; it saves straight away.</p>
|
|
48
|
+
<div id="ma-rows" aria-live="polite"><p class="hall-loading" role="status">Reading your settings…</p></div>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<!-- task 1005052: WHAT IT KNOWS — every speciality tool Settings has, from
|
|
52
|
+
the SAME component (speciality-tools.js fills these hosts by id). A
|
|
53
|
+
section of its own, not desk rows: a desk row holds at most two
|
|
54
|
+
buttons, and my-agents.js redraws the desk on every change. Ships
|
|
55
|
+
hidden; the component reveals it once its reads answer. -->
|
|
56
|
+
<section class="scroll ma-know" id="specialities-scroll" aria-labelledby="specialities-h" hidden>
|
|
57
|
+
<h2 class="scroll__h" id="specialities-h">What it knows</h2>
|
|
58
|
+
<p class="scroll__lede">A speciality is what you work on, and how you want to be asked about it. The one you use reaches every session where you hold work in its role.</p>
|
|
59
|
+
<div id="speciality-suggestions"></div>
|
|
60
|
+
<ul class="prows" id="specialities-rows"></ul>
|
|
61
|
+
<details class="ma-know__new">
|
|
62
|
+
<summary>create a speciality</summary>
|
|
63
|
+
<form id="speciality-create" class="speciality-create"></form>
|
|
64
|
+
</details>
|
|
65
|
+
<p class="ma-know__status" id="specialities-status" role="status" aria-live="polite"></p>
|
|
66
|
+
</section>
|
|
67
|
+
</div>
|
|
46
68
|
|
|
47
69
|
<aside class="scroll ma-reads" id="ma-reads" aria-labelledby="ma-reads-h">
|
|
48
70
|
<h2 class="scroll__h" id="ma-reads-h">What your agent reads first</h2>
|
|
@@ -67,6 +89,8 @@
|
|
|
67
89
|
<script src="/builders/palette.js" defer></script>
|
|
68
90
|
<script src="/builders/gov-living.js?v=2026-10-10-real1"></script>
|
|
69
91
|
<script src="/builders/gov-positions.js?v=2026-10-09-positions1"></script>
|
|
70
|
-
<script src="/builders/my-agents.js?v=2026-
|
|
92
|
+
<script src="/builders/my-agents.js?v=2026-11-03-defaults"></script>
|
|
93
|
+
<!-- the speciality tools (task 1005052): the one component Settings mounts too -->
|
|
94
|
+
<script src="/builders/speciality-tools.js?v=2026-11-03-shared"></script>
|
|
71
95
|
</body>
|
|
72
96
|
</html>
|
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
// yours. Every row is one of your agent settings, and a project law can LOCK it.
|
|
8
8
|
// Every part of it is a real read (task 1004717: the prototype overlay is gone):
|
|
9
9
|
// real EVERY ROW (task 1004714). GET /me's interaction profile, wandering,
|
|
10
|
-
// model allocation and session cards; GET /me/skill-prefs;
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// /
|
|
15
|
-
//
|
|
10
|
+
// model allocation and session cards; GET /me/skill-prefs; the sounds
|
|
11
|
+
// switch (off by the owner's call, .claude/sounds/play.js). Writes go to
|
|
12
|
+
// the real routes (PATCH /me/interaction|wandering|model-allocation|
|
|
13
|
+
// render|skill-prefs), so in the preview harness they reach its recorder
|
|
14
|
+
// (scripts/hall-preview/pref-writes.js). Session cards and each skill
|
|
15
|
+
// voice can go BACK to the default (task 1005052): the first chip clears
|
|
16
|
+
// your own pick (PATCH ... null), as Settings' "default" choices do.
|
|
17
|
+
// real WHAT IT KNOWS (task 1005052): every speciality tool Settings has —
|
|
18
|
+
// create, train, choose what it offers, adopt (its instructions read in
|
|
19
|
+
// full first), the skills walkthrough, stop using. Not this file: the
|
|
20
|
+
// SAME component Settings mounts (speciality-tools.js) fills its own
|
|
21
|
+
// section; this page only re-reads /me when it says a speciality changed,
|
|
22
|
+
// so "What your agent reads first" stays true.
|
|
16
23
|
// real the project's laws and the locks they put on your settings (task
|
|
17
24
|
// 1004707): GET /me's `laws` block — the very words every session reads —
|
|
18
25
|
// and `laws.locks`, what each lock did to you, enforced by the server. A
|
|
@@ -57,7 +64,7 @@
|
|
|
57
64
|
'model.main': { name: 'Main session' },
|
|
58
65
|
'model.subagentDefault': { name: 'Helpers' },
|
|
59
66
|
'model.subagentRoutine': { name: 'Routine helpers' },
|
|
60
|
-
render: { name: 'Session cards', words: { minimal: 'one line', text: 'plain text', standard: 'rich card', rich: 'rich, open' } },
|
|
67
|
+
render: { name: 'Session cards', words: { '': 'follow my profile', minimal: 'one line', text: 'plain text', standard: 'rich card', rich: 'rich, open' } },
|
|
61
68
|
};
|
|
62
69
|
// The law a habit would become, in the ask-format voice: a short plain sentence.
|
|
63
70
|
const LAW_LINES = {
|
|
@@ -70,7 +77,9 @@
|
|
|
70
77
|
{ key: 'talk', title: 'How it talks to you', keys: ['technical_level', 'toolchain_familiarity', 'verbosity', 'machinery', 'render', 'sounds'] },
|
|
71
78
|
{ key: 'work', title: 'How your agents work', keys: ['wandering'] },
|
|
72
79
|
{ key: 'models', title: 'Which models run', keys: ['model.main', 'model.subagentDefault', 'model.subagentRoutine', 'skills'] },
|
|
73
|
-
|
|
80
|
+
// 'What it knows' (the specialities) is a section of its own since task
|
|
81
|
+
// 1005052: its tools are more than a desk row's two buttons, and they are the
|
|
82
|
+
// shared speciality-tools.js component, which this desk's redraw would wipe.
|
|
74
83
|
];
|
|
75
84
|
// the permission atom that may file a laws vote (POST /government/board/laws)
|
|
76
85
|
const PROPOSE_ATOM = 'board.item.open';
|
|
@@ -86,8 +95,8 @@
|
|
|
86
95
|
const nameFor = (key) => (SETTING[key] ? SETTING[key].name : key);
|
|
87
96
|
|
|
88
97
|
// ---------- the pure view ----------
|
|
89
|
-
// buildView({ me, constitution, presets, skills,
|
|
90
|
-
// proposed, switched,
|
|
98
|
+
// buildView({ me, constitution, presets, skills, preview,
|
|
99
|
+
// proposed, switched, as, project, now }, { GovLiving, GovPositions })
|
|
91
100
|
// → { project, positions, strict, groups[], habits[], canPropose, reads, counts }
|
|
92
101
|
// `preview` is a strictness preset key to look at instead of the project's own;
|
|
93
102
|
// `presets` is GET /government/presets' body (task 1004715); `switched` maps a
|
|
@@ -132,9 +141,16 @@
|
|
|
132
141
|
if (!r) return null;
|
|
133
142
|
const levels = arr(r.levels).map((l) => l.value);
|
|
134
143
|
const level = str(r.level || r.derived_level);
|
|
144
|
+
// task 1005052: your own pick, or none — then the cards follow the level
|
|
145
|
+
// your interaction profile derives, and "follow my profile" (value '',
|
|
146
|
+
// written as null) is the chip pressed. A pinned level survives a later
|
|
147
|
+
// profile change; following does not pin today's.
|
|
148
|
+
const own = r.override_level == null ? null : str(r.override_level);
|
|
135
149
|
return {
|
|
136
|
-
key, name: nameFor(key), fact: wordFor(key, level),
|
|
137
|
-
|
|
150
|
+
key, name: nameFor(key), fact: wordFor(key, level), note: own === null ? 'follows your profile' : null,
|
|
151
|
+
locked: false, lock: null, cap: null, control: 'chips', value: own === null ? '' : own, effective: level,
|
|
152
|
+
options: [{ value: '', label: wordFor(key, ''), pressed: own === null }]
|
|
153
|
+
.concat(levels.map((v) => ({ value: v, label: wordFor(key, v), pressed: v === own }))),
|
|
138
154
|
};
|
|
139
155
|
}
|
|
140
156
|
if (key === 'sounds') {
|
|
@@ -149,19 +165,17 @@
|
|
|
149
165
|
return {
|
|
150
166
|
key, name: 'Per skill', fact: eff.length + ' skills' + (changed ? ' · ' + changed + ' yours' : ' · all default'),
|
|
151
167
|
locked: false, lock: null, cap: null, control: 'expand', options: [],
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
key, name: 'Specialities', fact: adopted.length ? adopted.join(', ') : 'none yet', locked: false, lock: null, cap: null, control: 'adopt',
|
|
164
|
-
options: offer.map((x) => ({ value: String(x.id), label: str(x.name).toLowerCase(), pressed: adopted.indexOf(str(x.name)) >= 0 })),
|
|
168
|
+
// task 1005052: "default" (value '', written as null) clears your own
|
|
169
|
+
// pick, as Settings' Default does; a model is pressed only if you chose it.
|
|
170
|
+
skills: eff.map((e) => {
|
|
171
|
+
const own = e.override_model == null ? null : str(e.override_model);
|
|
172
|
+
return {
|
|
173
|
+
key: 'skill.' + e.skill, skill: e.skill, value: e.effective_model,
|
|
174
|
+
note: own === null ? null : 'default: ' + str(e.default_model),
|
|
175
|
+
options: [{ value: '', label: 'default', pressed: own === null }]
|
|
176
|
+
.concat(arr(sk.allowed_models).map((v) => ({ value: v, label: v, pressed: v === own }))),
|
|
177
|
+
};
|
|
178
|
+
}),
|
|
165
179
|
};
|
|
166
180
|
}
|
|
167
181
|
return null;
|
|
@@ -170,7 +184,7 @@
|
|
|
170
184
|
const rows = g.keys.map((k) => settingRow(k) || extraRow(k)).filter(Boolean);
|
|
171
185
|
return { key: g.key, title: g.title, rows, source: 'real', locked: rows.filter((r) => r.locked).length };
|
|
172
186
|
}).filter((g) => g.rows.length);
|
|
173
|
-
const lockableKeys = groups.reduce((a, g) => a.concat(g.rows.filter((r) => r.key !== 'sounds'
|
|
187
|
+
const lockableKeys = groups.reduce((a, g) => a.concat(g.rows.filter((r) => r.key !== 'sounds').map((r) => r.key)), []);
|
|
174
188
|
const total = lockableKeys.length;
|
|
175
189
|
const lockedNow = groups.reduce((n, g) => n + g.locked, 0);
|
|
176
190
|
|
|
@@ -273,8 +287,9 @@
|
|
|
273
287
|
return n < 0 ? null : { method: 'PATCH', path: '/me/wandering', body: { wandering: n }, slice: 'wandering' };
|
|
274
288
|
}
|
|
275
289
|
if (key.indexOf('model.') === 0) return { method: 'PATCH', path: '/me/model-allocation', body: { [key.slice(6)]: value }, slice: 'model_allocation' };
|
|
276
|
-
|
|
277
|
-
if (key
|
|
290
|
+
// '' is the way back to the default: null clears your own pick (task 1005052)
|
|
291
|
+
if (key === 'render') return { method: 'PATCH', path: '/me/render', body: { widgets: value === '' ? null : value }, slice: 'render' };
|
|
292
|
+
if (key.indexOf('skill.') === 0) return { method: 'PATCH', path: '/me/skill-prefs', body: { [key.slice(6)]: value === '' ? null : value }, slice: 'skills' };
|
|
278
293
|
return null;
|
|
279
294
|
}
|
|
280
295
|
|
|
@@ -322,7 +337,7 @@
|
|
|
322
337
|
const LOCK = '<svg class="ma-lock__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="7" width="9" height="6.5" rx="1.5"/><path d="M5.5 7V5.2a2.5 2.5 0 0 1 5 0V7"/></svg>';
|
|
323
338
|
const TICK = '<svg class="ma-tick" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>';
|
|
324
339
|
|
|
325
|
-
const S = { me: null, constitution: null, presets: null, skills: null,
|
|
340
|
+
const S = { me: null, constitution: null, presets: null, skills: null, preview: null, proposed: {}, pending: {}, switched: {}, skillsOpen: false, readAll: false, view: null };
|
|
326
341
|
|
|
327
342
|
function asNow() {
|
|
328
343
|
try { return root.sessionStorage.getItem('hall-as') === 'nobody' || /[?&]as=nobody\b/.test(root.location.search) ? 'nobody' : null; }
|
|
@@ -330,8 +345,8 @@
|
|
|
330
345
|
}
|
|
331
346
|
function view() {
|
|
332
347
|
S.view = buildView({
|
|
333
|
-
me: S.me, constitution: S.constitution, presets: S.presets, skills: S.skills,
|
|
334
|
-
preview: S.preview, proposed: S.proposed, pending: S.pending, switched: S.switched,
|
|
348
|
+
me: S.me, constitution: S.constitution, presets: S.presets, skills: S.skills,
|
|
349
|
+
preview: S.preview, proposed: S.proposed, pending: S.pending, switched: S.switched, as: asNow(), project: PROJECT, now: Date.now(),
|
|
335
350
|
}, { GovLiving: root.GovLiving, GovPositions: root.GovPositions });
|
|
336
351
|
return S.view;
|
|
337
352
|
}
|
|
@@ -345,10 +360,6 @@
|
|
|
345
360
|
// every lock is a real law, enforced by the server (task 1004714)
|
|
346
361
|
if (row.locked) return '<span class="ma-lock">' + LOCK + '<span>' + esc(row.lock.text) + '</span></span>';
|
|
347
362
|
if (row.control === 'chips') return chipsHtml(row, row.name);
|
|
348
|
-
if (row.control === 'adopt') {
|
|
349
|
-
return row.options.length ? '<div class="ma-chips" role="group" aria-label="Adopt a speciality">' + row.options.map((op) =>
|
|
350
|
-
'<button type="button" class="ma-chip" data-adopt="' + esc(op.value) + '" aria-pressed="' + (op.pressed ? 'true' : 'false') + '"' + (op.pressed ? ' disabled' : '') + '>' + esc(op.pressed ? op.label : '+ ' + op.label) + '</button>').join('') + '</div>' : '';
|
|
351
|
-
}
|
|
352
363
|
if (row.control === 'expand') {
|
|
353
364
|
return '<button type="button" class="btn-ghost ma-more" data-expand="skills" aria-expanded="' + (S.skillsOpen ? 'true' : 'false') + '" aria-controls="ma-skills">' + (S.skillsOpen ? 'hide' : 'show') + '</button>';
|
|
354
365
|
}
|
|
@@ -357,14 +368,15 @@
|
|
|
357
368
|
function rowHtml(row) {
|
|
358
369
|
// a cap (task 1004714): the chips offer the values under it, and the line says which law
|
|
359
370
|
const sub = (row.cap ? '<span class="ma-row__yours ma-row__cap">' + LOCK + 'capped by ' + esc(row.cap.text) + '</span>' : '') +
|
|
360
|
-
(row.yours ? '<span class="ma-row__yours">yours: ' + esc(row.yours) + '</span>' : '')
|
|
371
|
+
(row.yours ? '<span class="ma-row__yours">yours: ' + esc(row.yours) + '</span>' : '') +
|
|
372
|
+
(row.note ? '<span class="ma-row__yours">' + esc(row.note) + '</span>' : '');
|
|
361
373
|
let html = '<li class="ma-row' + (row.locked ? ' ma-row--locked' : '') + '" data-row="' + esc(row.key) + '">' +
|
|
362
374
|
'<span class="ma-row__name">' + esc(row.name) + TICK + '</span>' +
|
|
363
375
|
'<span class="ma-row__fact"><span>' + esc(row.fact) + '</span>' + sub + '</span>' +
|
|
364
376
|
'<span class="ma-row__act">' + controlHtml(row) + '</span></li>';
|
|
365
377
|
if (row.control === 'expand' && S.skillsOpen && !row.locked) {
|
|
366
378
|
html += '<li class="ma-sub" id="ma-skills"><ul class="ma-rows ma-rows--sub">' + row.skills.map((s) =>
|
|
367
|
-
'<li class="ma-row ma-row--sub" data-row="' + esc(s.key) + '"><span class="ma-row__name ma-mono">' + esc(s.skill) + TICK + '</span><span class="ma-row__fact"><span>' + esc(s.value) + '</span
|
|
379
|
+
'<li class="ma-row ma-row--sub" data-row="' + esc(s.key) + '"><span class="ma-row__name ma-mono">' + esc(s.skill) + TICK + '</span><span class="ma-row__fact"><span>' + esc(s.value) + '</span>' + (s.note ? '<span class="ma-row__yours">' + esc(s.note) + '</span>' : '') + '</span>' +
|
|
368
380
|
'<span class="ma-row__act">' + chipsHtml({ key: s.key, options: s.options }, s.skill) + '</span></li>').join('') + '</ul></li>';
|
|
369
381
|
}
|
|
370
382
|
return html;
|
|
@@ -502,7 +514,9 @@
|
|
|
502
514
|
const row = findRow(key);
|
|
503
515
|
if (!row || row.locked) return;
|
|
504
516
|
const w = writeFor(key, value, S);
|
|
505
|
-
const
|
|
517
|
+
const skill = key.indexOf('skill.') === 0 ? key.slice(6) : null;
|
|
518
|
+
const said = value === '' ? (skill ? skill + ' on its default model' : nameFor(key).toLowerCase() + ' follow your profile')
|
|
519
|
+
: (skill || nameFor(key).toLowerCase()) + ' ' + wordFor(key, value);
|
|
506
520
|
if (!w) return;
|
|
507
521
|
api.request(w.method, API + w.path, { body: w.body }).then((r) => {
|
|
508
522
|
if (r && r.ok && r.data) {
|
|
@@ -525,8 +539,8 @@
|
|
|
525
539
|
if (w.slice === 'interaction' && S.me.interaction) { S.me.interaction.effective = Object.assign({}, S.me.interaction.effective, { [key]: value }); S.me.interaction.stored = Object.assign({}, S.me.interaction.stored, { [key]: value }); }
|
|
526
540
|
if (w.slice === 'wandering' && S.me.wandering) { S.me.wandering.effective_key = value; S.me.wandering.override_level = w.body.wandering; }
|
|
527
541
|
if (w.slice === 'model_allocation' && S.me.model_allocation) S.me.model_allocation.effective = Object.assign({}, S.me.model_allocation.effective, w.body);
|
|
528
|
-
if (w.slice === 'render' && S.me.render) S.me.render.level
|
|
529
|
-
if (w.slice === 'skills' && S.skills) arr(S.skills.effective).forEach((e) => { if (e.skill === key.slice(6)) { e.effective_model = value; e.override_model = value; } });
|
|
542
|
+
if (w.slice === 'render' && S.me.render) Object.assign(S.me.render, value === '' ? { level: S.me.render.derived_level, override_level: null } : { level: value, override_level: value });
|
|
543
|
+
if (w.slice === 'skills' && S.skills) arr(S.skills.effective).forEach((e) => { if (e.skill === key.slice(6)) { e.effective_model = value === '' ? e.default_model : value; e.override_model = value === '' ? null : value; } });
|
|
530
544
|
}
|
|
531
545
|
function findRow(key) {
|
|
532
546
|
const v = S.view;
|
|
@@ -539,16 +553,6 @@
|
|
|
539
553
|
}
|
|
540
554
|
return null;
|
|
541
555
|
}
|
|
542
|
-
function adopt(id) {
|
|
543
|
-
const sp = arr(S.specialities && S.specialities.specialities).find((x) => String(x.id) === String(id));
|
|
544
|
-
if (!sp) return;
|
|
545
|
-
api.request('POST', API + '/specialities/' + encodeURIComponent(id) + '/adopt', { body: {} }).then((r) => {
|
|
546
|
-
if (r && r.ok) {
|
|
547
|
-
S.adopted.push(str(sp.name)); render(); lit('specialities');
|
|
548
|
-
toast(sp.name + ' adopted. Its skills stay off until you turn them on.');
|
|
549
|
-
} else toast('Not adopted' + (r && r.status ? ' (' + r.status + ')' : '') + '. Nothing changed.');
|
|
550
|
-
}, () => toast('Not adopted: no answer.'));
|
|
551
|
-
}
|
|
552
556
|
// A habit proposed as a law: a real laws vote (POST /government/board/laws,
|
|
553
557
|
// task 1004706), sat under the constitution in force like any amendment.
|
|
554
558
|
function propose(key) {
|
|
@@ -604,9 +608,15 @@
|
|
|
604
608
|
if (b.hasAttribute('data-set')) { const row = findRow(b.getAttribute('data-set')); const raw = b.getAttribute('data-value'); const opt = row && row.options.find((x) => String(x.value) === raw); if (opt && !opt.pressed) choose(row.key, opt.value); return; }
|
|
605
609
|
if (b.hasAttribute('data-expand')) { S.skillsOpen = !S.skillsOpen; render(); return; }
|
|
606
610
|
if (b.hasAttribute('data-readall')) { S.readAll = !S.readAll; render(); return; }
|
|
607
|
-
if (b.hasAttribute('data-adopt')) { adopt(b.getAttribute('data-adopt')); return; }
|
|
608
611
|
if (b.hasAttribute('data-propose')) { propose(b.getAttribute('data-propose')); }
|
|
609
612
|
});
|
|
613
|
+
// The speciality tools (speciality-tools.js, their own section) say when what
|
|
614
|
+
// a session is sent moved: an adopt, a stop, a skills save. /me carries it,
|
|
615
|
+
// so read it again and "What your agent reads first" stays true (task 1005052).
|
|
616
|
+
document.addEventListener('otb:specialities-changed', async () => {
|
|
617
|
+
const me = await getJSON('/me');
|
|
618
|
+
if (me && me.builder) { S.me = me; render(); }
|
|
619
|
+
});
|
|
610
620
|
}
|
|
611
621
|
|
|
612
622
|
async function boot() {
|
|
@@ -618,15 +628,14 @@
|
|
|
618
628
|
return;
|
|
619
629
|
}
|
|
620
630
|
S.me = me;
|
|
621
|
-
const [constitution, presets, skills,
|
|
622
|
-
getJSON('/government/constitution'), getJSON('/government/presets'), getJSON('/me/skill-prefs'),
|
|
631
|
+
const [constitution, presets, skills, room] = await Promise.all([
|
|
632
|
+
getJSON('/government/constitution'), getJSON('/government/presets'), getJSON('/me/skill-prefs'),
|
|
623
633
|
getJSON('/government/board/items'),
|
|
624
634
|
]);
|
|
625
635
|
S.pending = pendingLaws(room);
|
|
626
636
|
S.presets = presets || null;
|
|
627
637
|
S.constitution = constitution;
|
|
628
638
|
S.skills = skills || null;
|
|
629
|
-
S.specialities = specialities || null;
|
|
630
639
|
render();
|
|
631
640
|
}
|
|
632
641
|
boot();
|
|
@@ -25,6 +25,24 @@
|
|
|
25
25
|
"actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["click", "button[data-set=\"verbosity\"][aria-pressed=\"false\"]"], ["wait", 400]],
|
|
26
26
|
"expect": { "visible": [".ma-row[data-saved] .ma-tick"] }
|
|
27
27
|
},
|
|
28
|
+
"what-it-knows": {
|
|
29
|
+
"auth": true,
|
|
30
|
+
"_": "Task 1005052: the speciality tools, the same component Settings mounts. Someone else's speciality (Economics) shows its full instructions open beside its one adopt control; your own (Pricing, in use) carries choose its skills, train, offered skills and stop using. Session cards above follow the profile.",
|
|
31
|
+
"actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["focus", "#specialities-rows button[data-act=\"adopt\"]"], ["wait", 300]],
|
|
32
|
+
"expect": { "visible": ["#specialities-scroll", ".speciality-contract[open]", "#specialities-rows button[data-act=\"abandon\"]", "#specialities-rows button[data-act=\"walk\"]", "button[data-set=\"render\"][data-value=\"\"][aria-pressed=\"true\"]"] }
|
|
33
|
+
},
|
|
34
|
+
"speciality-walk": {
|
|
35
|
+
"auth": true,
|
|
36
|
+
"_": "Adopting Economics opens the skills walkthrough in its card: one skill per step, leave it off or turn it on at equal weight.",
|
|
37
|
+
"actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["click", "#specialities-rows button[data-act=\"adopt\"]"], ["wait", 700]],
|
|
38
|
+
"expect": { "visible": [".skill-walk", ".skill-walk__facts", "button[data-walk=\"on\"]", "button[data-walk=\"off\"]"] }
|
|
39
|
+
},
|
|
40
|
+
"speciality-forms": {
|
|
41
|
+
"auth": true,
|
|
42
|
+
"_": "The owner's tools open in place: Train on Pricing, and the folded create form with its skills picker.",
|
|
43
|
+
"actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["click", "#specialities-rows button[data-act=\"train\"]"], ["wait", 300], ["click", ".ma-know__new > summary"], ["wait", 300], ["click", "#speciality-create-offer > summary"], ["wait", 900]],
|
|
44
|
+
"expect": { "visible": ["form.speciality-train", "#speciality-create input[name=\"name\"]", "#speciality-create-skills .speciality-offer__list"] }
|
|
45
|
+
},
|
|
28
46
|
"nobody": {
|
|
29
47
|
"auth": true,
|
|
30
48
|
"url": "/my-agents?as=nobody",
|
|
@@ -157,138 +157,8 @@ select.voices__select { min-width: 140px; }
|
|
|
157
157
|
.settings-btn--discord { background: #5865F2; border-color: #4853c4; color: #ffffff; }
|
|
158
158
|
.settings-btn--discord:hover:not(:disabled) { background: #4853c4; border-color: #4853c4; color: #ffffff; }
|
|
159
159
|
|
|
160
|
-
/*
|
|
161
|
-
|
|
162
|
-
create form. Tokens only — no literal colours, so light/dark follow the page. */
|
|
163
|
-
.speciality-craft {
|
|
164
|
-
font-size: 0.78rem;
|
|
165
|
-
text-transform: lowercase;
|
|
166
|
-
opacity: 0.72;
|
|
167
|
-
margin-left: 0.4rem;
|
|
168
|
-
}
|
|
169
|
-
.speciality-badge {
|
|
170
|
-
font-size: 0.7rem;
|
|
171
|
-
padding: 0.05rem 0.4rem;
|
|
172
|
-
border-radius: 0.5rem;
|
|
173
|
-
margin-left: 0.4rem;
|
|
174
|
-
border: 1px solid currentColor;
|
|
175
|
-
}
|
|
176
|
-
.speciality-counts { opacity: 0.82; }
|
|
177
|
-
.speciality-card { flex-wrap: wrap; }
|
|
178
|
-
.speciality-train,
|
|
179
|
-
.speciality-create {
|
|
180
|
-
flex-basis: 100%;
|
|
181
|
-
margin-top: 0.75rem;
|
|
182
|
-
padding-top: 0.75rem;
|
|
183
|
-
border-top: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
|
|
184
|
-
display: flex;
|
|
185
|
-
flex-direction: column;
|
|
186
|
-
gap: 0.6rem;
|
|
187
|
-
}
|
|
188
|
-
.speciality-create__h { margin: 0; font-size: 0.95rem; }
|
|
189
|
-
.speciality-field { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
190
|
-
.speciality-field > span { font-size: 0.85rem; }
|
|
191
|
-
.speciality-field small { display: block; opacity: 0.7; font-weight: normal; }
|
|
192
|
-
.speciality-field input[type='text'],
|
|
193
|
-
.speciality-field select,
|
|
194
|
-
.speciality-field textarea {
|
|
195
|
-
width: 100%;
|
|
196
|
-
box-sizing: border-box;
|
|
197
|
-
}
|
|
198
|
-
/* The checkbox row reads as one sentence, so it runs inline rather than stacked. */
|
|
199
|
-
.speciality-field--check { flex-direction: row; align-items: flex-start; gap: 0.5rem; }
|
|
200
|
-
.speciality-field--check input { margin-top: 0.2rem; }
|
|
201
|
-
.speciality-actions { display: flex; gap: 0.5rem; }
|
|
202
|
-
/* What a speciality OFFERS (task 1004404): the owner's pick-list of this
|
|
203
|
-
instance's skills, each with its plain line. Sixty-odd rows, so the list
|
|
204
|
-
scrolls inside its own box rather than pushing the form's buttons off-screen;
|
|
205
|
-
the reading lines keep the measure. */
|
|
206
|
-
.speciality-field input[type='search'] { width: 100%; box-sizing: border-box; }
|
|
207
|
-
.speciality-offer,
|
|
208
|
-
.speciality-offer-box { flex-basis: 100%; display: flex; flex-direction: column; gap: 0.6rem; }
|
|
209
|
-
.speciality-offer { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
|
|
210
|
-
.speciality-offer > *, .speciality-offer-box > * { max-width: var(--measure); }
|
|
211
|
-
.speciality-offer-box > summary { cursor: pointer; font-size: 0.85rem; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
|
|
212
|
-
.speciality-offer-box > summary small { opacity: 0.7; }
|
|
213
|
-
.speciality-offer__choices, #speciality-create-skills { display: flex; flex-direction: column; gap: 0.6rem; }
|
|
214
|
-
.speciality-offer__list {
|
|
215
|
-
list-style: none;
|
|
216
|
-
margin: 0;
|
|
217
|
-
padding: 0 0.25rem 0 0;
|
|
218
|
-
display: flex;
|
|
219
|
-
flex-direction: column;
|
|
220
|
-
gap: 0.5rem;
|
|
221
|
-
max-height: 24rem;
|
|
222
|
-
overflow-y: auto;
|
|
223
|
-
overflow-x: hidden;
|
|
224
|
-
}
|
|
225
|
-
.speciality-offer__list li[hidden] { display: none; }
|
|
226
|
-
.speciality-offer__list strong { font-family: var(--font-mono); font-weight: 600; }
|
|
227
|
-
.speciality-offer__list .speciality-field--check > span { font-size: 0.85rem; line-height: 1.45; }
|
|
228
|
-
/* The adopted contract, shown in full before "Use this" — another builder's prose
|
|
229
|
-
reaching your sessions is the one thing here that must never be folded away. */
|
|
230
|
-
.speciality-contract { flex-basis: 100%; margin-top: 0.5rem; font-size: 0.85rem; }
|
|
231
|
-
/* 24px tall: the panel was never revealed until task 1004073, so the kit had
|
|
232
|
-
never measured this toggle; it came back 21px. */
|
|
233
|
-
.speciality-contract > summary { cursor: pointer; opacity: 0.85; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
|
|
234
|
-
.speciality-contract__body {
|
|
235
|
-
white-space: pre-wrap;
|
|
236
|
-
margin: 0.4rem 0 0;
|
|
237
|
-
padding: 0.5rem 0.65rem;
|
|
238
|
-
border-left: 2px solid var(--rule, rgba(128, 128, 128, 0.35));
|
|
239
|
-
border-radius: 0;
|
|
240
|
-
font-family: inherit;
|
|
241
|
-
opacity: 0.92;
|
|
242
|
-
}
|
|
243
|
-
/* The skills walkthrough (task 1004073, ADR 0310 §3): one offered skill at a
|
|
244
|
-
time, three answers as a definition list, then a decision. It sits inside the
|
|
245
|
-
card like the train form, so it inherits the card's ground in both modes. */
|
|
246
|
-
.skill-walk {
|
|
247
|
-
flex-basis: 100%;
|
|
248
|
-
margin-top: 0.75rem;
|
|
249
|
-
padding-top: 0.75rem;
|
|
250
|
-
border-top: 1px solid var(--rule);
|
|
251
|
-
display: flex;
|
|
252
|
-
flex-direction: column;
|
|
253
|
-
gap: 0.6rem;
|
|
254
|
-
}
|
|
255
|
-
/* The measure caps the reading lines, never the section: a capped flex-basis
|
|
256
|
-
lets the section fit beside the card's actions instead of wrapping under them. */
|
|
257
|
-
.skill-walk > * { max-width: var(--measure); }
|
|
258
|
-
.skill-walk__progress { margin: 0; font-size: 0.8rem; color: var(--ink-soft); }
|
|
259
|
-
.skill-walk__name { margin: 0; font-family: var(--font-mono); font-size: 1rem; color: var(--ink); }
|
|
260
|
-
.skill-walk__facts { margin: 0; display: grid; gap: 0.2rem 0; }
|
|
261
|
-
.skill-walk__facts dt { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.4rem; }
|
|
262
|
-
.skill-walk__facts dt:first-child { margin-top: 0; }
|
|
263
|
-
.skill-walk__facts dd { margin: 0; color: var(--ink); line-height: 1.5; }
|
|
264
|
-
.skill-walk__note { font-size: 0.8rem; }
|
|
265
|
-
/* "Leave it off" and "Turn it on" are the panel family's plain pill at EQUAL
|
|
266
|
-
weight: the default is off, so the page must not lean on the reader toward on.
|
|
267
|
-
Only "Save my choices" takes the accent. A decision already made reads through
|
|
268
|
-
aria-pressed when the person steps Back to it. */
|
|
269
|
-
.skill-walk__actions { flex-wrap: wrap; }
|
|
270
|
-
.skill-walk__actions .pbtn[aria-pressed="true"] { border-color: var(--ink-soft); }
|
|
271
|
-
.skill-walk__recap { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
272
|
-
.skill-walk__recap li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.4rem; }
|
|
273
|
-
.skill-walk__recap .prow__hint { flex-basis: 100%; margin: 0; }
|
|
274
|
-
.skill-walk__recap strong { font-family: var(--font-mono); font-weight: 600; }
|
|
275
|
-
/* "Finish later" is a way out, not a choice about a skill, so it reads as a quiet
|
|
276
|
-
link below the decision rather than a third button beside it. */
|
|
277
|
-
.skill-walk__later {
|
|
278
|
-
align-self: flex-start;
|
|
279
|
-
background: none;
|
|
280
|
-
border: 0;
|
|
281
|
-
padding: 0 0.25rem;
|
|
282
|
-
margin-inline-start: -0.25rem;
|
|
283
|
-
min-height: 24px;
|
|
284
|
-
font: inherit;
|
|
285
|
-
font-size: 0.8rem;
|
|
286
|
-
color: var(--ink-soft);
|
|
287
|
-
text-decoration: underline;
|
|
288
|
-
text-underline-offset: 3px;
|
|
289
|
-
cursor: pointer;
|
|
290
|
-
}
|
|
291
|
-
.skill-walk__later:hover { color: var(--ink); }
|
|
160
|
+
/* The specialities panel's rules moved to speciality-tools.css (task 1005052),
|
|
161
|
+
which Settings links right after this sheet and My agents links too. */
|
|
292
162
|
|
|
293
163
|
/* ---- Software update (task 1004296) -------------------------------------- */
|
|
294
164
|
/* A version is read digit by digit and compared against another, so it takes the mono
|