@bongos/core 1.19.763 → 1.19.765

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.
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1751,
7
- "unique_ids": 1515,
8
- "certain": 1751,
6
+ "strings": 1756,
7
+ "unique_ids": 1519,
8
+ "certain": 1756,
9
9
  "heuristic": 0,
10
10
  "duplicate_ids": 164
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 80,
16
- "strings": 1272
15
+ "files": 81,
16
+ "strings": 1277
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -38,6 +38,22 @@
38
38
  "The design-contract surface layout: `modules/ui-design/docs/design-contract.md` expects `src/ui/<surface>/`, which this tree does not use (there is no `src/ui/`; the adapters live in `modules/ui-design/adapters/`). Surface ids here are a MAPPING onto real paths, not evidence the contract layout exists."
39
39
  ],
40
40
  "duplicates": [
41
+ {
42
+ "id": "8c234d5c8bd4",
43
+ "surface": "builders-hall",
44
+ "text": "Could not save. Try again.",
45
+ "occurrences": 8,
46
+ "at": [
47
+ "modules/hall-ui/public/settings-interaction.js:96",
48
+ "modules/hall-ui/public/settings.js:103",
49
+ "modules/hall-ui/public/settings.js:460",
50
+ "modules/hall-ui/public/settings.js:529",
51
+ "modules/hall-ui/public/settings.js:683",
52
+ "modules/hall-ui/public/settings.js:782",
53
+ "modules/hall-ui/public/settings.js:862",
54
+ "modules/hall-ui/public/settings.js:1161"
55
+ ]
56
+ },
41
57
  {
42
58
  "id": "0fbe47b01d2c",
43
59
  "surface": "landing",
@@ -69,21 +85,6 @@
69
85
  "modules/public-landing/public/projects.html:1148"
70
86
  ]
71
87
  },
72
- {
73
- "id": "8c234d5c8bd4",
74
- "surface": "builders-hall",
75
- "text": "Could not save. Try again.",
76
- "occurrences": 7,
77
- "at": [
78
- "modules/hall-ui/public/settings.js:103",
79
- "modules/hall-ui/public/settings.js:460",
80
- "modules/hall-ui/public/settings.js:529",
81
- "modules/hall-ui/public/settings.js:683",
82
- "modules/hall-ui/public/settings.js:782",
83
- "modules/hall-ui/public/settings.js:862",
84
- "modules/hall-ui/public/settings.js:1161"
85
- ]
86
- },
87
88
  {
88
89
  "id": "5df2baabf82e",
89
90
  "surface": "landing",
@@ -1467,7 +1468,7 @@
1467
1468
  "occurrences": 2,
1468
1469
  "at": [
1469
1470
  "modules/hall-ui/public/settings.js:856",
1470
- "modules/hall-ui/public/settings.js:1413"
1471
+ "modules/hall-ui/public/settings.js:1414"
1471
1472
  ]
1472
1473
  },
1473
1474
  {
@@ -1946,7 +1947,7 @@
1946
1947
  "surface": "builders-hall",
1947
1948
  "text": ", claiming, and shipping show — render as rich in-feed widgets or as plain text. Widgets are prettier but spend tokens; plain text is cheaper with the same information.",
1948
1949
  "file": "modules/hall-ui/public/settings.html",
1949
- "line": 277,
1950
+ "line": 295,
1950
1951
  "origin": "html-text",
1951
1952
  "confidence": "certain"
1952
1953
  },
@@ -2198,7 +2199,7 @@
2198
2199
  "surface": "builders-hall",
2199
2200
  "text": "A sound plays at each turn of your work — a gentle chime when Claude finishes, a flourish when a task ships or your rank rises. Turn off any you would rather not hear.",
2200
2201
  "file": "modules/hall-ui/public/settings.html",
2201
- "line": 312,
2202
+ "line": 330,
2202
2203
  "origin": "html-text",
2203
2204
  "confidence": "certain"
2204
2205
  },
@@ -2963,7 +2964,7 @@
2963
2964
  "surface": "builders-hall",
2964
2965
  "text": "Changes save at once and take effect the next time you start a session on this machine.",
2965
2966
  "file": "modules/hall-ui/public/settings.html",
2966
- "line": 323,
2967
+ "line": 341,
2967
2968
  "origin": "html-text",
2968
2969
  "confidence": "certain"
2969
2970
  },
@@ -2972,7 +2973,7 @@
2972
2973
  "surface": "builders-hall",
