@bongos/core 1.24.27 → 1.24.29
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 +53 -58
- package/.claude/skills/builder-claim/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/docs/copy-inventory.md +173 -203
- package/docs/copy-registry.json +350 -622
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +8 -8
- package/docs/page-readings.json +346 -331
- package/modules/builder-settings/interaction-prefs.js +2 -2
- 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 +98 -56
- package/modules/hall-ui/public/my-agents.states.json +18 -0
- package/modules/hall-ui/public/palette.js +8 -0
- package/modules/hall-ui/public/settings-moved.js +41 -0
- package/modules/hall-ui/public/settings.css +4 -158
- package/modules/hall-ui/public/settings.html +19 -147
- package/modules/hall-ui/public/settings.js +20 -215
- package/modules/hall-ui/public/settings.states.json +2 -4
- 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 +5 -2
- package/modules/hall-ui/records/panel-family.md +4 -0
- package/modules/lifecycle/ship-card.js +2 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/hall-preview/server.js +4 -0
- package/src/module-api.js +1 -1
- package/tests/government_tidy_up.mjs +21 -24
- package/tests/hall_loading.mjs +3 -1
- package/tests/hall_my_agents.mjs +190 -2
- package/tests/hall_palette.mjs +12 -0
- package/tests/hall_settings_world.mjs +64 -100
- package/tests/helpers.mjs +162 -0
- package/tests/interaction_posture.mjs +3 -1
- package/tests/speciality_offer_ui.mjs +183 -149
- package/tests/speciality_walkthrough_ui.mjs +182 -161
- package/modules/hall-ui/public/settings-interaction.js +0 -132
- package/modules/hall-ui/public/settings-law-locks.js +0 -53
- package/modules/hall-ui/public/settings-render.js +0 -138
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// public-builders/settings.js — the personal Settings page (/builders/settings).
|
|
2
2
|
//
|
|
3
3
|
// Open to ANY authenticated builder (no rank gate — a builder owns their own
|
|
4
|
-
// preferences; the server enforces the same posture on /me/
|
|
5
|
-
//
|
|
4
|
+
// preferences; the server enforces the same posture on /me/disciplines). Among
|
|
5
|
+
// its sections:
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// CRAFT — preferred disciplines (GET /api/bongos/me, PATCH /api/bongos/me/disciplines).
|
|
8
|
+
// SOUNDS — per-sound on/off toggles (GET/PATCH /api/bongos/me/sound-prefs, #786).
|
|
9
|
+
//
|
|
10
|
+
// The agent settings (how Claude talks to you, wandering, session cards, skill
|
|
11
|
+
// voices, specialities) are NOT here since task 1005044: My agents is their one
|
|
12
|
+
// page, and settings-moved.js sends their old deep links there.
|
|
10
13
|
//
|
|
11
14
|
// On a 401 it redirects into the web sign-in flow, like the other hall pages.
|
|
12
15
|
// Each section loads behind its own try/catch so one failing endpoint doesn't
|
|
@@ -32,102 +35,6 @@
|
|
|
32
35
|
window.location.assign(`${API}/auth/web/start`);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
// ---- model labels (match the hall's wording in builders.js) --------------
|
|
36
|
-
|
|
37
|
-
const MODEL_LABELS = {
|
|
38
|
-
opus: 'Opus',
|
|
39
|
-
sonnet: 'Sonnet',
|
|
40
|
-
haiku: 'Haiku',
|
|
41
|
-
script: 'no LLM',
|
|
42
|
-
};
|
|
43
|
-
const modelLabel = (m) => MODEL_LABELS[m] || m || '—';
|
|
44
|
-
|
|
45
|
-
// ---- section 1: VOICES (per-skill model picker) --------------------------
|
|
46
|
-
|
|
47
|
-
// Render one row per skill in `effective`. Each row: skill name, the system
|
|
48
|
-
// default (muted), and a <select> whose value is the builder's override (or
|
|
49
|
-
// "" for "fall back to default"). The allowed model set is echoed back by the
|
|
50
|
-
// endpoint so we don't mirror it from a second source.
|
|
51
|
-
function renderVoices(data) {
|
|
52
|
-
const body = $('#voices-body');
|
|
53
|
-
if (!body) return;
|
|
54
|
-
const rows = Array.isArray(data?.effective) ? data.effective : [];
|
|
55
|
-
const allowed = Array.isArray(data?.allowed_models)
|
|
56
|
-
? data.allowed_models
|
|
57
|
-
: ['opus', 'sonnet', 'haiku', 'script'];
|
|
58
|
-
if (rows.length === 0) {
|
|
59
|
-
body.innerHTML = `<tr><td colspan="3">${window.OTBKit.emptyStateHtml('No skills registered.')}</td></tr>`;
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
body.innerHTML = rows.map((row) => {
|
|
63
|
-
const opts = [`<option value="">Default (${escapeHtml(modelLabel(row.default_model))})</option>`]
|
|
64
|
-
.concat(allowed.map((m) => {
|
|
65
|
-
const sel = row.override_model === m ? ' selected' : '';
|
|
66
|
-
return `<option value="${escapeHtml(m)}"${sel}>${escapeHtml(modelLabel(m))}</option>`;
|
|
67
|
-
}))
|
|
68
|
-
.join('');
|
|
69
|
-
return (
|
|
70
|
-
`<tr data-skill="${escapeHtml(row.skill)}">` +
|
|
71
|
-
`<td class="voices__skill"><code>${escapeHtml(row.skill)}</code></td>` +
|
|
72
|
-
`<td><span class="voices__default" title="System default for this skill">${escapeHtml(modelLabel(row.default_model))}</span></td>` +
|
|
73
|
-
'<td>' +
|
|
74
|
-
`<select class="voices__select" data-skill="${escapeHtml(row.skill)}" aria-label="Voice for ${escapeHtml(row.skill)}">${opts}</select>` +
|
|
75
|
-
'</td>' +
|
|
76
|
-
'</tr>'
|
|
77
|
-
);
|
|
78
|
-
}).join('');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// PATCH a single skill's override. Body shape (see me.js + skill-prefs.js):
|
|
82
|
-
// { "<skill-name>": "<model>" | null }
|
|
83
|
-
// where null clears the override (fall back to the system default).
|
|
84
|
-
async function saveVoice(skill, modelOrNull) {
|
|
85
|
-
const status = $('#voices-status');
|
|
86
|
-
if (status) status.textContent = 'Saving…';
|
|
87
|
-
try {
|
|
88
|
-
const body = { [skill]: modelOrNull };
|
|
89
|
-
const res = await api.request('PATCH', `${API}/me/skill-prefs`, { body });
|
|
90
|
-
if (res.status === 401) { toAuth(); return; }
|
|
91
|
-
if (!res.ok) throw new Error(`skill-prefs PATCH → ${res.status}`);
|
|
92
|
-
const data = res.data;
|
|
93
|
-
renderVoices(data);
|
|
94
|
-
const msg = modelOrNull
|
|
95
|
-
? `${skill} → ${modelLabel(modelOrNull)}.`
|
|
96
|
-
: `Cleared ${skill} — falls back to default.`;
|
|
97
|
-
if (status) status.textContent = msg;
|
|
98
|
-
toast(msg);
|
|
99
|
-
} catch (err) {
|
|
100
|
-
console.error('[settings] save voice failed', err);
|
|
101
|
-
if (status) status.textContent = 'Could not save. Try again.';
|
|
102
|
-
toast('Could not save your choice. Try again.');
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function loadVoices() {
|
|
107
|
-
try {
|
|
108
|
-
const res = await api.request('GET', `${API}/me/skill-prefs`);
|
|
109
|
-
if (res.status === 401) { toAuth(); return; }
|
|
110
|
-
if (!res.ok) throw new Error(`skill-prefs GET → ${res.status}`);
|
|
111
|
-
const data = res.data;
|
|
112
|
-
renderVoices(data);
|
|
113
|
-
$('#voices-scroll').hidden = false;
|
|
114
|
-
// Delegate change events so post-render selects stay wired.
|
|
115
|
-
const table = $('#voices-table');
|
|
116
|
-
table.addEventListener('change', (e) => {
|
|
117
|
-
const sel = e.target.closest('.voices__select');
|
|
118
|
-
if (!sel) return;
|
|
119
|
-
const skill = sel.dataset.skill;
|
|
120
|
-
const next = sel.value === '' ? null : sel.value;
|
|
121
|
-
saveVoice(skill, next);
|
|
122
|
-
});
|
|
123
|
-
} catch (err) {
|
|
124
|
-
console.error('[settings] failed to load voices', err);
|
|
125
|
-
$('#voices-scroll').hidden = false;
|
|
126
|
-
$('#voices-body').innerHTML =
|
|
127
|
-
'<tr><td colspan="3" class="voices-empty">The skill list could not be loaded. Reload the page to try again.</td></tr>';
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
38
|
// ---- section 5: SOUNDS (per-sound on/off toggles, #786) ------------------
|
|
132
39
|
|
|
133
40
|
// Render one row per sound from `effective`. Each row: label + description and
|
|
@@ -392,104 +299,6 @@
|
|
|
392
299
|
}
|
|
393
300
|
}
|
|
394
301
|
|
|
395
|
-
// ---- section 2b: WANDERING (off-task thread suppression, task 1268) -------
|
|
396
|
-
//
|
|
397
|
-
// GET/PATCH /api/bongos/me/wandering. The payload (wandering-prefs.describeForApi)
|
|
398
|
-
// carries the level catalog with per-rank `allowed`, the effective level, the
|
|
399
|
-
// rank default, and the ceiling. Single-select: clicking an allowed level
|
|
400
|
-
// PATCHes { wandering: n }. Levels above the rank cap render disabled — the
|
|
401
|
-
// server enforces the same clamp, this is just the friendly UI half. Reuses
|
|
402
|
-
// the .sound-row / .sound-toggle styling.
|
|
403
|
-
function renderWandering(data) {
|
|
404
|
-
const wrap = $('#wander-rows');
|
|
405
|
-
if (!wrap) return;
|
|
406
|
-
const levels = Array.isArray(data?.levels) ? data.levels : [];
|
|
407
|
-
const eff = typeof data?.effective_level === 'number' ? data.effective_level : null;
|
|
408
|
-
const def = typeof data?.default_level === 'number' ? data.default_level : null;
|
|
409
|
-
if (levels.length === 0) {
|
|
410
|
-
wrap.innerHTML = window.OTBKit.emptyStateHtml('No levels registered.');
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
// One pick among four: a radio row per level (panel.css `.prow--pick`), the
|
|
414
|
-
// whole row its label. A level above the rank ceiling is a disabled radio
|
|
415
|
-
// that says so in the label voice rather than a dead "Rank-locked" button.
|
|
416
|
-
// a project law may fix or cap the level too (task 1004724, settings-law-locks.js)
|
|
417
|
-
const Laws = window.OTBSettingsLaws || null;
|
|
418
|
-
const lawLock = Laws ? Laws.lockFor(data, 'wandering') : null;
|
|
419
|
-
wrap.innerHTML = levels.map((lv) => {
|
|
420
|
-
const chosen = lv.n === eff;
|
|
421
|
-
const byLaw = lv.allowed && !!lawLock && !Laws.allows(lawLock, lv.key);
|
|
422
|
-
const locked = !lv.allowed || byLaw;
|
|
423
|
-
const id = `wander-${lv.n}`;
|
|
424
|
-
const tags = (lv.n === def ? '<span class="prow__tag">default for your rank</span>' : '') +
|
|
425
|
-
(!lv.allowed ? '<span class="prow__tag">rank-locked</span>' : '') +
|
|
426
|
-
(byLaw ? `<span class="prow__tag">${escapeHtml(Laws.tagFor(lawLock))}</span>` : '');
|
|
427
|
-
return (
|
|
428
|
-
`<label class="prow prow--pick" for="${id}" data-level="${lv.n}"${locked ? ' aria-disabled="true"' : ''}>` +
|
|
429
|
-
`<input class="pradio" type="radio" name="wandering" id="${id}" value="${lv.n}"` +
|
|
430
|
-
`${chosen ? ' checked' : ''}${locked ? ' disabled' : ''}>` +
|
|
431
|
-
'<span class="prow__main">' +
|
|
432
|
-
`<span class="prow__label">${escapeHtml(lv.label)}${tags}</span>` +
|
|
433
|
-
`<span class="prow__desc">${escapeHtml(lv.blurb || '')}</span>` +
|
|
434
|
-
'</span></label>'
|
|
435
|
-
);
|
|
436
|
-
}).join('');
|
|
437
|
-
const note = $('#wandering-note');
|
|
438
|
-
if (note) {
|
|
439
|
-
note.textContent = ((data && data.effective_label && data.max_key)
|
|
440
|
-
? `Now: ${data.effective_label}. Your rank allows up to ${data.max_key}.`
|
|
441
|
-
: '') + (lawLock ? ' ' + Laws.lawLine(lawLock) : '');
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// PATCH the wandering level. Body: { wandering: <0..3> } (see me.js +
|
|
446
|
-
// wandering-prefs.js). A level above the rank ceiling is rejected server-side
|
|
447
|
-
// (422) — but those buttons render disabled, so it shouldn't be reachable.
|
|
448
|
-
async function saveWandering(level, btn) {
|
|
449
|
-
const status = $('#wandering-status');
|
|
450
|
-
if (status) status.textContent = 'Saving…';
|
|
451
|
-
if (btn) btn.disabled = true;
|
|
452
|
-
try {
|
|
453
|
-
const res = await api.request('PATCH', `${API}/me/wandering`, { body: { wandering: level } });
|
|
454
|
-
if (res.status === 401) { toAuth(); return; }
|
|
455
|
-
if (!res.ok) throw new Error(`wandering PATCH → ${res.status}`);
|
|
456
|
-
const data = res.data;
|
|
457
|
-
renderWandering(data);
|
|
458
|
-
const msg = `Wandering → ${data.effective_label || level}.`;
|
|
459
|
-
if (status) status.textContent = msg;
|
|
460
|
-
toast(msg);
|
|
461
|
-
} catch (err) {
|
|
462
|
-
console.error('[settings] save wandering failed', err);
|
|
463
|
-
if (status) status.textContent = 'Could not save. Try again.';
|
|
464
|
-
toast('Could not save your wandering level. Try again.');
|
|
465
|
-
if (btn) btn.disabled = false;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async function loadWandering() {
|
|
470
|
-
try {
|
|
471
|
-
const res = await api.request('GET', `${API}/me/wandering`);
|
|
472
|
-
if (res.status === 401) { toAuth(); return; }
|
|
473
|
-
if (!res.ok) throw new Error(`wandering GET → ${res.status}`);
|
|
474
|
-
const data = res.data;
|
|
475
|
-
renderWandering(data);
|
|
476
|
-
$('#wandering-scroll').hidden = false;
|
|
477
|
-
// Delegate the change so post-render radios stay wired (single-select).
|
|
478
|
-
const wrap = $('#wander-rows');
|
|
479
|
-
wrap.addEventListener('change', (e) => {
|
|
480
|
-
const r = e.target.closest('.pradio');
|
|
481
|
-
if (!r || r.disabled || !r.checked) return; // ignore locked; a radio only fires on becoming checked
|
|
482
|
-
const level = Number(r.value);
|
|
483
|
-
if (Number.isInteger(level)) saveWandering(level, r);
|
|
484
|
-
});
|
|
485
|
-
} catch (err) {
|
|
486
|
-
console.error('[settings] failed to load wandering', err);
|
|
487
|
-
$('#wandering-scroll').hidden = false;
|
|
488
|
-
$('#wander-rows').innerHTML =
|
|
489
|
-
'<p class="voices-empty">Your wandering level could not be loaded. Reload the page to try again.</p>';
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
302
|
// ---- section: PROFILE AND PRIVACY — a link out to the hub (task 1004259)
|
|
494
303
|
//
|
|
495
304
|
// Handle, bio, links, public/private, recruiters and the hide-my-numbers switch
|
|
@@ -1100,7 +909,7 @@
|
|
|
1100
909
|
const RAIL_GROUPS = RAIL_ITEMS.map((i) => i.id).concat('extensions');
|
|
1101
910
|
let rail = null;
|
|
1102
911
|
|
|
1103
|
-
// task 2172 — stable per-panel anchors (/settings#cli, /settings#
|
|
912
|
+
// task 2172 — stable per-panel anchors (/settings#cli, /settings#sounds, …)
|
|
1104
913
|
// so skills and docs can deep-link a panel without depending on internal
|
|
1105
914
|
// section ids. #art-key predates the map and IS its section id (the hall's
|
|
1106
915
|
// "Add your key" action + the CLI nudge target it) — kept verbatim.
|
|
@@ -1117,10 +926,6 @@
|
|
|
1117
926
|
'cli': 'cli-scroll',
|
|
1118
927
|
'autobongos': 'autobongos-scroll',
|
|
1119
928
|
'art-key': 'art-key',
|
|
1120
|
-
'interaction': 'interaction-scroll',
|
|
1121
|
-
'wandering': 'wandering-scroll',
|
|
1122
|
-
'rendering': 'render-scroll',
|
|
1123
|
-
'voices': 'voices-scroll',
|
|
1124
929
|
'sounds': 'sounds-scroll',
|
|
1125
930
|
'event-sounds': 'event-sounds-scroll',
|
|
1126
931
|
'software-update': 'software-update-scroll',
|
|
@@ -1162,6 +967,14 @@
|
|
|
1162
967
|
}
|
|
1163
968
|
}
|
|
1164
969
|
|
|
970
|
+
// task 1005044 — a link to an agent setting that moved to My agents
|
|
971
|
+
// (settings-moved.js) goes on there; replace(), so Back does not bounce here.
|
|
972
|
+
function leaveIfMoved() {
|
|
973
|
+
const to = window.OTBSettingsMoved && window.OTBSettingsMoved.movedTarget(window.location.hash);
|
|
974
|
+
if (to) window.location.replace(to);
|
|
975
|
+
return !!to;
|
|
976
|
+
}
|
|
977
|
+
|
|
1165
978
|
function wireRail() {
|
|
1166
979
|
const mount = document.getElementById('settings-rail');
|
|
1167
980
|
if (!mount || !window.OTBKit) return;
|
|
@@ -1178,7 +991,7 @@
|
|
|
1178
991
|
});
|
|
1179
992
|
rail.activate('account');
|
|
1180
993
|
applyHash({ scroll: false });
|
|
1181
|
-
window.addEventListener('hashchange', () => applyHash());
|
|
994
|
+
window.addEventListener('hashchange', () => { if (!leaveIfMoved()) applyHash(); });
|
|
1182
995
|
}
|
|
1183
996
|
|
|
1184
997
|
// Hide the rail + groups entirely while the not-signed-in gate is showing —
|
|
@@ -1251,21 +1064,12 @@
|
|
|
1251
1064
|
rail.activate($('#settings-groups')?.dataset.active || 'account');
|
|
1252
1065
|
}
|
|
1253
1066
|
|
|
1254
|
-
// Voices loads independently — its own endpoint + its own try/catch.
|
|
1255
|
-
await loadVoices();
|
|
1256
1067
|
// Sounds are switched off for now (task 1004554 — .claude/sounds/play.js's
|
|
1257
1068
|
// SOUNDS_ON). The per-sound toggles (#786) and the per-event picker (task 582)
|
|
1258
1069
|
// would only offer choices that do nothing, so neither loads; the Sound group
|
|
1259
1070
|
// shows the one-line note instead. To restore: call loadSounds() and
|
|
1260
1071
|
// loadEventSounds() here again and drop the note.
|
|
1261
1072
|
$('#sounds-off-scroll').hidden = false;
|
|
1262
|
-
// (The interaction profile panel self-boots from settings-interaction.js —
|
|
1263
|
-
// its own script on the settings-sessions.js precedent, task 1002514.)
|
|
1264
|
-
// Wandering loads independently too (off-task thread suppression, task 1268).
|
|
1265
|
-
await loadWandering();
|
|
1266
|
-
// (Rendering — the card-richness ladder — is settings-render.js, on the same
|
|
1267
|
-
// precedent: it became a four-rung picker in task 1002518 and this file is
|
|
1268
|
-
// against the 1500-line ratchet.)
|
|
1269
1073
|
// Profile and privacy — one row linking out to the hub's account page
|
|
1270
1074
|
// (task 1004259); hidden where the page carries no hub origin.
|
|
1271
1075
|
loadAccountHub();
|
|
@@ -1273,10 +1077,11 @@
|
|
|
1273
1077
|
// the `needs` art_key item that drives the covered/action-needed banner.
|
|
1274
1078
|
await loadArtKey(me);
|
|
1275
1079
|
// Every section has now loaded/revealed — land any panel-anchor deep link
|
|
1276
|
-
// (/settings#cli, #
|
|
1080
|
+
// (/settings#cli, #art-key, …) on its visible section.
|
|
1277
1081
|
applyHash();
|
|
1278
1082
|
}
|
|
1279
1083
|
|
|
1084
|
+
if (leaveIfMoved()) return;
|
|
1280
1085
|
wireRail();
|
|
1281
1086
|
load();
|
|
1282
1087
|
})();
|
|
@@ -9,11 +9,9 @@
|
|
|
9
9
|
"states": {
|
|
10
10
|
"account": { "auth": true, "actions": [["wait", 900]], "expect": { "visible": ["#settings-h1", "#display-name-input", "#craft-chips"] } },
|
|
11
11
|
"access": { "auth": true, "url": "/settings#access", "actions": [["wait", 900]], "expect": { "visible": ["#cli-reissue-btn", "#sessions-list", "#artkey-card"] } },
|
|
12
|
-
"preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#
|
|
13
|
-
"skills-walkthrough": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["click", "#specialities-rows button[data-act=\"adopt\"]"], ["wait", 700]], "expect": { "visible": [".skill-walk", ".skill-walk__facts", "button[data-walk=\"on\"]", "button[data-walk=\"off\"]"] } },
|
|
14
|
-
"offered-skills": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["click", "#specialities-rows button[data-act=\"offer\"]"], ["wait", 700]], "expect": { "visible": ["form.speciality-offer", ".speciality-offer__list", ".speciality-offer__filter"] } },
|
|
12
|
+
"preferences": { "auth": true, "url": "/settings#preferences", "actions": [["wait", 900]], "expect": { "visible": ["#agents-moved-preferences", "#agents-moved-preferences a[href=\"/my-agents#talk\"]", "#usage-reports-h"] } },
|
|
15
13
|
"install-reports": { "_": "The Module install reports switch (task 1003793): the opt-out for counting your store-module installs in a module's score, on by default. Scrolled into view by focusing its default choice.", "auth": true, "url": "/settings#preferences", "actions": [["wait", 900], ["clickIfVisible", "[data-craft-close]"], ["wait", 300], ["focus", "#usage-reports-on"], ["wait", 300]], "expect": { "visible": ["#usage-reports-h", "#usage-reports-on", "#usage-reports-off"] } },
|
|
16
|
-
"sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#
|
|
14
|
+
"sound": { "auth": true, "url": "/settings#sound", "actions": [["wait", 900]], "expect": { "visible": ["#agents-moved-sound", "#agents-moved-sound a[href=\"/my-agents#skills\"]", "#sounds-off-h"] } },
|
|
17
15
|
"update": { "auth": true, "url": "/settings#software-update", "actions": [["wait", 1200]], "expect": { "visible": ["#software-update-h", "#su-body", "#su-update", ".su-versions"] } }
|
|
18
16
|
},
|
|
19
17
|
"reducedMotion": { "auth": true, "actions": [["wait", 900]] }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* speciality-tools.css — the SPECIALITY TOOLS sheet (task 1005052): the structural
|
|
2
|
+
rules of the one component Settings and My agents both mount
|
|
3
|
+
(speciality-tools.js) — the card, the train, offer and create forms, the
|
|
4
|
+
instructions block and the skills walkthrough. Moved out of settings.css
|
|
5
|
+
verbatim, so Settings draws exactly as it did; each page sheet adds its own
|
|
6
|
+
look on top (Settings: panel.css, linked before this; My agents: the `.ma-know`
|
|
7
|
+
skin in my-agents.css, linked after). */
|
|
8
|
+
|
|
9
|
+
/* Specialities panel (task 1003855, ADR 0296). A speciality is a named bundle of
|
|
10
|
+
knowledge and terms under a craft; this styles the list, the train form and the
|
|
11
|
+
create form. Tokens only — no literal colours, so light/dark follow the page. */
|
|
12
|
+
.speciality-craft {
|
|
13
|
+
font-size: 0.78rem;
|
|
14
|
+
text-transform: lowercase;
|
|
15
|
+
opacity: 0.72;
|
|
16
|
+
margin-left: 0.4rem;
|
|
17
|
+
}
|
|
18
|
+
.speciality-badge {
|
|
19
|
+
font-size: 0.7rem;
|
|
20
|
+
padding: 0.05rem 0.4rem;
|
|
21
|
+
border-radius: 0.5rem;
|
|
22
|
+
margin-left: 0.4rem;
|
|
23
|
+
border: 1px solid currentColor;
|
|
24
|
+
}
|
|
25
|
+
.speciality-counts { opacity: 0.82; }
|
|
26
|
+
.speciality-card { flex-wrap: wrap; }
|
|
27
|
+
.speciality-train,
|
|
28
|
+
.speciality-create {
|
|
29
|
+
flex-basis: 100%;
|
|
30
|
+
margin-top: 0.75rem;
|
|
31
|
+
padding-top: 0.75rem;
|
|
32
|
+
border-top: 1px solid var(--rule, rgba(128, 128, 128, 0.25));
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
gap: 0.6rem;
|
|
36
|
+
}
|
|
37
|
+
.speciality-create__h { margin: 0; font-size: 0.95rem; }
|
|
38
|
+
.speciality-field { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
39
|
+
.speciality-field > span { font-size: 0.85rem; }
|
|
40
|
+
.speciality-field small { display: block; opacity: 0.7; font-weight: normal; }
|
|
41
|
+
.speciality-field input[type='text'],
|
|
42
|
+
.speciality-field select,
|
|
43
|
+
.speciality-field textarea {
|
|
44
|
+
width: 100%;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
}
|
|
47
|
+
/* The checkbox row reads as one sentence, so it runs inline rather than stacked. */
|
|
48
|
+
.speciality-field--check { flex-direction: row; align-items: flex-start; gap: 0.5rem; }
|
|
49
|
+
.speciality-field--check input { margin-top: 0.2rem; }
|
|
50
|
+
.speciality-actions { display: flex; gap: 0.5rem; }
|
|
51
|
+
/* What a speciality OFFERS (task 1004404): the owner's pick-list of this
|
|
52
|
+
instance's skills, each with its plain line. Sixty-odd rows, so the list
|
|
53
|
+
scrolls inside its own box rather than pushing the form's buttons off-screen;
|
|
54
|
+
the reading lines keep the measure. */
|
|
55
|
+
.speciality-field input[type='search'] { width: 100%; box-sizing: border-box; }
|
|
56
|
+
.speciality-offer,
|
|
57
|
+
.speciality-offer-box { flex-basis: 100%; display: flex; flex-direction: column; gap: 0.6rem; }
|
|
58
|
+
.speciality-offer { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
|
|
59
|
+
.speciality-offer > *, .speciality-offer-box > * { max-width: var(--measure); }
|
|
60
|
+
.speciality-offer-box > summary { cursor: pointer; font-size: 0.85rem; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
|
|
61
|
+
.speciality-offer-box > summary small { opacity: 0.7; }
|
|
62
|
+
.speciality-offer__choices, #speciality-create-skills { display: flex; flex-direction: column; gap: 0.6rem; }
|
|
63
|
+
.speciality-offer__list {
|
|
64
|
+
list-style: none;
|
|
65
|
+
margin: 0;
|
|
66
|
+
padding: 0 0.25rem 0 0;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
gap: 0.5rem;
|
|
70
|
+
max-height: 24rem;
|
|
71
|
+
overflow-y: auto;
|
|
72
|
+
overflow-x: hidden;
|
|
73
|
+
}
|
|
74
|
+
.speciality-offer__list li[hidden] { display: none; }
|
|
75
|
+
.speciality-offer__list strong { font-family: var(--font-mono); font-weight: 600; }
|
|
76
|
+
.speciality-offer__list .speciality-field--check > span { font-size: 0.85rem; line-height: 1.45; }
|
|
77
|
+
/* The adopted contract, shown in full before "Use this" — another builder's prose
|
|
78
|
+
reaching your sessions is the one thing here that must never be folded away. */
|
|
79
|
+
.speciality-contract { flex-basis: 100%; margin-top: 0.5rem; font-size: 0.85rem; }
|
|
80
|
+
/* 24px tall: the panel was never revealed until task 1004073, so the kit had
|
|
81
|
+
never measured this toggle; it came back 21px. */
|
|
82
|
+
.speciality-contract > summary { cursor: pointer; opacity: 0.85; min-height: 24px; padding-block: 2px; box-sizing: border-box; }
|
|
83
|
+
.speciality-contract__body {
|
|
84
|
+
white-space: pre-wrap;
|
|
85
|
+
margin: 0.4rem 0 0;
|
|
86
|
+
padding: 0.5rem 0.65rem;
|
|
87
|
+
border-left: 2px solid var(--rule, rgba(128, 128, 128, 0.35));
|
|
88
|
+
border-radius: 0;
|
|
89
|
+
font-family: inherit;
|
|
90
|
+
opacity: 0.92;
|
|
91
|
+
}
|
|
92
|
+
/* The skills walkthrough (task 1004073, ADR 0310 §3): one offered skill at a
|
|
93
|
+
time, three answers as a definition list, then a decision. It sits inside the
|
|
94
|
+
card like the train form, so it inherits the card's ground in both modes. */
|
|
95
|
+
.skill-walk {
|
|
96
|
+
flex-basis: 100%;
|
|
97
|
+
margin-top: 0.75rem;
|
|
98
|
+
padding-top: 0.75rem;
|
|
99
|
+
border-top: 1px solid var(--rule);
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: 0.6rem;
|
|
103
|
+
}
|
|
104
|
+
/* The measure caps the reading lines, never the section: a capped flex-basis
|
|
105
|
+
lets the section fit beside the card's actions instead of wrapping under them. */
|
|
106
|
+
.skill-walk > * { max-width: var(--measure); }
|
|
107
|
+
.skill-walk__progress { margin: 0; font-size: 0.8rem; color: var(--ink-soft); }
|
|
108
|
+
.skill-walk__name { margin: 0; font-family: var(--font-mono); font-size: 1rem; color: var(--ink); }
|
|
109
|
+
.skill-walk__facts { margin: 0; display: grid; gap: 0.2rem 0; }
|
|
110
|
+
.skill-walk__facts dt { font-size: 0.8rem; color: var(--ink-soft); margin-top: 0.4rem; }
|
|
111
|
+
.skill-walk__facts dt:first-child { margin-top: 0; }
|
|
112
|
+
.skill-walk__facts dd { margin: 0; color: var(--ink); line-height: 1.5; }
|
|
113
|
+
.skill-walk__note { font-size: 0.8rem; }
|
|
114
|
+
/* "Leave it off" and "Turn it on" are the panel family's plain pill at EQUAL
|
|
115
|
+
weight: the default is off, so the page must not lean on the reader toward on.
|
|
116
|
+
Only "Save my choices" takes the accent. A decision already made reads through
|
|
117
|
+
aria-pressed when the person steps Back to it. */
|
|
118
|
+
.skill-walk__actions { flex-wrap: wrap; }
|
|
119
|
+
.skill-walk__actions .pbtn[aria-pressed="true"] { border-color: var(--ink-soft); }
|
|
120
|
+
.skill-walk__recap { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
121
|
+
.skill-walk__recap li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.4rem; }
|
|
122
|
+
.skill-walk__recap .prow__hint { flex-basis: 100%; margin: 0; }
|
|
123
|
+
.skill-walk__recap strong { font-family: var(--font-mono); font-weight: 600; }
|
|
124
|
+
/* "Finish later" is a way out, not a choice about a skill, so it reads as a quiet
|
|
125
|
+
link below the decision rather than a third button beside it. */
|
|
126
|
+
.skill-walk__later {
|
|
127
|
+
align-self: flex-start;
|
|
128
|
+
background: none;
|
|
129
|
+
border: 0;
|
|
130
|
+
padding: 0 0.25rem;
|
|
131
|
+
margin-inline-start: -0.25rem;
|
|
132
|
+
min-height: 24px;
|
|
133
|
+
font: inherit;
|
|
134
|
+
font-size: 0.8rem;
|
|
135
|
+
color: var(--ink-soft);
|
|
136
|
+
text-decoration: underline;
|
|
137
|
+
text-underline-offset: 3px;
|
|
138
|
+
cursor: pointer;
|
|
139
|
+
}
|
|
140
|
+
.skill-walk__later:hover { color: var(--ink); }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// modules/hall-ui/public/
|
|
2
|
-
//
|
|
1
|
+
// modules/hall-ui/public/speciality-tools.js — THE SPECIALITY TOOLS, one component
|
|
2
|
+
// mounted by two pages: Settings' "Your specialities" panel (task 1003855; ADR
|
|
3
|
+
// 0296) and My agents' "What it knows" section (task 1005052).
|
|
3
4
|
//
|
|
4
5
|
// WHAT A SPECIALITY IS, for anyone reading this file first: a craft says HOW you
|
|
5
6
|
// work (engineer / ideator / artist / ui); a speciality says what you work ON and
|
|
@@ -7,10 +8,21 @@
|
|
|
7
8
|
// contract you write here is injected into every session where you hold a claim
|
|
8
9
|
// in that craft.
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
11
|
+
// ONE COMPONENT, TWO MOUNTS (task 1005052). This was settings-specialities.js;
|
|
12
|
+
// it was carved out so My agents gets every tool Settings has without a second
|
|
13
|
+
// copy. A page mounts it by carrying the hosts, by id, inside one section:
|
|
14
|
+
// #specialities-scroll the section (ships `hidden`; revealed by load())
|
|
15
|
+
// #speciality-suggestions what the project suggests
|
|
16
|
+
// #specialities-rows the catalogue, one card per speciality (a <ul>)
|
|
17
|
+
// #speciality-create an EMPTY <form>: createFormHtml() below fills it,
|
|
18
|
+
// so the create form's markup exists once, here
|
|
19
|
+
// #specialities-status the status line
|
|
20
|
+
// Nothing else is page-specific: each page's own sheet draws the same markup in
|
|
21
|
+
// its own look (Settings through panel.css, My agents through my-agents.css's
|
|
22
|
+
// `.ma-know` skin; the structural rules are speciality-tools.css). Task 1005044
|
|
23
|
+
// removes the Settings mount (its section and this file's script tag); the code
|
|
24
|
+
// stays. Self-contained — it shares nothing with either page but `window.OTB`,
|
|
25
|
+
// loaded before it, and one DOM event it dispatches (changed(), below).
|
|
14
26
|
//
|
|
15
27
|
// TRAINING IS TWO-FOLD, and only half of it is visible here. Documents you add are
|
|
16
28
|
// the half you drive; the other half accumulates on its own from learnings captured
|
|
@@ -33,6 +45,13 @@
|
|
|
33
45
|
const el = (id) => document.getElementById(id);
|
|
34
46
|
const statusLine = (msg) => { const s = el('specialities-status'); if (s) s.textContent = msg || ''; };
|
|
35
47
|
|
|
48
|
+
// What a session is sent just moved (an adopt, a stop, a skills save): say so,
|
|
49
|
+
// so a page that shows it can read it again. My agents' "What your agent reads
|
|
50
|
+
// first" listens (task 1005052); Settings does not, and nothing here waits on it.
|
|
51
|
+
function changed() {
|
|
52
|
+
if (typeof CustomEvent === 'function' && document.dispatchEvent) document.dispatchEvent(new CustomEvent('otb:specialities-changed'));
|
|
53
|
+
}
|
|
54
|
+
|
|
36
55
|
async function call(method, path, body) {
|
|
37
56
|
const res = await fetch(`${API}${path}`, {
|
|
38
57
|
method,
|
|
@@ -203,6 +222,7 @@
|
|
|
203
222
|
state.walk = null;
|
|
204
223
|
toast(`Saved — ${enabled.length} of ${steps.length} skill${steps.length === 1 ? '' : 's'} on.`);
|
|
205
224
|
await load();
|
|
225
|
+
changed();
|
|
206
226
|
return;
|
|
207
227
|
} catch (err) {
|
|
208
228
|
w.saving = false;
|
|
@@ -458,6 +478,7 @@
|
|
|
458
478
|
if (state.walk && state.walk.id === String(id)) state.walk = null;
|
|
459
479
|
}
|
|
460
480
|
await load();
|
|
481
|
+
if (action === 'adopt' || action === 'abandon') changed();
|
|
461
482
|
// Adopting is the moment the ruling is about: a speciality that offers
|
|
462
483
|
// skills opens straight into the walkthrough, with every one still off.
|
|
463
484
|
if (action === 'adopt') {
|
|
@@ -585,8 +606,64 @@
|
|
|
585
606
|
});
|
|
586
607
|
}
|
|
587
608
|
|
|
609
|
+
// THE CREATE FORM, rendered into the page's empty `<form id="speciality-create">`
|
|
610
|
+
// (task 1005052). It lived in settings.html until a second page mounted these
|
|
611
|
+
// tools; here it is one copy both pages share, filled before wire() reads its
|
|
612
|
+
// fields. The markup is the one settings.html carried, unchanged.
|
|
613
|
+
function createFormHtml() {
|
|
614
|
+
return `
|
|
615
|
+
<h3 class="speciality-create__h">Create one</h3>
|
|
616
|
+
<label class="speciality-field">
|
|
617
|
+
<span>Name</span>
|
|
618
|
+
<input type="text" name="name" maxlength="80" required placeholder="Economics" />
|
|
619
|
+
</label>
|
|
620
|
+
<label class="speciality-field">
|
|
621
|
+
<span>Role it sits under</span>
|
|
622
|
+
<select name="discipline" required>
|
|
623
|
+
<option value="">Pick one…</option>
|
|
624
|
+
<option value="engineer">Engineer</option>
|
|
625
|
+
<option value="ideator">Ideator</option>
|
|
626
|
+
<option value="artist">Artist</option>
|
|
627
|
+
<option value="ui">UI design</option>
|
|
628
|
+
</select>
|
|
629
|
+
</label>
|
|
630
|
+
<label class="speciality-field">
|
|
631
|
+
<span>Summary <small>(optional)</small></span>
|
|
632
|
+
<input type="text" name="summary" maxlength="500" placeholder="Markets, incentives, and how to price them." />
|
|
633
|
+
</label>
|
|
634
|
+
<label class="speciality-field">
|
|
635
|
+
<span>How Claude should engage you in it <small>(optional)</small></span>
|
|
636
|
+
<textarea name="contract_md" rows="4" maxlength="4000" placeholder="Argue from mechanism, not vibes. Always name the assumption a number rests on."></textarea>
|
|
637
|
+
</label>
|
|
638
|
+
<label class="speciality-field">
|
|
639
|
+
<span>Who can use it</span>
|
|
640
|
+
<select name="visibility">
|
|
641
|
+
<option value="private">Only me</option>
|
|
642
|
+
<option value="project">Anyone in the owning project</option>
|
|
643
|
+
<option value="public">Anyone on this instance</option>
|
|
644
|
+
</select>
|
|
645
|
+
</label>
|
|
646
|
+
<label class="speciality-field speciality-field--check">
|
|
647
|
+
<input type="checkbox" name="sellable" />
|
|
648
|
+
<span>
|
|
649
|
+
May be offered for sale later
|
|
650
|
+
<small>Records your decision only — there is no marketplace yet, and nothing is listed anywhere.</small>
|
|
651
|
+
</span>
|
|
652
|
+
</label>
|
|
653
|
+
<details id="speciality-create-offer" class="speciality-offer-box">
|
|
654
|
+
<summary>Skills it offers <small>(optional)</small></summary>
|
|
655
|
+
<div id="speciality-create-skills"><p class="hall-loading hall-loading--inline" role="status">Loading this project’s skills…</p></div>
|
|
656
|
+
</details>
|
|
657
|
+
<div class="speciality-actions">
|
|
658
|
+
<button type="submit" class="btn">Create speciality</button>
|
|
659
|
+
</div>
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
|
|
588
663
|
function boot() {
|
|
589
664
|
if (!el('specialities-rows')) return; // panel not on this page
|
|
665
|
+
const create = el('speciality-create');
|
|
666
|
+
if (create) create.innerHTML = createFormHtml();
|
|
590
667
|
wire();
|
|
591
668
|
load();
|
|
592
669
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# My agents, and governing as a held seat — /my-agents and the nav (task 1004685, the living-city prototype)
|
|
2
2
|
|
|
3
|
-
Covers: `my-agents.{html,js,css,states.json}`, `gov-positions.js`, the `position` flag in `shell.js` NAV, the seat in `craft-mode.js`, and the harness recorder `scripts/hall-preview/pref-writes.js`. Built on round-3 boards 6 ("the city and My agents"), 7 ("how strict") and 8 ("governance is a position") of the living-city design canvas. **Every row is real since task 1004714**: the settings, the laws' locks and caps, and Make it a law. The strictness preset strip is real too since task 1004715 (`GET /government/presets`, [ADR 0380](../../../docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md)).
|
|
3
|
+
Covers: `my-agents.{html,js,css,states.json}`, the speciality tools (`speciality-tools.{js,css}`, task 1005052), Settings' pointer here and its old-link map (`settings-moved.js`, task 1005044), `gov-positions.js`, the `position` flag in `shell.js` NAV, the seat in `craft-mode.js`, and the harness recorder `scripts/hall-preview/pref-writes.js`. Built on round-3 boards 6 ("the city and My agents"), 7 ("how strict") and 8 ("governance is a position") of the living-city design canvas. **Every row is real since task 1004714**: the settings, the laws' locks and caps, and Make it a law. The strictness preset strip is real too since task 1004715 (`GET /government/presets`, [ADR 0380](../../../docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md)).
|
|
4
4
|
|
|
5
5
|
## What it is
|
|
6
6
|
|
|
7
|
-
- **My agents** (in You, after Profile): every setting that shapes how your own Claude works, one line each — a name of at most three words, one fact, one-tap chips (the owner's desk line rule). Real rows come from `GET /me` (interaction profile, wandering, model allocation, session cards), `GET /me/skill-prefs
|
|
7
|
+
- **My agents** (in You, after Profile): every setting that shapes how your own Claude works, one line each — a name of at most three words, one fact, one-tap chips (the owner's desk line rule). Real rows come from `GET /me` (interaction profile, wandering, model allocation, session cards), `GET /me/skill-prefs` and the sounds switch; they write to the real routes. Session cards and each skill voice offer their way back to the default as the FIRST chip (`follow my profile`, `default`; task 1005052): it writes `null`, which clears your own pick rather than pinning today's value, and a level or model is pressed only when it is your own pick. The five settings no table held (when stuck, when it asks, how it asks, at once, going live) were dropped by task 1004714 rather than shown as settings nothing stores or obeys; how an agent asks is a project rule now (the ask shape, ADR 0372-0373).
|
|
8
8
|
- **Only a law in force locks a row** (task 1004714). The rows read gov-living's model, which holds nothing but real reads (task 1004717 deleted the prototype overlay), so nothing else locks, caps or advises one. The strictness strip (real, task 1004715) names the preset the laws in force amount to ("No preset yet" on the hub) and previews what each WOULD lock of these rows — the server's locks after a switch, fixed or capped (Open workshop none, Guild off-task, Factory off-task and the three model slots) — with a tile and one pip per lockable row; it never locks a row. Previewing another preset offers "put it to a vote" to a builder who may open a board item: one real laws vote (`POST /government/board/presets`).
|
|
9
9
|
- **What your agent reads first** quotes the REAL contract text `/me` carries (the Conductor relays it), says plainly when a knob is not sent (permissive wandering is `inject: false`), and puts the project's REAL laws above it: `/me`'s `laws` block, the very words every session gets first (task 1004707). With no law in force that block is not drawn.
|
|
10
10
|
- **A real law's lock is real.** `/me`'s `laws.locks` says what each law in force did to your settings; the server already applied it (the slice's effective value obeys it), so the row is locked, naming the law and its number in the real book, with your own choice underneath. A law that only CAPS a setting (`{ setting, max }`) does not lock the row: the server names every value it still allows (`allowed` on each lock, `law-locks.js` allowedFor — the same list a refused PATCH carries), the chips offer those, and the line says `capped by <law> · Law N`. A cap that leaves one value, or a server that names none, reads as the whole-row lock. A lock the server could not enforce (a setting no table holds yet) locks nothing. A write the law forbids answers 409 `locked_by_law`, and the toast says the law.
|
|
11
11
|
- **Make it a law** lists your own non-default choices that no law already locks or caps; proposing one files a real laws vote (`POST /government/board/laws`, task 1004706): one law, in the desk's voice, locking the setting to your value, sat under the constitution in force. It is offered only to a builder `/me` says may open a board item (`permissions['board.item.open']`, one of `NAV_PERMISSION_ATOMS`); an unreadable map offers nothing. A filed habit links to its vote in the Board Room.
|
|
12
|
+
- **What it knows is the speciality tools, not desk rows** (task 1005052). The section `#specialities-scroll` under the desk is filled by `speciality-tools.js` — the SAME component Settings mounts (it was `settings-specialities.js`), finding its hosts by id: create (it renders the create form into the page's empty `<form id="speciality-create">`, so the markup exists once), train, offered skills, adopt with someone else's full instructions open in the same card, the skills walkthrough, stop using, and the project's suggestions. It is its own section because a desk row holds at most two buttons and `my-agents.js` redraws `#ma-rows` on every change, which would wipe a half-filled form. Its markup is in the panel family's words (`.prow`, `.btn`, `.pbtn`); this page has no `panel.css`, so `my-agents.css`'s `.ma-know` skin draws them here as hairline rows and ghost pills, no accent (the structure is `speciality-tools.css`, shared). The desk's old one-tap adopt chips are gone: they adopted someone else's speciality without showing its instructions. After an adopt, a stop or a skills save the component dispatches `otb:specialities-changed` and this page re-reads `/me`, so "What your agent reads first" stays true. Since task 1005044 this is the component's only host. Tests: `tests/speciality_offer_ui.mjs` and `tests/speciality_walkthrough_ui.mjs` run every tool on this page's own markup (`tests/helpers.mjs` `bootSpecialityTools`, `SPECIALITY_PAGES`). Kit states `what-it-knows`, `speciality-walk`, `speciality-forms`; the harness serves an in-use Pricing (`me__specialities.json`) and canned stop-using and train answers.
|
|
13
|
+
- **This is the ONE page for the agent settings** (task 1005044, the owner's call on idea 1001309). How Claude talks to you, Wandering, Session cards, Skill voices and the specialities were edited on Settings too, on the same routes, with no hint the two were one setting. Settings now carries none of them: a line in its Preferences group ("Your agents") and one in Sound ("Skill voices") link here, and `settings-moved.js` sends every old deep link (`/settings#interaction`, `#wandering`, `#rendering`, `#render`, `#voices`, `#specialities` and the old section ids) on with `location.replace`. **The anchors this page lands** are `my-agents.js` `ANCHORS`: `#talk`, `#work`, `#models` (the desk groups, `id="ma-group-<key>"`), `#skills` (opens Per skill) and `#knows` (What it knows, watched until speciality-tools.js reveals it — no timer, the motion test allows one). The palette offers the five (`palette.js` SECTIONS, `tests/hall_palette.mjs`). Settings' old panels went with their only host: `settings-interaction.js`, `settings-render.js`, `settings-law-locks.js` are deleted; what each promised is now proven here (`tests/hall_my_agents.mjs` `MOVED:`, `tests/government_tidy_up.mjs` case 5). Not carried over on purpose: Settings drew wandering levels above your rank's ceiling as disabled rows; here they are simply not offered.
|
|
14
|
+
- **The fact keeps a floor.** The row grid is `minmax(118px, 160px) minmax(132px, 1fr) auto` since task 1005052: session cards' five chips wrap rather than squeeze its fact to one word a line.
|
|
12
15
|
- **Governing is a held seat** (real since task 1004713, ADR 0377). `GET /me` carries `positions`, resolved by the server (`modules/government/positions.js`: the owner seat, a Lead in office, a seat on a building's sitting, the board under the constitution in force). `gov-positions.js` only reads that block — no constitution or rank. Items flagged `position: true` (the City, the Board Room, the Government group) hide for a builder the server says holds none, and the craft mode names Governing by the seat ("Planning Lead") or drops it. A `/me` with no block is unknown and keeps today's nav.
|
|
13
16
|
|
|
14
17
|
## The rules an edit here must keep
|
|
@@ -29,4 +29,8 @@ Also fixed here, as the board task handed forward: the two sub-24px links on Hom
|
|
|
29
29
|
|
|
30
30
|
**Settings: adopting a speciality walks through its skills (task 1004073, ADR 0310 §3)** — the owner ruled an adopted speciality's skills start OFF "and you should walk through what comes with each". So adopting one that offers skills opens `.skill-walk` inside its card: one skill per step (what it does, when to reach for it, what it costs, what it needs, who can run it), "Leave it off" / "Turn it on" at EQUAL weight (plain `.pbtn`, no accent — the default is off, so the page must not lean toward on), Back, then a recap restating each skill in a line, then ONE `PUT` of the whole set. "Finish later" saves nothing. Focus moves to each new step heading. Two things found on the way: the specialities panel shipped `hidden` and nothing ever revealed it, so adoption was unreachable from the hall; and its contract `<summary>` measured 21px once visible (now 24). **Kit:** `settings.states.json` `skills-walkthrough` + `preferences`, ALL CLEAN at 1440/390/320 × dark/light against the harness, with fixtures `specialities*.json` built from the real skills on disk and canned answers for adopt and save. Tests: `tests/speciality_walkthrough_ui.mjs` (runs the real page script).
|
|
31
31
|
|
|
32
|
+
**Settings: the speciality panel became a shared component (task 1005052)** — `settings-specialities.js` is now `speciality-tools.js`, mounted by Settings and by My agents (records/my-agents.md); it renders the create form into settings.html's now-empty `<form id="speciality-create">`, and its rules moved verbatim from `settings.css` to `speciality-tools.css`, linked right after it. Settings was verified unchanged against a before-capture: the same elements, every computed style and the same text in five states (rest, train, offered skills, the create picker, the walkthrough). **Known gap, older than this:** the `skills-walkthrough` and `offered-skills` states never close the first-visit craft picker, so their clicks time out under the kit and the page reader (both notes are on main); with a `clickIfVisible [data-craft-close]` step they are ALL CLEAN.
|
|
33
|
+
|
|
32
34
|
**Settings: a speciality's owner chooses what it offers (task 1004404, ADR 0310 §1)** — until this only the API could set an offer, so the walkthrough above never appeared for a speciality made in the hall. The create form gains a `<details>` "Skills it offers" picker (filled on first open, so a builder who never offers one never pays for the list), and an owned card gains "Offered skills", an inline `form.speciality-offer` like Train. Both list `GET /specialities/installed-skills` — only what this instance has — each row a checkbox with the skill's plain line and cost, the words adopters will see, under a line saying offering turns nothing on. A "Find a skill" box narrows the sixty-odd rows (Enter never submits), the list scrolls in its own box, an offered skill removed since is named and dropped on save rather than silently kept, and saving is ONE `PATCH { skills }`. **The checkboxes take the shared 24px rule in `panel.css`** (the `.chip-ctl input` / `.craft-pref` block, one selector added) rather than a fourth copy of the `::before` box; without it 128 targets measured 13px. **Kit:** `settings.states.json` `offered-skills` + `preferences`, ALL CLEAN at 1440/390/320 × dark/light, with fixtures `specialities__installed-skills.json` (the real 64 skills, explained) and an owned speciality in `specialities.json`, plus canned POST/PATCH answers. Tests: `tests/speciality_offer_ui.mjs` (runs the real page script).
|
|
35
|
+
|
|
36
|
+
**Settings: the agent settings left for My agents (task 1005044)** — How Claude talks to you, the specialities, Wandering, Rendering and Skill voices are gone from Settings (owner decision, idea 1001309): they were edited here and on My agents on the same routes. Preferences keeps Module install reports plus a `#agents-moved-preferences` line linking `/my-agents#talk`; Sound keeps the sounds-off note plus `#agents-moved-sound` linking `/my-agents#skills`. Old deep links go on through `settings-moved.js` (records/my-agents.md). `settings-interaction.js`, `settings-render.js` and `settings-law-locks.js` were deleted with their only host, and `settings.css` lost the voices table. The `skills-walkthrough` and `offered-skills` states left `settings.states.json` (My agents' `speciality-walk` and `speciality-forms` cover them). `tests/hall_settings_world.mjs` pins the absence, the two lines and the redirects.
|