@bongos/core 1.24.18 → 1.24.20

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.
@@ -3292,5 +3292,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3292
3292
  landed since 1.24.16 with no explicit bump. run 41129472650. (task 1002620)
3293
3293
  1.24.18 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3294
3294
  landed since 1.24.17 with no explicit bump. run 41131020363. (task 1002620)
3295
+ 1.24.19 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3296
+ landed since 1.24.18 with no explicit bump. run 41137645458. (task 1002620)
3297
+ 1.24.20 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3298
+ landed since 1.24.19 with no explicit bump. run 41141610027. (task 1002620)
3295
3299
  ---------------------------------------------------------------------------
3296
3300
  ```
@@ -5,16 +5,16 @@
5
5
  "adr": "0341 D3",
6
6
  "reread": "node scripts/bongos/page-reader.js --stale",
7
7
  "how": ["Generated by node scripts/bongos/page-reader.js (BV2.TW04, task 1004315; ADR 0341 D3). Do not hand-edit.","One entry per page of docs/page-inventory.json. Lines are in page order: the default state first, and a line only another state shows sits where that state shows it, with its state named.","placement: placed = a docs/copy-registry.json row renders it (file, line, string_id); shared = shell text every page of the surface carries (top bar, sidebar, footer), with its row when the registry has one; unplaced = the registry cannot place it (a rewrite becomes an engineer task, ADR 0341 D4).","text keeps {…} where the registry row has a hole, or where a value the page read from its API was filled in. A string that is only such data is counted under omitted.data, not listed.","reading_hash is over the non-shared lines (key, section, text); files_hash is over the page's inventory files. --check compares files_hash only and never opens a browser.","Rendered through the hall-preview harness (hall) and the ui-design kit stub (landing, status), at 1280x800 with the clock pinned, so a reading is the fixture world's page."],
8
- "counts": {"pages":52,"lines":5612,"placed":1753,"shared":461,"unplaced":3398,"omitted_data":3766},
8
+ "counts": {"pages":52,"lines":5614,"placed":1753,"shared":461,"unplaced":3400,"omitted_data":3766},
9
9
  "pages": [
10
10
  {
11
11
  "id": "builders:ai-studio",
12
12
  "surface": "builders",
13
13
  "title": "Studio",
14
14
  "states_read": ["pick","describe","make","waiting","result","result-own","failed","balance","balance-all","out"],
15
- "files_hash": "48c0f2c2c104a63b",
16
- "reading_hash": "475acccd2681bd7f",
17
- "counts": {"lines":102,"placed":38,"shared":8,"unplaced":56},
15
+ "files_hash": "f90837ff3546b240",
16
+ "reading_hash": "495f0ef9f7d7a1b7",
17
+ "counts": {"lines":104,"placed":38,"shared":8,"unplaced":58},
18
18
  "omitted": {"data":15},
19
19
  "notes": [],
20
20
  "lines": [
@@ -28,13 +28,13 @@
28
28
  {"key":"L0008","section":"top bar","text":"Ideating","placement":"shared"},
29
29
  {"key":"L0009","section":"page top","text":"← Studio","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":122,"string_id":"a857d79bc0df"},
30
30
  {"key":"L0010","section":"{…} left","text":"left","placement":"placed","file":"modules/hall-ui/public/market-watch.js","line":73,"string_id":"92b79edf21f6","state":"balance"},
31
- {"key":"L0011","section":"{…} left","text":"Add how much?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":527,"string_id":"cb46cfb66dfd","state":"balance"},
31
+ {"key":"L0011","section":"{…} left","text":"Add how much?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":563,"string_id":"cb46cfb66dfd","state":"balance"},
32
32
  {"key":"L0012","section":"{…} left","text":"other","placement":"unplaced","state":"balance"},
33
- {"key":"L0013","section":"{…} left","text":"Add {…}","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":531,"string_id":"e4da002ae135","state":"balance"},
34
- {"key":"L0014","section":"{…} left","text":"{…} lands in your balance; your card is charged {…}.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":579,"string_id":"5ad81ff61920","state":"balance"},
33
+ {"key":"L0013","section":"{…} left","text":"Add {…}","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":567,"string_id":"e4da002ae135","state":"balance"},
34
+ {"key":"L0014","section":"{…} left","text":"{…} lands in your balance; your card is charged {…}.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":615,"string_id":"5ad81ff61920","state":"balance"},
35
35
  {"key":"L0015","section":"{…} left","text":"Test mode: no real money moves.","placement":"unplaced","state":"balance"},
36
36
  {"key":"L0016","section":"{…} left","text":"September","placement":"unplaced","state":"balance"},
37
- {"key":"L0017","section":"{…} left","text":"Nothing yet this month.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":537,"string_id":"8d0e0cbaa5ff","state":"balance"},
37
+ {"key":"L0017","section":"{…} left","text":"Nothing yet this month.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":573,"string_id":"8d0e0cbaa5ff","state":"balance"},
38
38
  {"key":"L0018","section":"{…} left","text":"Show this month only","placement":"unplaced","state":"balance-all"},
39
39
  {"key":"L0019","section":"{…} left","text":"Turn the sketch into a finished hero pi…","placement":"unplaced","state":"balance-all","occurrences":3},
40
40
  {"key":"L0020","section":"{…} left","text":"set aside while it ran","placement":"unplaced","state":"balance-all","occurrences":3},
@@ -51,75 +51,77 @@
51
51
  {"key":"L0031","section":"{…} left","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"balance"},
52
52
  {"key":"L0032","section":"page top","text":"No picture this time.","placement":"unplaced","state":"failed"},
53
53
  {"key":"L0033","section":"page top","text":"Save picture","placement":"unplaced","state":"result"},
54
- {"key":"L0034","section":"page top","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","state":"result","occurrences":4},
55
- {"key":"L0035","section":"page top","text":"lit it from the lamp","placement":"unplaced","state":"result"},
56
- {"key":"L0036","section":"page top","text":"If you want it closer to the landing page's salmon, ask for","placement":"unplaced","state":"result"},
57
- {"key":"L0037","section":"page top","text":"\"warmer lamp, pinker glow\"","placement":"unplaced","state":"result"},
58
- {"key":"L0038","section":"page top","text":"Your request","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":478,"string_id":"573a5581aa7a","state":"result"},
59
- {"key":"L0039","section":"page top","text":"Make it again","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":478,"string_id":"d535454fac6d","state":"result"},
60
- {"key":"L0040","section":"page top","text":"Change files","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":478,"string_id":"f5a83e058206","state":"result"},
61
- {"key":"L0041","section":"page top","text":"Start something new","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":478,"string_id":"45e2495c0dbd","state":"result"},
62
- {"key":"L0042","section":"page top","text":"Paid with","placement":"unplaced","state":"result"},
63
- {"key":"L0043","section":"page top","text":"your own Gemini key","placement":"unplaced","state":"result-own"},
64
- {"key":"L0044","section":"page top","text":"Cost","placement":"unplaced","state":"result-own"},
65
- {"key":"L0045","section":"page top","text":"not reported","placement":"unplaced","state":"result-own"},
66
- {"key":"L0046","section":"page top","text":"Google doesn't tell us the price on your own key, so check your Google bill. We charged nothing.","placement":"unplaced","state":"result-own"},
67
- {"key":"L0047","section":"page top","text":"your balance","placement":"unplaced","state":"result"},
68
- {"key":"L0048","section":"page top","text":"OpenRouter","placement":"unplaced","state":"result"},
69
- {"key":"L0049","section":"page top","text":"Our 8%","placement":"unplaced","state":"result"},
70
- {"key":"L0050","section":"page top","text":"You paid","placement":"unplaced","state":"result"},
71
- {"key":"L0051","section":"page top","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"result"},
72
- {"key":"L0052","section":"{…} so far","text":"usually done in about a minute","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":439,"string_id":"f26117caa2f1","state":"waiting"},
73
- {"key":"L0053","section":"{…} so far","text":"Read your 3 files","placement":"unplaced","state":"waiting"},
74
- {"key":"L0054","section":"{…} so far","text":"Wrote the answer","placement":"unplaced","state":"waiting"},
75
- {"key":"L0055","section":"{…} so far","text":"Drawing the pictures","placement":"unplaced","state":"waiting"},
76
- {"key":"L0056","section":"{…} so far","text":"The lamp stays on until it's done. You can close this page; the result waits in your studio.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":439,"string_id":"5a2220cd0d71","state":"waiting"},
77
- {"key":"L0057","section":"{…} so far","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"waiting"},
78
- {"key":"L0058","section":"page top","text":"1 Pick ✓","placement":"unplaced","state":"describe"},
79
- {"key":"L0059","section":"page top","text":"2 Describe ✓","placement":"unplaced","state":"make"},
80
- {"key":"L0060","section":"page top","text":"1 Pick","placement":"unplaced"},
81
- {"key":"L0061","section":"page top","text":"2 Describe","placement":"unplaced"},
82
- {"key":"L0062","section":"page top","text":"3 Make","placement":"unplaced"},
83
- {"key":"L0063","section":"How do you want to pay?","text":"How do you want to pay?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":351,"string_id":"5c442fe0f174","state":"make"},
84
- {"key":"L0064","section":"How do you want to pay?","text":"PDF","placement":"unplaced","state":"make"},
85
- {"key":"L0065","section":"How do you want to pay?","text":"“Turn the sketch into a finished hero picture for the landing pa…”","placement":"unplaced","state":"make"},
86
- {"key":"L0066","section":"How do you want to pay?","text":"change","placement":"unplaced","state":"make"},
87
- {"key":"L0067","section":"How do you want to pay?","text":"My own key","placement":"unplaced","state":"make"},
88
- {"key":"L0068","section":"How do you want to pay?","text":"Free here. {…} bills you for it directly.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":336,"string_id":"e9a1af5dc515","state":"make"},
89
- {"key":"L0069","section":"How do you want to pay?","text":"Gemini key ending {…}","placement":"unplaced","state":"make"},
90
- {"key":"L0070","section":"How do you want to pay?","text":"My balance","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":340,"string_id":"39afdc03594a","state":"make"},
91
- {"key":"L0071","section":"How do you want to pay?","text":"Usually a few cents. Up to {…} is set aside, and the rest comes back.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":340,"string_id":"664555b65658","state":"make"},
92
- {"key":"L0072","section":"How do you want to pay?","text":"{…} left","placement":"placed","file":"modules/hall-ui/public/market-watch.js","line":73,"string_id":"92b79edf21f6","state":"make"},
93
- {"key":"L0073","section":"How do you want to pay?","text":"You {…}.","placement":"placed","file":"modules/hall-ui/public/board-room.js","line":471,"string_id":"819dfb951611","state":"make"},
94
- {"key":"L0074","section":"How do you want to pay?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"make"},
95
- {"key":"L0075","section":"What should it make?","text":"What should it make?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"dc5f6264af0f","state":"describe"},
96
- {"key":"L0076","section":"What should it make?","text":"Say what you want, in your own words.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"53b9f8b2c279","attr":"placeholder","state":"describe"},
97
- {"key":"L0077","section":"What should it make?","text":"Your project's look and words are added for you, so you don't need to describe them.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"e8afba68c0ee","state":"describe"},
98
- {"key":"L0078","section":"What should it make?","text":"Next","placement":"unplaced","state":"describe"},
99
- {"key":"L0079","section":"What should it make?","text":"Back","placement":"unplaced","state":"describe"},
100
- {"key":"L0080","section":"What should it make?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"describe"},
101
- {"key":"L0081","section":"What are you starting from?","text":"What are you starting from?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"b5426a29991c"},
102
- {"key":"L0082","section":"What are you starting from?","text":"Drop sketches, pictures or a PDF here, paste a picture,","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"39f897eaff3c"},
103
- {"key":"L0083","section":"What are you starting from?","text":"choose files","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"ad625012b13c"},
104
- {"key":"L0084","section":"What are you starting from?","text":"or","placement":"unplaced"},
105
- {"key":"L0085","section":"What are you starting from?","text":"take a photo","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"e25936f449c7"},
106
- {"key":"L0086","section":"What are you starting from?","text":"Up to {…}. Pictures up to 4 MB, PDFs up to 8 MB.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"56f9e72dc635"},
107
- {"key":"L0087","section":"What are you starting from?","text":"1.2 MB","placement":"unplaced"},
108
- {"key":"L0088","section":"What are you starting from?","text":"remove","placement":"unplaced","occurrences":3},
109
- {"key":"L0089","section":"What are you starting from?","text":"840 KB","placement":"unplaced"},
110
- {"key":"L0090","section":"What are you starting from?","text":"PDF","placement":"unplaced"},
111
- {"key":"L0091","section":"What are you starting from?","text":"3.1 MB","placement":"unplaced"},
112
- {"key":"L0092","section":"What are you starting from?","text":"Next","placement":"unplaced"},
113
- {"key":"L0093","section":"What are you starting from?","text":"3 files ready","placement":"unplaced"},
114
- {"key":"L0094","section":"What are you starting from?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e"},
115
- {"key":"L0095","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":671,"string_id":"60853319f290"},
116
- {"key":"L0096","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":672,"string_id":"4f87d5b17b54"},
117
- {"key":"L0097","section":"What are you doing now?","text":"Building","placement":"unplaced"},
118
- {"key":"L0098","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
119
- {"key":"L0099","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
120
- {"key":"L0100","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
121
- {"key":"L0101","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
122
- {"key":"L0102","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":675,"string_id":"fa86bf403775"}
54
+ {"key":"L0034","section":"page top","text":"This cost about $0.0640 on your Gemini key.","placement":"unplaced","state":"result-own"},
55
+ {"key":"L0035","section":"page top","text":"This cost $0.0421, taken from your balance.","placement":"unplaced","state":"result"},
56
+ {"key":"L0036","section":"page top","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","state":"result","occurrences":4},
57
+ {"key":"L0037","section":"page top","text":"lit it from the lamp","placement":"unplaced","state":"result"},
58
+ {"key":"L0038","section":"page top","text":"If you want it closer to the landing page's salmon, ask for","placement":"unplaced","state":"result"},
59
+ {"key":"L0039","section":"page top","text":"\"warmer lamp, pinker glow\"","placement":"unplaced","state":"result"},
60
+ {"key":"L0040","section":"page top","text":"Your request","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":514,"string_id":"573a5581aa7a","state":"result"},
61
+ {"key":"L0041","section":"page top","text":"Make it again","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":514,"string_id":"d535454fac6d","state":"result"},
62
+ {"key":"L0042","section":"page top","text":"Change files","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":514,"string_id":"f5a83e058206","state":"result"},
63
+ {"key":"L0043","section":"page top","text":"Start something new","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":514,"string_id":"45e2495c0dbd","state":"result"},
64
+ {"key":"L0044","section":"page top","text":"Paid with","placement":"unplaced","state":"result"},
65
+ {"key":"L0045","section":"page top","text":"your own Gemini key","placement":"unplaced","state":"result-own"},
66
+ {"key":"L0046","section":"page top","text":"Cost","placement":"unplaced","state":"result-own"},
67
+ {"key":"L0047","section":"page top","text":"about $0.0640","placement":"unplaced","state":"result-own"},
68
+ {"key":"L0048","section":"page top","text":"Google doesn't tell us the exact price on your own key, so this is an estimate. Your Google bill has the real figure. We charged nothing.","placement":"unplaced","state":"result-own"},
69
+ {"key":"L0049","section":"page top","text":"your balance","placement":"unplaced","state":"result"},
70
+ {"key":"L0050","section":"page top","text":"OpenRouter","placement":"unplaced","state":"result"},
71
+ {"key":"L0051","section":"page top","text":"Our 8%","placement":"unplaced","state":"result"},
72
+ {"key":"L0052","section":"page top","text":"You paid","placement":"unplaced","state":"result"},
73
+ {"key":"L0053","section":"page top","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"result"},
74
+ {"key":"L0054","section":"{…} so far","text":"usually done in about a minute","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":473,"string_id":"f26117caa2f1","state":"waiting"},
75
+ {"key":"L0055","section":"{…} so far","text":"Read your 3 files","placement":"unplaced","state":"waiting"},
76
+ {"key":"L0056","section":"{…} so far","text":"Wrote the answer","placement":"unplaced","state":"waiting"},
77
+ {"key":"L0057","section":"{…} so far","text":"Drawing the pictures","placement":"unplaced","state":"waiting"},
78
+ {"key":"L0058","section":"{…} so far","text":"The lamp stays on until it's done. You can close this page; the result waits in your studio.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":473,"string_id":"5a2220cd0d71","state":"waiting"},
79
+ {"key":"L0059","section":"{…} so far","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"waiting"},
80
+ {"key":"L0060","section":"page top","text":"1 Pick ✓","placement":"unplaced","state":"describe"},
81
+ {"key":"L0061","section":"page top","text":"2 Describe ✓","placement":"unplaced","state":"make"},
82
+ {"key":"L0062","section":"page top","text":"1 Pick","placement":"unplaced"},
83
+ {"key":"L0063","section":"page top","text":"2 Describe","placement":"unplaced"},
84
+ {"key":"L0064","section":"page top","text":"3 Make","placement":"unplaced"},
85
+ {"key":"L0065","section":"How do you want to pay?","text":"How do you want to pay?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":351,"string_id":"5c442fe0f174","state":"make"},
86
+ {"key":"L0066","section":"How do you want to pay?","text":"PDF","placement":"unplaced","state":"make"},
87
+ {"key":"L0067","section":"How do you want to pay?","text":"“Turn the sketch into a finished hero picture for the landing pa…”","placement":"unplaced","state":"make"},
88
+ {"key":"L0068","section":"How do you want to pay?","text":"change","placement":"unplaced","state":"make"},
89
+ {"key":"L0069","section":"How do you want to pay?","text":"My own key","placement":"unplaced","state":"make"},
90
+ {"key":"L0070","section":"How do you want to pay?","text":"Free here. {…} bills you for it directly.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":336,"string_id":"e9a1af5dc515","state":"make"},
91
+ {"key":"L0071","section":"How do you want to pay?","text":"Gemini key ending {…}","placement":"unplaced","state":"make"},
92
+ {"key":"L0072","section":"How do you want to pay?","text":"My balance","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":340,"string_id":"39afdc03594a","state":"make"},
93
+ {"key":"L0073","section":"How do you want to pay?","text":"Usually a few cents. Up to {…} is set aside, and the rest comes back.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":340,"string_id":"664555b65658","state":"make"},
94
+ {"key":"L0074","section":"How do you want to pay?","text":"{…} left","placement":"placed","file":"modules/hall-ui/public/market-watch.js","line":73,"string_id":"92b79edf21f6","state":"make"},
95
+ {"key":"L0075","section":"How do you want to pay?","text":"You {…}.","placement":"placed","file":"modules/hall-ui/public/board-room.js","line":471,"string_id":"819dfb951611","state":"make"},
96
+ {"key":"L0076","section":"How do you want to pay?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"make"},
97
+ {"key":"L0077","section":"What should it make?","text":"What should it make?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"dc5f6264af0f","state":"describe"},
98
+ {"key":"L0078","section":"What should it make?","text":"Say what you want, in your own words.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"53b9f8b2c279","attr":"placeholder","state":"describe"},
99
+ {"key":"L0079","section":"What should it make?","text":"Your project's look and words are added for you, so you don't need to describe them.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":301,"string_id":"e8afba68c0ee","state":"describe"},
100
+ {"key":"L0080","section":"What should it make?","text":"Next","placement":"unplaced","state":"describe"},
101
+ {"key":"L0081","section":"What should it make?","text":"Back","placement":"unplaced","state":"describe"},
102
+ {"key":"L0082","section":"What should it make?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e","state":"describe"},
103
+ {"key":"L0083","section":"What are you starting from?","text":"What are you starting from?","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"b5426a29991c"},
104
+ {"key":"L0084","section":"What are you starting from?","text":"Drop sketches, pictures or a PDF here, paste a picture,","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"39f897eaff3c"},
105
+ {"key":"L0085","section":"What are you starting from?","text":"choose files","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"ad625012b13c"},
106
+ {"key":"L0086","section":"What are you starting from?","text":"or","placement":"unplaced"},
107
+ {"key":"L0087","section":"What are you starting from?","text":"take a photo","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"e25936f449c7"},
108
+ {"key":"L0088","section":"What are you starting from?","text":"Up to {…}. Pictures up to 4 MB, PDFs up to 8 MB.","placement":"placed","file":"modules/hall-ui/public/ai-studio.js","line":180,"string_id":"56f9e72dc635"},
109
+ {"key":"L0089","section":"What are you starting from?","text":"1.2 MB","placement":"unplaced"},
110
+ {"key":"L0090","section":"What are you starting from?","text":"remove","placement":"unplaced","occurrences":3},
111
+ {"key":"L0091","section":"What are you starting from?","text":"840 KB","placement":"unplaced"},
112
+ {"key":"L0092","section":"What are you starting from?","text":"PDF","placement":"unplaced"},
113
+ {"key":"L0093","section":"What are you starting from?","text":"3.1 MB","placement":"unplaced"},
114
+ {"key":"L0094","section":"What are you starting from?","text":"Next","placement":"unplaced"},
115
+ {"key":"L0095","section":"What are you starting from?","text":"3 files ready","placement":"unplaced"},
116
+ {"key":"L0096","section":"What are you starting from?","text":"Builders hall · this session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":670,"string_id":"fd1903586e8e"},
117
+ {"key":"L0097","section":"What are you doing now?","text":"What are you doing now?","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":671,"string_id":"60853319f290"},
118
+ {"key":"L0098","section":"What are you doing now?","text":"Pick one. You can switch any time from the top bar.","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":672,"string_id":"4f87d5b17b54"},
119
+ {"key":"L0099","section":"What are you doing now?","text":"Building","placement":"unplaced"},
120
+ {"key":"L0100","section":"What are you doing now?","text":"{…} and {…}","placement":"placed","file":"modules/hall-ui/public/profile-nudge.js","line":77,"string_id":"56b00d773232","occurrences":3},
121
+ {"key":"L0101","section":"What are you doing now?","text":"Artistry","placement":"unplaced"},
122
+ {"key":"L0102","section":"What are you doing now?","text":"Ideating","placement":"unplaced"},
123
+ {"key":"L0103","section":"What are you doing now?","text":"Turn thoughts into ideas.","placement":"unplaced"},
124
+ {"key":"L0104","section":"What are you doing now?","text":"Ask me again next session","placement":"placed","file":"modules/hall-ui/public/craft-mode.js","line":675,"string_id":"fa86bf403775"}
123
125
  ]
124
126
  },
125
127
  {
@@ -2941,7 +2943,7 @@
2941
2943
  "surface": "builders",
2942
2944
  "title": "Builders",
2943
2945
  "states_read": ["roster","artists","search","out"],
2944
- "files_hash": "700cde231d6f30bf",
2946
+ "files_hash": "6b822ffa6ef8523d",
2945
2947
  "reading_hash": "a61b867b5e26fa40",
2946
2948
  "counts": {"lines":45,"placed":12,"shared":8,"unplaced":25},
2947
2949
  "omitted": {"data":60},
@@ -4216,7 +4218,7 @@
4216
4218
  "surface": "builders",
4217
4219
  "title": "Studio",
4218
4220
  "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
4219
- "files_hash": "6c591072e20de9f0",
4221
+ "files_hash": "a07e1e7a763b27b0",
4220
4222
  "reading_hash": "bcba11e6cef56218",
4221
4223
  "counts": {"lines":31,"placed":16,"shared":6,"unplaced":9},
4222
4224
  "omitted": {"data":4},
@@ -100,7 +100,7 @@ function buildContext({ branding, projectDoc, designDoc }) {
100
100
  const palette = branding && branding.theme && branding.theme.colors ? JSON.stringify(branding.theme.colors).slice(0, 800) : '';
101
101
  const parts = [
102
102
  `You are the art director's assistant for ${id.productName || 'this project'}${id.worldName && id.worldName !== id.productName ? `, set in the world of ${id.worldName}` : ''}.`,