2973
2974
  "text": "Changes save at once. Your new choice plays the next time that event fires.",
2974
2975
  "file": "modules/hall-ui/public/settings.html",
2975
- "line": 336,
2976
+ "line": 354,
2976
2977
  "origin": "html-text",
2977
2978
  "confidence": "certain"
2978
2979
  },
@@ -2981,7 +2982,7 @@
2981
2982
  "surface": "builders-hall",
2982
2983
  "text": "Changes save the moment you pick. “Default” clears your override and falls back to the system's choice.",
2983
2984
  "file": "modules/hall-ui/public/settings.html",
2984
- "line": 306,
2985
+ "line": 324,
2985
2986
  "origin": "html-text",
2986
2987
  "confidence": "certain"
2987
2988
  },
@@ -3053,7 +3054,7 @@
3053
3054
  "surface": "builders-hall",
3054
3055
  "text": "Choose which sound plays for each build event — one when Claude waits for your input, another when a task ships, a coin clink when {{currency}} arrive. Each event offers a small set of choices; more will be added as new audio ships.",
3055
3056
  "file": "modules/hall-ui/public/settings.html",
3056
- "line": 329,
3057
+ "line": 347,
3057
3058
  "origin": "html-text",
3058
3059
  "confidence": "certain"
3059
3060
  },
@@ -3818,7 +3819,7 @@
3818
3819
  "surface": "builders-hall",
3819
3820
  "text": "Could not load your account. Reload the page to try again.",
3820
3821
  "file": "modules/hall-ui/public/settings.js",
3821
- "line": 1396,
3822
+ "line": 1397,
3822
3823
  "origin": "js-text-assign",
3823
3824
  "confidence": "certain"
3824
3825
  },
@@ -3845,7 +3846,7 @@
3845
3846
  "surface": "builders-hall",
3846
3847
  "text": "Could not load your settings.",
3847
3848
  "file": "modules/hall-ui/public/settings.js",
3848
- "line": 1399,
3849
+ "line": 1400,
3849
3850
  "origin": "js-notice",
3850
3851
  "confidence": "certain"
3851
3852
  },
@@ -3948,6 +3949,15 @@
3948
3949
  "origin": "js-notice",
3949
3950
  "confidence": "certain"
3950
3951
  },
3952
+ {
3953
+ "id": "cfe6c5ceb3ef",
3954
+ "surface": "builders-hall",
3955
+ "text": "Could not save your interaction profile. Try again.",
3956
+ "file": "modules/hall-ui/public/settings-interaction.js",
3957
+ "line": 97,
3958
+ "origin": "js-notice",
3959
+ "confidence": "certain"
3960
+ },
3951
3961
  {
3952
3962
  "id": "ea4fb30debd0",
3953
3963
  "surface": "builders-hall",
@@ -4029,6 +4039,15 @@
4029
4039
  "origin": "js-notice",
4030
4040
  "confidence": "certain"
4031
4041
  },
4042
+ {
4043
+ "id": "8c234d5c8bd4",
4044
+ "surface": "builders-hall",
4045
+ "text": "Could not save. Try again.",
4046
+ "file": "modules/hall-ui/public/settings-interaction.js",
4047
+ "line": 96,
4048
+ "origin": "js-text-assign",
4049
+ "confidence": "certain"
4050
+ },
4032
4051
  {
4033
4052
  "id": "8c234d5c8bd4",
4034
4053
  "surface": "builders-hall",
@@ -4583,7 +4602,7 @@
4583
4602
  "surface": "builders-hall",
4584
4603
  "text": "Each slash-command can run on a different model. The system has a default for every skill; choose another to override it, or leave it on Default.",
4585
4604
  "file": "modules/hall-ui/public/settings.html",
4586
- "line": 292,
4605
+ "line": 310,
4587
4606
  "origin": "html-text",
4588
4607
  "confidence": "certain"
4589
4608
  },
@@ -4655,7 +4674,7 @@
4655
4674
  "surface": "builders-hall",
4656
4675
  "text": "Event sounds",
4657
4676
  "file": "modules/hall-ui/public/settings.html",
4658
- "line": 328,
4677
+ "line": 346,
4659
4678
  "origin": "html-text",
4660
4679
  "confidence": "certain"
4661
4680
  },
@@ -5316,6 +5335,15 @@
5316
5335
  "origin": "html-text",