103
- 'An artist on the project has sent a sketch or reference images with a request. Answer as a thoughtful collaborator: say what you see, what works, and how to take it further, in plain words.',
103
+ 'An artist on the project has sent a sketch or reference images with a request. Answer as a thoughtful collaborator: say what you see, what works, and how to take it further, in plain words. Keep it short and friendly for a visual artist: a few short paragraphs or a brief list, no code, no markup symbols, and no technical file formats unless they asked for one.',
104
104
  'Keep everything you suggest consistent with the project as described below.',
105
105
  ];
106
106
  if (palette) parts.push(`## Brand colours\n${palette}`);
@@ -98,6 +98,48 @@
98
98
  }));
99
99
  }
100
100
 
101
+ // What a request will probably cost, from this builder's own finished ones
102
+ // paid the same way: the median of what we billed (ours) or of the cost the
103
+ // provider reported (their own key). max is the most one can ever cost on
104
+ // our key (max_cost_micro).
105
+ // typical is null when there is no history yet: the page then says so
106
+ // instead of inventing a number.
107
+ function estimate(requests, maxMicro, ours) {
108
+ const costs = (requests || [])
109
+ .filter((r) => r && r.status === 'done' && (r.key_source === 'ours') === !!ours)
110
+ .map((r) => (ours ? r.price_micro : r.provider_cost_micro))
111
+ .filter((n) => isMicro(n) && n > 0)
112
+ .sort((a, b) => a - b);
113
+ const max = isMicro(maxMicro) ? maxMicro : null;
114
+ if (!costs.length) return { typical: null, max, basis: 0 };
115
+ const mid = Math.floor(costs.length / 2);
116
+ const median = costs.length % 2 ? costs[mid] : Math.round((costs[mid - 1] + costs[mid]) / 2);
117
+ return { typical: max === null ? median : Math.min(median, max), max, basis: costs.length };
118
+ }
119
+
120
+ // Google reports no price on a builder's own Gemini key, so the page says
121
+ // "about" and works it out from Google's published rates: a written reply
122
+ // (gemini-2.5-pro, a few thousand tokens in and about a thousand out) and
123
+ // each picture (gemini-2.5-flash-image, about $0.039 each). The two numbers
124
+ // are named here and spelled nowhere else; update them if Google's change.
125
+ const GEMINI_REPLY_MICRO = 25000;
126
+ const GEMINI_IMAGE_MICRO = 39000;
127
+ function geminiEstimate(imageCount) {
128
+ const n = Math.max(1, Math.floor(Number(imageCount) || 0));
129
+ return GEMINI_REPLY_MICRO + n * GEMINI_IMAGE_MICRO;
130
+ }
131
+ const outputCount = (r) => ((r && r.files) || []).filter((f) => f.role === 'output').length;
132
+
133
+ // The headline cost on the result: one sentence an artist can read at a glance.
134
+ function costHeadline(req) {
135
+ const r = req || {};
136
+ if (r.status !== 'done') return '';
137
+ if (r.key_source === 'ours' && isMicro(r.price_micro)) return `This cost ${usd(r.price_micro)}, taken from your balance.`;
138
+ if (r.key_source === 'own' && isMicro(r.provider_cost_micro)) return `This cost ${usd(r.provider_cost_micro)}, billed to your own key.`;
139
+ if (r.key_source === 'own') return `This cost about ${usd(geminiEstimate(outputCount(r)))} on your Gemini key.`;
140
+ return '';
141
+ }
142
+
101
143
  // The prompt as one quoted line for step 3's summary row.
102
144
  function quoteLine(prompt, max) {
103
145
  const t = String(prompt || '').replace(/\s+/g, ' ').trim();
@@ -189,8 +231,8 @@
189
231
  }
190
232
  if (r.key_source === 'own') {
191
233
  return {
192
- rows: [['Paid with', 'your own Gemini key'], ['Cost', 'not reported']],
193
- note: "Google doesn't tell us the price on your own key, so check your Google bill. We charged nothing.",
234
+ rows: [['Paid with', 'your own Gemini key'], ['Cost', `about ${usd(geminiEstimate(outputCount(r)))}`]],
235
+ note: "Google doesn't tell us the exact price on your own key, so this is an estimate. Your Google bill has the real figure. We charged nothing.",
194
236
  };
195
237
  }
196
238
  return { rows: [], note: '' };
@@ -238,15 +280,38 @@
238
280
  }
239
281
  }
240
282
 
241
- // The model's reply as paragraphs, **bold** kept, everything else escaped by
242
- // the hall's ONE escaper, handed in (window.OTB.escapeHtml in the page).
283
+ // The model's reply read the way an artist wants to see it: headings as
284
+ // headings, bullets as a list, **bold** and `names` kept, divider dashes
285
+ // dropped, and any code listing folded away behind one line instead of
286
+ // pouring down the page. Every character of text goes through the hall's ONE
287
+ // escaper, handed in (window.OTB.escapeHtml in the page), before any tag is
288
+ // added.
243
289
  function replyHtml(md, escape) {
244
290
  const text = String(md || '').replace(/\r\n/g, '\n').trim();
245
291
  if (!text || typeof escape !== 'function') return '';
246
- return text.split(/\n{2,}/).map((para) => {
247
- const safe = escape(para.trim()).replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br>');
248
- return `<p>${safe}</p>`;
249
- }).join('');
292
+ const inline = (s) => escape(s).replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>').replace(/`([^`\n]+)`/g, '<code>$1</code>');
293
+ const out = [];
294
+ let para = [];
295
+ let list = [];
296
+ let code = null;
297
+ const flushPara = () => { if (para.length) out.push(`<p>${para.map(inline).join('<br>')}</p>`); para = []; };
298
+ const flushList = () => { if (list.length) out.push(`<ul>${list.map((t) => `<li>${inline(t)}</li>`).join('')}</ul>`); list = []; };
299
+ for (const raw of text.split('\n')) {
300
+ const line = raw.trimEnd();
301
+ if (/^\s*```/.test(line)) {
302
+ if (code) { out.push(`<details class="ais-code"><summary>Code from the answer</summary><pre>${escape(code.join('\n'))}</pre></details>`); code = null; } else { flushPara(); flushList(); code = []; }
303
+ continue;
304
+ }
305
+ if (code) { code.push(raw); continue; }
306
+ const head = /^#{1,6}\s+(.+)$/.exec(line);
307
+ const item = /^\s*[*•-]\s+(.+)$/.exec(line);
308
+ if (!line.trim()) { flushPara(); flushList(); } else if (/^\s*-{2,}\s*$/.test(line)) { flushPara(); flushList(); } else if (head) {
309
+ flushPara(); flushList(); out.push(`<h3 class="ais-rh">${inline(head[1].replace(/\*\*/g, ''))}</h3>`);
310
+ } else if (item) { flushPara(); list.push(item[1]); } else { flushList(); para.push(line.trim()); }
311
+ }
312
+ if (code) out.push(`<details class="ais-code"><summary>Code from the answer</summary><pre>${escape(code.join('\n'))}</pre></details>`);
313
+ flushPara(); flushList();
314
+ return out.join('');
250
315
  }
251
316
 
252
317
  // The home's AI action line: the newest request, while it is being made or
@@ -266,7 +331,7 @@
266
331
 
267
332
  const lib = {
268
333
  MAX_FILES, MAX_IMAGE_BYTES, MAX_PDF_BYTES, IMAGE_TYPES, PDF_TYPE, STEPS, TOPUP_CHOICES, ANSWER_AFTER_SEC,
269
- usd, balanceUsd, sizeText, routeOf, checkFile, isImage, stepMarks, quoteLine, ownKey, ownKeys, payChoices,
334
+ usd, balanceUsd, sizeText, routeOf, checkFile, isImage, stepMarks, quoteLine, ownKey, ownKeys, payChoices, estimate, geminiEstimate, costHeadline,
270
335
  waitSteps, clock, isFinished, receipt, againLine, monthSummary, ledgerLine, replyHtml, homeLine,
271
336
  };
272
337
  if (typeof module !== 'undefined' && module.exports) module.exports = lib;
@@ -138,12 +138,24 @@
138
138
  .ais-pick img { display: block; width: 100%; height: 100%; object-fit: cover; }
139
139
  .ais-pick--on { outline: 2px solid var(--st-accent); outline-offset: 0; }
140
140
  .ais-reply p { margin: 0 0 12px; font-size: 15px; line-height: 1.6; color: var(--st-ink-sub); font-weight: 300; }
141
+ .ais-rh { margin: 18px 0 6px; font-size: 16px; font-weight: 500; color: var(--st-ink); }
142
+ .ais-reply ul { margin: 0 0 12px; padding-left: 20px; font-size: 15px; line-height: 1.6; color: var(--st-ink-sub); font-weight: 300; }
143
+ .ais-reply code { font-size: 13.5px; }
144
+ .ais-code { margin: 0 0 12px; font-size: 13.5px; color: var(--st-ink-faint); }
145
+ .ais-code summary { cursor: pointer; min-height: 24px; }
146
+ .ais-code pre { margin: 8px 0 0; padding: 12px; overflow-x: auto; border-radius: 12px; background: var(--st-glass); white-space: pre; }
141
147
  .ais-reply b { color: var(--st-ink); font-weight: 500; }
142
148
  .ais-again { display: flex; flex-direction: column; gap: 8px; }
143
149
  .ais-receipt, .ais-topup {
144
150
  display: flex; flex-direction: column; padding: 10px 20px; border-radius: 22px;
145
151
  background: var(--st-glass); border: 1px solid var(--st-glass-edge);
146
152
  }
153
+ .ais-confirm {
154
+ display: flex; flex-direction: column; gap: 8px; padding: 16px 20px; border-radius: 22px;
155
+ background: var(--st-glass); border: 1px solid var(--st-glass-edge);
156
+ }
157
+ .ais-confirm__head { margin: 0; font-size: 20px; font-weight: 500; color: var(--st-ink); }
158
+ .ais-cost { margin: 0 0 12px; font-size: 17px; font-weight: 500; color: var(--st-ink); }
147
159
  .ais-line { display: flex; align-items: center; gap: 14px; min-height: 40px; border-top: 1px solid var(--st-track); font-size: 14px; color: var(--st-ink); }
148
160
  .ais-line:first-child, .ais-head + .ais-line { border-top: 0; }
149
161
  .ais-line a { color: var(--st-link); display: inline-flex; align-items: center; min-height: 24px; }
@@ -352,13 +352,47 @@
352
352
  <h1 class="ais-ask" data-head tabindex="-1">How do you want to pay?</h1>
353
353
  <div class="ais-row ais-row--summary">${thumbs}<span class="ais-grow ais-soft">${esc(lib.quoteLine(state.draft.prompt))}</span><a class="ais-link" href="/builders/ai-studio?step=describe" data-nav="step=describe">change</a></div>
354
354
  ${ownCard || balanceCard ? `<div class="ais-cards">${ownCard}${balanceCard}</div>` : ''}
355
+ <div class="ais-confirm" id="ais-confirm" role="group" aria-label="Confirm the cost" hidden></div>
355
356
  <p class="ais-faint">${esc(foot)}</p>
356
357
  ${extra}
357
358
  <p class="ais-err" id="ais-err" role="alert" hidden></p>`;
358
- page.querySelectorAll('[data-pay]').forEach((b) => b.addEventListener('click', () => sendRequest(b.getAttribute('data-pay'), state.draft.prompt, readyFiles(), b)));
359
+ page.querySelectorAll('[data-pay]').forEach((b) => b.addEventListener('click', () => askToConfirm(b.getAttribute('data-pay'), c)));
359
360
  wireNavLinks();
360
361
  }
361
362
 
363
+ // Before anything is sent or set aside: say what it will probably cost and
364
+ // wait for a yes. Past runs set the guess; the ceiling is what a run can
365
+ // never exceed.
366
+ async function askToConfirm(payWith, choices) {
367
+ if (state.busy) return;
368
+ const own = payWith !== 'ours';
369
+ if (payWith !== 'gemini' && !state.history) {
370
+ const res = await api.request('GET', `${API}/ai-studio/requests`);
371
+ state.history = res.ok && res.data ? res.data.requests : [];
372
+ }
373
+ const est = lib.estimate(state.history || [], state.balance && state.balance.max_cost_micro, !own);
374
+ const typical = payWith === 'gemini' ? lib.geminiEstimate(1) : est.typical;
375
+ const guess = typical === null ? 'A few cents' : `About ${lib.usd(typical)}`;
376
+ const ceiling = own || est.max === null ? '' : `It will never cost more than ${lib.usd(est.max)}.`;
377
+ const who = own ? 'Your own key pays, so nothing comes out of your balance here.' : `Taken from your balance (${choices.left}), and anything unused comes back.`;
378
+ const basis = payWith === 'gemini' ? 'Google reports no price on your own key, so this is worked out from its published rates: one reply and one picture.' : est.basis ? `Based on your last ${est.basis === 1 ? 'request' : `${est.basis} requests`}.` : 'You have no finished requests yet, so this is a rough guess.';
379
+ const box = page.querySelector('#ais-confirm');
380
+ if (!box) return;
381
+ box.hidden = false;
382
+ box.innerHTML = `
383
+ <h2 class="ais-confirm__head" data-confirm-head tabindex="-1">${esc(guess)} to make this</h2>
384
+ <p class="ais-soft">${esc([ceiling, who].filter(Boolean).join(' '))}</p>
385
+ <p class="ais-faint">${esc(basis)}</p>
386
+ <div class="ais-go ais-go--wrap">
387
+ <button type="button" class="ais-btn" id="ais-confirm-go">Make it</button>
388
+ <button type="button" class="ais-ghost" id="ais-confirm-cancel">Not yet</button>
389
+ </div>`;
390
+ const head = box.querySelector('[data-confirm-head]');
391
+ if (head) head.focus();
392
+ box.querySelector('#ais-confirm-cancel').addEventListener('click', () => { box.hidden = true; box.innerHTML = ''; });
393
+ box.querySelector('#ais-confirm-go').addEventListener('click', (ev) => sendRequest(payWith, state.draft.prompt, readyFiles(), ev.currentTarget));
394
+ }
395
+
362
396
  // Same-page links move through history, so the steps keep the back button.
363
397
  function wireNavLinks() {
364
398
  page.querySelectorAll('[data-nav]').forEach((a) => a.addEventListener('click', (ev) => {
@@ -465,6 +499,7 @@
465
499
  const rc = lib.receipt(req, state.balance && state.balance.price_multiplier);
466
500
  const receiptHtml = rc.rows.length ? `<div class="ais-receipt">${rc.rows.map(([k, v]) => `<div class="ais-line"><span class="ais-grow">${esc(k)}</span><span>${esc(v)}</span></div>`).join('')}${rc.note ? `<div class="ais-line"><span class="ais-grow ais-faint ais-wrap">${esc(rc.note)}</span></div>` : ''}</div>` : '';
467
501
  const failed = req.status !== 'done';
502
+ const headline = lib.costHeadline(req);
468
503
  main.setAttribute('data-view', 'result');
469
504
  page.innerHTML = `
470
505
  <h1 class="ais-sr" data-head tabindex="-1">${failed ? 'That did not work' : 'Your result'}</h1>
@@ -476,6 +511,7 @@
476
511
  </div>
477
512
  <div class="ais-result__words">
478
513
  ${failed ? `<p class="ais-err">${esc(req.error || 'It did not work, and nothing was charged.')}</p>` : ''}
514
+ ${headline ? `<p class="ais-cost" data-cost>${esc(headline)}</p>` : ''}
479
515
  <div class="ais-reply">${lib.replyHtml(req.reply_md, esc)}</div>
480
516
  <div class="ais-again">
481
517
  <label class="ais-head" for="ais-again">Your request</label>
@@ -52,3 +52,46 @@
52
52
  font-size: 11.5px;
53
53
  color: var(--ink-faint);
54
54
  }
55
+
56
+ /* ---- the circuit divider (task 1005032, the owner's Studio idea 1001297; mock:
57
+ docs/design/mocks/builders-leaderboard/) -----------------------------------
58
+ A trace between the column headings and the first row: a fixed cap at each
59
+ end, a hairline stretched between them, and orange nodes on the traces. It is
60
+ drawn on the FIRST BODY ROW (its top 30px, which the row's own top padding
61
+ makes room for) rather than under the sticky heading, so it scrolls with the
62
+ rows and never strands a blank band under a stuck header. Two masks, two
63
+ tokens: the traces are --ink-faint, the nodes --accent, so both modes follow
64
+ the world. The empty-state row has no trace. Pure decoration, no markup. */
65
+ #roster-body .ledger th { border-bottom-color: transparent; }
66
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) { position: relative; }
67
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) td { padding-top: 44px; }
68
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) td:first-child::before,
69
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) td:first-child::after {
70
+ content: ""; position: absolute; left: 0; right: 0; top: 0; height: 30px;
71
+ pointer-events: none;
72
+ -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
73
+ }
74
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) td:first-child::before {
75
+ background: var(--ink-faint);
76
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg fill='none' stroke='black' stroke-width='1.5'%3E%3Cpath d='M6 18H88V6H100V14H156V18H300'/%3E%3Cpath stroke-opacity='.55' stroke-width='1.2' d='M12 22H80M104 12H124M132 14H152M160 16H180M92 10V24'/%3E%3C/g%3E%3C/svg%3E"),
77
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg transform='matrix(-1 0 0 1 300 0)' fill='none' stroke='black' stroke-width='1.5'%3E%3Cpath d='M6 18H88V22H168V28H196V18H300'/%3E%3Cpath stroke-opacity='.55' stroke-width='1.2' d='M12 14H70M110 10H128M136 16H160M170 4V14M200 8V24'/%3E%3C/g%3E%3C/svg%3E"),
78
+ linear-gradient(black, black);
79
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg fill='none' stroke='black' stroke-width='1.5'%3E%3Cpath d='M6 18H88V6H100V14H156V18H300'/%3E%3Cpath stroke-opacity='.55' stroke-width='1.2' d='M12 22H80M104 12H124M132 14H152M160 16H180M92 10V24'/%3E%3C/g%3E%3C/svg%3E"),
80
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg transform='matrix(-1 0 0 1 300 0)' fill='none' stroke='black' stroke-width='1.5'%3E%3Cpath d='M6 18H88V22H168V28H196V18H300'/%3E%3Cpath stroke-opacity='.55' stroke-width='1.2' d='M12 14H70M110 10H128M136 16H160M170 4V14M200 8V24'/%3E%3C/g%3E%3C/svg%3E"),
81
+ linear-gradient(black, black);
82
+ -webkit-mask-size: 300px 30px, 300px 30px, calc(100% - 598px) 1.5px;
83
+ mask-size: 300px 30px, 300px 30px, calc(100% - 598px) 1.5px;
84
+ -webkit-mask-position: left top, right top, 299px 17.25px;
85
+ mask-position: left top, right top, 299px 17.25px;
86
+ }
87
+ #roster-body .ledger tbody tr:first-child:not(:has(.ledger__empty)) td:first-child::after {
88
+ background: var(--accent);
89
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg fill='black'%3E%3Ccircle cx='6' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='88' cy='6' r='7' fill-opacity='.25'/%3E%3Ccircle cx='296' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='6' cy='18' r='3.5'/%3E%3Ccircle cx='88' cy='6' r='3.5'/%3E%3Ccircle cx='296' cy='18' r='3.5'/%3E%3C/g%3E%3C/svg%3E"),
90
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg transform='matrix(-1 0 0 1 300 0)' fill='black'%3E%3Ccircle cx='6' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='196' cy='28' r='7' fill-opacity='.25'/%3E%3Ccircle cx='296' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='6' cy='18' r='3.5'/%3E%3Ccircle cx='196' cy='28' r='3.5'/%3E%3Ccircle cx='296' cy='18' r='3.5'/%3E%3C/g%3E%3C/svg%3E");
91
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg fill='black'%3E%3Ccircle cx='6' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='88' cy='6' r='7' fill-opacity='.25'/%3E%3Ccircle cx='296' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='6' cy='18' r='3.5'/%3E%3Ccircle cx='88' cy='6' r='3.5'/%3E%3Ccircle cx='296' cy='18' r='3.5'/%3E%3C/g%3E%3C/svg%3E"),
92
+ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 30'%3E%3Cg transform='matrix(-1 0 0 1 300 0)' fill='black'%3E%3Ccircle cx='6' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='196' cy='28' r='7' fill-opacity='.25'/%3E%3Ccircle cx='296' cy='18' r='7' fill-opacity='.25'/%3E%3Ccircle cx='6' cy='18' r='3.5'/%3E%3Ccircle cx='196' cy='28' r='3.5'/%3E%3Ccircle cx='296' cy='18' r='3.5'/%3E%3C/g%3E%3C/svg%3E");
93
+ -webkit-mask-size: 300px 30px, 300px 30px;
94
+ mask-size: 300px 30px, 300px 30px;
95
+ -webkit-mask-position: left top, right top;
96
+ mask-position: left top, right top;
97
+ }
@@ -28,3 +28,12 @@ Pinned by `tests/leaderboard_roles.mjs` (the read, the route, the REAL roster.js
28
28
  - **The roster keeps its live columns**: place with the builder's number, rank, karma and tokens, the founding mark and the laurels, and its search and sort lenses. The mock draws the leaderboard bare; the live hall's chrome wins (the blend rule).
29
29
  - **The column is "Roles", not "Preferred roles"**: it lists roles the builder has earned in as well as the one they chose, so "preferred" would understate it.
30
30
  - **The status page has the roles but not the toggle.** It is a one-glance public dashboard of the top builders on its own palette; the toggle lives on the roster, which is the leaderboard a builder works from.
31
+
32
+ ## The circuit divider (task 1005032)
33
+
34
+ The owner's Studio idea 1001297 (a circuit-trace image as a table divider; mock `docs/design/mocks/builders-leaderboard/`, task 1005030) now sits between the roster's column headings and its first row. It is **CSS only, in `roster.css`**: the ledger kit is untouched and no markup was added.
35
+
36
+ - **It is drawn on the first BODY row, not under the sticky heading.** A divider hung under the sticky `th` would stay stuck while the rows scrolled away, leaving a blank band; on the first row it scrolls with the data. The row's top padding makes room for it.
37
+ - **The pseudo-elements sit on the row's first `td`, never on the `tr`.** On the `tr` they became two extra table cells and pushed every cell in the row two columns right (seen in the first render). The `tr` only carries `position: relative`, so the `td`'s absolute children size against the whole row.
38
+ - **Two masks, two tokens.** Traces are `--ink-faint` and the orange nodes `--accent`, each shown through an SVG mask, so light and dark follow the world with no hard-coded colour. Left cap, right cap and the stretching hairline are three layers of one mask.
39
+ - **The empty-state row has no trace** (`:not(:has(.ledger__empty))`). Checked at desktop and 375px, light and dark: the table scrolls inside its card on a phone and the divider spans it; the page does not scroll sideways.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.24.18",
3
+ "version": "1.24.20",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.24.18",
9
+ "version": "1.24.20",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",