5317
5336
  "confidence": "certain"
5318
5337
  },
5338
+ {
5339
+ "id": "0469a83cf1e8",
5340
+ "surface": "builders-hall",
5341
+ "text": "How Claude talks to you",
5342
+ "file": "modules/hall-ui/public/settings.html",
5343
+ "line": 260,
5344
+ "origin": "html-text",
5345
+ "confidence": "certain"
5346
+ },
5319
5347
  {
5320
5348
  "id": "68382894d8f9",
5321
5349
  "surface": "builders-hall",
@@ -5357,7 +5385,16 @@
5357
5385
  "surface": "builders-hall",
5358
5386
  "text": "How readily Claude opens a",
5359
5387
  "file": "modules/hall-ui/public/settings.html",
5360
- "line": 259,
5388
+ "line": 277,
5389
+ "origin": "html-text",
5390
+ "confidence": "certain"
5391
+ },
5392
+ {
5393
+ "id": "63dbe9873b4e",
5394
+ "surface": "builders-hall",
5395
+ "text": "How replies are pitched for you — the vocabulary assumed, how much hand-holding local tooling gets, how long an answer runs, and how much of the work behind it is narrated. These reach every session you start. The defaults assume no software-engineering background; change any of them and the rest stay as they are.",
5396
+ "file": "modules/hall-ui/public/settings.html",
5397
+ "line": 261,
5361
5398
  "origin": "html-text",
5362
5399
  "confidence": "certain"
5363
5400
  },
@@ -5897,7 +5934,7 @@
5897
5934
  "surface": "builders-hall",
5898
5935
  "text": "Latest version: v{…}.",
5899
5936
  "file": "modules/hall-ui/public/settings.js",
5900
- "line": 1482,
5937
+ "line": 1485,
5901
5938
  "origin": "js-text-assign",
5902
5939
  "confidence": "certain"
5903
5940
  },
@@ -6536,7 +6573,7 @@
6536
6573
  "surface": "builders-hall",
6537
6574
  "text": "No builder account found for this sign-in.",
6538
6575
  "file": "modules/hall-ui/public/settings.js",
6539
- "line": 1404,
6576
+ "line": 1405,
6540
6577
  "origin": "js-text-assign",
6541
6578
  "confidence": "certain"
6542
6579
  },
@@ -8291,7 +8328,7 @@
8291
8328
  "surface": "builders-hall",
8292
8329
  "text": "Saves the moment you toggle; applies from your next session.",
8293
8330
  "file": "modules/hall-ui/public/settings.html",
8294
- "line": 284,
8331
+ "line": 302,
8295
8332
  "origin": "html-text",
8296
8333
  "confidence": "certain"
8297
8334
  },
@@ -8768,7 +8805,7 @@
8768
8805
  "surface": "builders-hall",
8769
8806
  "text": "Skill voices",
8770
8807
  "file": "modules/hall-ui/public/settings.html",
8771
- "line": 291,
8808
+ "line": 309,
8772
8809
  "origin": "html-text",
8773
8810
  "confidence": "certain"
8774
8811
  },
@@ -10379,7 +10416,7 @@
10379
10416
  "surface": "builders-hall",
10380
10417
  "text": "Whether your session cards — what",
10381
10418
  "file": "modules/hall-ui/public/settings.html",
10382
- "line": 276,
10419
+ "line": 294,
10383
10420
  "origin": "html-text",
10384
10421
  "confidence": "certain"
10385
10422
  },
@@ -10685,7 +10722,7 @@
10685
10722
  "surface": "builders-hall",
10686
10723
  "text": "Your choice",
10687
10724
  "file": "modules/hall-ui/public/settings.html",
10688
- "line": 300,
10725
+ "line": 318,
10689
10726
  "origin": "html-text",
10690
10727
  "confidence": "certain"
10691
10728
  },
@@ -10761,6 +10798,15 @@
10761
10798
  "origin": "js-markup",
10762
10799
  "confidence": "certain"
10763
10800
  },
10801
+ {
10802
+ "id": "00fb37fd5adf",
10803
+ "surface": "builders-hall",
10804
+ "text": "Your interaction profile could not be loaded. Reload the page to try again.",
10805
+ "file": "modules/hall-ui/public/settings-interaction.js",
10806
+ "line": 122,
10807
+ "origin": "js-markup",
10808
+ "confidence": "certain"
10809
+ },
10764
10810
  {
10765
10811
  "id": "51fd6f1ecb2f",
10766
10812
  "surface": "builders-hall",
@@ -10838,7 +10884,7 @@
10838
10884
  "surface": "builders-hall",
10839
10885
  "text": "Your preferences, {…}.",
10840
10886
  "file": "modules/hall-ui/public/settings.js",
10841
- "line": 1413,
10887
+ "line": 1414,
10842
10888
  "origin": "js-text-assign",
10843
10889
  "confidence": "certain"
10844
10890
  },
@@ -11684,7 +11730,7 @@
11684
11730
  "surface": "builders-hall",
11685
11731
  "text": "new thread",
11686
11732
  "file": "modules/hall-ui/public/settings.html",
11687
- "line": 260,
11733
+ "line": 278,
11688
11734
  "origin": "html-text",
11689
11735
  "confidence": "certain"
11690
11736
  },
@@ -11819,7 +11865,7 @@
11819
11865
  "surface": "builders-hall",
11820
11866
  "text": "of work — a tangent, an investigation, an unprompted suggestion — instead of staying on your claimed task. It never limits the work on the task itself. Your rank sets the starting point; newcomers stay locked low.",
11821
11867
  "file": "modules/hall-ui/public/settings.html",
11822
- "line": 260,
11868
+ "line": 278,
11823
11869
  "origin": "html-text",
11824
11870
  "confidence": "certain"
11825
11871
  },
@@ -2000,5 +2000,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2000
2000
  landed since 1.19.761 with no explicit bump. run 35053643269. (task 1002620)
2001
2001
  1.19.763 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2002
2002
  landed since 1.19.762 with no explicit bump. run 35056690992. (task 1002620)
2003
+ 1.19.764 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2004
+ landed since 1.19.763 with no explicit bump. run 35059447999. (task 1002620)
2005
+ 1.19.765 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2006
+ landed since 1.19.764 with no explicit bump. run 35102327521. (task 1002620)
2003
2007
  ---------------------------------------------------------------------------
2004
2008
  ```
@@ -23,9 +23,26 @@
23
23
  const RESERVED_KEY = 'interaction';
24
24
 
25
25
  // Each field: allowed values (first = default), a short label per value, and a
26
- // one-line contract fragment injected when the value is NON-default (the default
27
- // already matches the portable baseline, so injecting it would be noise —
28
- // mirrors wandering's `inject` idea).
26
+ // contract fragment injected when that value is the effective one.
27
+ //
28
+ // WHY THE DEFAULTS CARRY REAL TEXT, unlike wandering (task 1002514, clause 1 of
29
+ // criterion `accessibility-fluency-profile`). Wandering's default is "do what
30
+ // you'd naturally do", so injecting it would be noise — hence `inject:false` on
31
+ // its permissive levels. This knob's default is NOT that. `non_technical` /
32
+ // `new` / `concise` is a SPECIFIC accessible posture, the one CLAUDE.md §2 spells
33
+ // out as portable prose, and the builder who most needs it is exactly the one
34
+ // who never opens Settings. Leaving the default silent meant the accessible-by-
35
+ // default builder was the only builder the profile said nothing about — the
36
+ // profile worked for everyone who had already opted out of it.
37
+ //
38
+ // The exception that proves the rule is `machinery: show`, which really does mean
39
+ // "behave as you otherwise would" and so stays empty. A default earns injected
40
+ // text by encoding a posture, not by being a default.
41
+ //
42
+ // This does mean every session now carries a short contract. That is the point:
43
+ // the posture has to arrive somewhere, and a knob it can be tuned with beats
44
+ // prose it cannot. Task 1002515 removes the CLAUDE.md §2 duplication that this
45
+ // briefly doubles.
29
46
  const FIELDS = {
30
47
  technical_level: {
31
48
  label: 'Technical level',
@@ -33,8 +50,7 @@ const FIELDS = {
33
50
  values: {
34
51
  non_technical: {
35
52
  label: 'Non-technical (default)',
36
- // default — matches the portable baseline; not injected.
37
- contract: '',
53
+ contract: 'Technical level: non-technical — do not assume software-engineering vocabulary, and define a term the first time you need it. Frame trade-offs as cost, time and what the end user will see, not as design patterns or framework taxonomy. Ask about outcomes and product behaviour; decide implementation details yourself and present them for confirmation.',
38
54
  },
39
55
  mixed: {
40
56
  label: 'Some technical background',
@@ -52,7 +68,7 @@ const FIELDS = {
52
68
  values: {
53
69
  new: {
54
70
  label: 'New to local tooling (default)',
55
- contract: '',
71
+ contract: 'Toolchain familiarity: new — walk local tooling (package managers, ports, env vars, shells) through step by step rather than naming a command and moving on. Where a supported UI path exists, give that before the terminal one.',
56
72
  },
57
73
  comfortable: {
58
74
  label: 'Comfortable with local tooling',
@@ -66,7 +82,7 @@ const FIELDS = {
66
82
  values: {
67
83
  concise: {
68
84
  label: 'Concise (default)',
69
- contract: '',
85
+ contract: 'Verbosity: concise — lead with the answer and stop; they will ask for more if they want it.',
70
86
  },
71
87
  detailed: {
72
88
  label: 'Detailed',
@@ -94,6 +110,9 @@ const FIELDS = {
94
110
  values: {
95
111
  show: {
96
112
  label: 'Show the work (default)',
113
+ // Deliberately the one empty default left (see the FIELDS preamble).
114
+ // `show` means "narrate as you otherwise would", which is the absence of
115
+ // an instruction — there is no posture here to spend context stating.
97
116
  contract: '',
98
117
  },
99
118
  quiet: {
@@ -135,9 +154,11 @@ function effectiveProfile(behaviorPrefs) {
135
154
  return out;
136
155
  }
137
156
 
138
- // The session-injected contract: the non-default fragments joined into one
139
- // paragraph. Empty string when the builder is on all defaults (the portable
140
- // baseline already covers that — nothing to inject).
157
+ // The session-injected contract: every effective field's fragment joined into
158
+ // one paragraph — defaults included, because the accessible default IS a posture
159
+ // (see the FIELDS preamble). Fields whose effective value carries no fragment
160
+ // contribute nothing, so the string stays as short as the profile is plain.
161
+ // Empty only if every effective field is fragment-less, which no live profile is.
141
162
  function contractFor(behaviorPrefs) {
142
163
  const eff = effectiveProfile(behaviorPrefs);
143
164
  const parts = [];
@@ -92,6 +92,17 @@
92
92
  .prow--pick[aria-disabled="true"] { cursor: default; }
93
93
  .prow--pick[aria-disabled="true"] .prow__label { color: var(--ink-soft); }
94
94
 
95
+ /* a pick GROUP: several independent single-selects in one panel (the interaction
96
+ profile, task 1002514). A fieldset so each group's legend is its radiogroup's
97
+ accessible name — the browser's own border/padding is stripped, because the
98
+ grouping here is carried by the legend and the rule above it, not by a box. */
99
+ .pgroup { border: 0; padding: 0; margin: 0; min-width: 0; }
100
+ .pgroup + .pgroup { margin-top: var(--sp-md); }
101
+ .pgroup > legend { padding: 0; margin-bottom: 2px; }
102
+ /* the group's blurb sits under the legend, above its rows — so the first row's
103
+ top rule reads as the start of the choices rather than a divider mid-sentence */
104
+ .pgroup > .prow__desc { margin: 0 0 var(--sp-hair); }
105
+
95
106
  /* the action row at a panel's foot: buttons and their status on one line */
96
107
  .row-end,
97
108
  .settings-card, .craft-actions, .artkey-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-xs); margin-top: var(--sp-sm); }
@@ -0,0 +1,127 @@
1
+ // modules/hall-ui/public/settings-interaction.js — the "How Claude talks to you"
2
+ // panel on /builders/settings (task 2010's profile, delivered by task 1002514).
3
+ //
4
+ // WHY THE PANEL EXISTS. The interaction profile shipped complete except for a
5
+ // way to set it: interaction-prefs.js stored it, GET/PATCH /me/interaction served
6
+ // it, and the conductor hook already injected the resolved contract into every
7
+ // session — but settings.js had 18 `wandering` references and 0 `interaction`
8
+ // ones, so the profile was reachable only by a raw API call. In practice that
9
+ // meant nobody had one.
10
+ //
11
+ // Its own file rather than more of settings.js, on the settings-sessions.js
12
+ // precedent and for the same stated reason: settings.js sits against the
13
+ // 1500-line size ratchet (adding this inline pushed it to 1601 and regressed the
14
+ // budget). The panel is self-contained — it shares nothing with the rest of the
15
+ // page but the `window.OTB` toolkit and the generated API client, both globals
16
+ // loaded before it.
17
+ //
18
+ // THE ONE SHAPE DIFFERENCE FROM WANDERING, which is the sibling to read first:
19
+ // wandering is a single level, so it is one radiogroup. This payload is a FIELD
20
+ // CATALOG — several independent fields, each with its own value set — so each
21
+ // field is its own single-select and a PATCH carries only the field that changed
22
+ // ({ <field>: <value> }). The route merges, so the untouched fields keep their
23
+ // values and a field never explicitly set keeps falling back to its default.
24
+
25
+ (() => {
26
+ 'use strict';
27
+
28
+ const API = '/api/bongos';
29
+ const { escapeHtml, $ } = window.OTB;
30
+ const toast = (msg) => window.OTB.toast(msg);
31
+ const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
32
+
33
+ const toAuth = () => window.location.assign(`${API}/auth/web/start`);
34
+
35
+ function render(data) {
36
+ const wrap = $('#interaction-rows');
37
+ if (!wrap) return;
38
+ const fields = Array.isArray(data && data.fields) ? data.fields : [];
39
+ const eff = (data && data.effective) || {};
40
+ if (fields.length === 0) {
41
+ wrap.innerHTML = window.OTBKit.emptyStateHtml('No interaction settings registered.');
42
+ return;
43
+ }
44
+ // A fieldset per field so the field's label is its radiogroup's accessible
45
+ // name — four independent single-selects, not one flat list of radios.
46
+ wrap.innerHTML = fields.map((f) => {
47
+ const chosenValue = eff[f.field];
48
+ const rows = (f.values || []).map((v) => {
49
+ const id = `interaction-${f.field}-${v.value}`;
50
+ const chosen = v.value === chosenValue;
51
+ // No "default" tag here, deliberately: interaction-prefs.js already ends
52
+ // every default value's LABEL with "(default)", so adding the row tag the
53
+ // wandering picker uses rendered "Non-technical (default) · DEFAULT".
54
+ // One source for the word, and it is the label the API already ships.
55
+ return (
56
+ `<label class="prow prow--pick" for="${id}">` +
57
+ `<input class="pradio" type="radio" name="interaction-${escapeHtml(f.field)}" id="${id}"` +
58
+ ` value="${escapeHtml(v.value)}" data-field="${escapeHtml(f.field)}"${chosen ? ' checked' : ''}>` +
59
+ '<span class="prow__main">' +
60
+ `<span class="prow__label">${escapeHtml(v.label)}</span>` +
61
+ '</span></label>'
62
+ );
63
+ }).join('');
64
+ return (
65
+ `<fieldset class="pgroup" id="interaction-field-${escapeHtml(f.field)}">` +
66
+ `<legend class="prow__label">${escapeHtml(f.label)}</legend>` +
67
+ `<p class="prow__desc">${escapeHtml(f.blurb || '')}</p>` +
68
+ rows +
69
+ '</fieldset>'
70
+ );
71
+ }).join('');
72
+ }
73
+
74
+ // The human label for a saved value, for the status line — falls back to the
75
+ // raw value rather than going blank if the catalog and the payload disagree.
76
+ function labelFor(data, field, value) {
77
+ const f = (Array.isArray(data && data.fields) ? data.fields : []).find((x) => x.field === field);
78
+ const v = f && (f.values || []).find((x) => x.value === value);
79
+ return (v && v.label) || value;
80
+ }
81
+
82
+ async function save(field, value, input) {
83
+ const status = $('#interaction-status');
84
+ if (status) status.textContent = 'Saving…';
85
+ if (input) input.disabled = true;
86
+ try {
87
+ const res = await api.request('PATCH', `${API}/me/interaction`, { body: { [field]: value } });
88
+ if (res.status === 401) { toAuth(); return; }
89
+ if (!res.ok) throw new Error(`interaction PATCH → ${res.status}`);
90
+ render(res.data);
91
+ const msg = `Saved — ${labelFor(res.data, field, value)}.`;
92
+ if (status) status.textContent = msg;
93
+ toast(msg);
94
+ } catch (err) {
95
+ console.error('[settings] save interaction failed', err);
96
+ if (status) status.textContent = 'Could not save. Try again.';
97
+ toast('Could not save your interaction profile. Try again.');
98
+ if (input) input.disabled = false;
99
+ }
100
+ }
101
+
102
+ async function setup() {
103
+ try {
104
+ const res = await api.request('GET', `${API}/me/interaction`);
105
+ if (res.status === 401) { toAuth(); return; }
106
+ if (!res.ok) throw new Error(`interaction GET → ${res.status}`);
107
+ render(res.data);
108
+ $('#interaction-scroll').hidden = false;
109
+ // Delegate the change so post-render radios stay wired.
110
+ $('#interaction-rows').addEventListener('change', (e) => {
111
+ const r = e.target.closest('.pradio');
112
+ if (!r || r.disabled || !r.checked) return;
113
+ const field = r.getAttribute('data-field');
114
+ if (field) save(field, r.value, r);
115
+ });
116
+ } catch (err) {
117
+ // Its own try/catch, like every other section: a failing endpoint shows this
118
+ // panel's own error rather than taking the settings page down with it.
119
+ console.error('[settings] failed to load interaction', err);
120
+ $('#interaction-scroll').hidden = false;
121
+ $('#interaction-rows').innerHTML =
122
+ '<p class="voices-empty">Your interaction profile could not be loaded. Reload the page to try again.</p>';
123
+ }
124
+ }
125
+
126
+ setup();
127
+ })();
@@ -251,6 +251,24 @@
251
251
 
252
252
  <!-- ============ group: preferences ============ -->
253
253
 
254
+ <!-- INTERACTION PROFILE — how replies are pitched for you (task 2010,
255
+ delivered in task 1002514). GET/PATCH /api/bongos/me/interaction.
256
+ Several independent fields, each its own single-select; the resolved
257
+ contract is injected into every session by the conductor hook. No
258
+ rank gate — this is ergonomics, not authority. -->
259
+ <section class="scroll" id="interaction-scroll" aria-labelledby="interaction-h" data-group="preferences" hidden>
260
+ <h2 id="interaction-h" class="scroll__h">How Claude talks to you</h2>
261
+ <p class="scroll__lede">
262
+ How replies are pitched for you — the vocabulary assumed, how much
263
+ hand-holding local tooling gets, how long an answer runs, and how much
264
+ of the work behind it is narrated. These reach every session you start.
265
+ The defaults assume no software-engineering background; change any of
266
+ them and the rest stay as they are.
267
+ </p>
268
+ <div class="prows" id="interaction-rows"></div>
269
+ <p class="settings-status" id="interaction-status" role="status" aria-live="polite"></p>
270
+ </section>
271
+
254
272
  <!-- WANDERING — how readily the agent opens off-task threads (task 1268).
255
273
  GET/PATCH /api/bongos/me/wandering. Rank sets the default + ceiling; the
256
274
  server clamps, so options above the rank cap render disabled. -->
@@ -373,5 +391,9 @@
373
391
  <!-- task 1003341: the Active sessions panel. Its own file rather than more of
374
392
  settings.js, which sits close to the 1500-line size ratchet. -->
375
393
  <script src="/builders/settings-sessions.js?v=2026-08-29-sessions"></script>
394
+ <!-- task 1002514: the interaction-profile panel ("How Claude talks to you"), on
395
+ the same precedent and for the same reason — settings.js is against the
396
+ 1500-line ratchet, and this panel shares nothing with the rest of the page. -->
397
+ <script src="/builders/settings-interaction.js?v=2026-09-15-interaction"></script>
376
398
  </body>
377
399
  </html>
@@ -1311,6 +1311,7 @@
1311
1311
  'dev-box': 'box-scroll',
1312
1312
  'dev-box-app': 'boxapp-scroll',
1313
1313
  'pairing': 'pairing-scroll',
1314
+ 'interaction': 'interaction-scroll',
1314
1315
  'wandering': 'wandering-scroll',
1315
1316
  'rendering': 'render-scroll',
1316
1317
  'voices': 'voices-scroll',
@@ -1453,6 +1454,8 @@
1453
1454
  await loadSounds();
1454
1455
  // Event sounds loads independently (per-event variant picker, task 582).
1455
1456
  await loadEventSounds();
1457
+ // (The interaction profile panel self-boots from settings-interaction.js —
1458
+ // its own script on the settings-sessions.js precedent, task 1002514.)
1456
1459
  // Wandering loads independently too (off-task thread suppression, task 1268).
1457
1460
  await loadWandering();
1458
1461
  // Rendering loads independently too (rich in-feed cards on/off, task 1279).