shadok-ai 0.15.32 → 0.15.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,8 +79,10 @@ Enter to skip; you can add it later from the web UI).
79
79
  - **Groups column** — on a desktop the groups get a column of their own, left
80
80
  of the agents: pick a group and the agents column shows only its agents
81
81
  (*Ungrouped* holds the rest, the home agent included unless you file it). Each
82
- group has an icon — drawn at random, double-click it to pick another or type
83
- any emoji — and a **+** that creates the new agent straight into that group.
82
+ group has an icon — drawn at random, double-click it to pick another: choose
83
+ from the grid, type any emoji, or **type a word** (`rocket`, `bug`, `money`…)
84
+ to search for a matching one — and a **+** that creates the new agent straight
85
+ into that group.
84
86
  A single click on a group (its icon or its row) selects it; this matters once
85
87
  **«** folds the column to its icons, where the icon is the whole row.
86
88
  Dragging an agent onto a group still files it there, and dragging groups
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.15.32",
3
+ "version": "0.15.34",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test --test-timeout=120000 test/*.ts context/*/test/*.test.mjs",
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Keyword → emoji search for the group icon picker. Pure, so the browser and
3
+ * test/emoji-search.test.ts share it.
4
+ *
5
+ * The dataset is CURATED, not the full Unicode set: a few hundred emojis that
6
+ * cover the words people reach for when labelling a group (tech, work, status,
7
+ * nature, objects, places, symbols), kept small enough to ship inline with the
8
+ * page. Add rows freely — order does not matter, ranking is by how the query
9
+ * matches the keywords. The first keyword of a row reads as its primary name.
10
+ */
11
+ export const EMOJI = [
12
+ ["📁", "folder files directory archive"],
13
+ ["🗂", "folders tabs index organise organize dividers"],
14
+ ["🗃", "card box files records archive"],
15
+ ["📦", "package box parcel ship delivery build release"],
16
+ ["🚀", "rocket launch ship space startup boost deploy fast"],
17
+ ["🛰", "satellite space orbit signal telemetry"],
18
+ ["🧪", "test experiment lab chemistry tube qa trial"],
19
+ ["🔬", "microscope research science lab analysis inspect"],
20
+ ["🧬", "dna biology genetics science research"],
21
+ ["⚗️", "alembic chemistry distill experiment lab"],
22
+ ["🧫", "petri culture biology lab sample"],
23
+ ["🛠", "tools build maintenance fix wrench hammer devops"],
24
+ ["🔧", "wrench fix tool settings config repair"],
25
+ ["🔨", "hammer build fix construction tool"],
26
+ ["⚙️", "gear settings config cog engine mechanism system"],
27
+ ["🪛", "screwdriver fix tool repair"],
28
+ ["🧰", "toolbox tools kit maintenance"],
29
+ ["🎯", "target goal aim objective focus bullseye kpi"],
30
+ ["🧭", "compass navigate direction explore guide strategy"],
31
+ ["🗺", "map world explore route plan navigation"],
32
+ ["📍", "pin location place marker here"],
33
+ ["🌱", "seedling sprout grow new growth plant startup"],
34
+ ["🌳", "tree nature growth forest green"],
35
+ ["🍃", "leaf nature leaves green eco"],
36
+ ["🌿", "herb plant nature green eco"],
37
+ ["🌸", "blossom flower spring pink nature"],
38
+ ["🌼", "flower daisy spring nature yellow"],
39
+ ["🌻", "sunflower flower nature summer"],
40
+ ["🌺", "hibiscus flower tropical nature"],
41
+ ["🌲", "evergreen tree forest nature pine"],
42
+ ["🌵", "cactus plant desert nature"],
43
+ ["⚡", "lightning bolt power energy fast electric flash speed"],
44
+ ["🔥", "fire flame hot burning trending hype lit"],
45
+ ["💥", "boom explosion crash impact burst"],
46
+ ["✨", "sparkles shiny magic new clean polish stars"],
47
+ ["🌟", "star glowing shine favorite highlight"],
48
+ ["⭐", "star favorite rating bookmark"],
49
+ ["💫", "dizzy sparkle star shooting"],
50
+ ["🧩", "puzzle piece plugin module integration part extension"],
51
+ ["📊", "bar chart graph stats analytics metrics data report"],
52
+ ["📈", "chart up growth trending increase gain stats"],
53
+ ["📉", "chart down decline loss decrease stats"],
54
+ ["🧮", "abacus count math calculate numbers"],
55
+ ["🗒", "notepad notes memo list"],
56
+ ["📝", "memo note write edit document pencil draft"],
57
+ ["📋", "clipboard list tasks todo checklist copy"],
58
+ ["📄", "page document file paper sheet"],
59
+ ["📑", "bookmark tabs document pages index"],
60
+ ["📚", "books library docs knowledge learning reading"],
61
+ ["📖", "book open read docs manual guide"],
62
+ ["🔖", "bookmark tag label save marker"],
63
+ ["🏷", "label tag price category badge"],
64
+ ["🔒", "lock secure private locked security closed"],
65
+ ["🔓", "unlock open unlocked access"],
66
+ ["🔐", "lock key secure encryption security credentials"],
67
+ ["🔑", "key access password credentials secret login unlock"],
68
+ ["🗝", "key old access unlock"],
69
+ ["🛡", "shield security protect defense guard safe"],
70
+ ["🚨", "alert siren warning emergency alarm incident"],
71
+ ["⚠️", "warning caution alert danger attention"],
72
+ ["🚧", "construction wip work in progress barrier roadblock"],
73
+ ["🎨", "art design paint palette creative ui ux color"],
74
+ ["🖌", "paintbrush design art paint draw"],
75
+ ["🖍", "crayon draw color art"],
76
+ ["✏️", "pencil write edit draw note"],
77
+ ["🖊", "pen write sign ink"],
78
+ ["📣", "megaphone announce marketing promote shout broadcast"],
79
+ ["📢", "loudspeaker announce broadcast marketing alert"],
80
+ ["📡", "antenna satellite signal broadcast network transmit"],
81
+ ["📻", "radio broadcast audio news"],
82
+ ["🐛", "bug defect issue error insect problem fix"],
83
+ ["🐞", "ladybug bug beetle insect"],
84
+ ["🪲", "beetle bug insect"],
85
+ ["🦗", "cricket bug insect grasshopper"],
86
+ ["💡", "idea lightbulb insight suggestion bright think innovation"],
87
+ ["🧠", "brain think ai intelligence smart memory learning"],
88
+ ["🤖", "robot bot ai automation agent machine"],
89
+ ["👾", "alien game invader retro arcade monster"],
90
+ ["🕹", "joystick game gaming controller arcade play"],
91
+ ["🎮", "game controller gaming play console"],
92
+ ["🎲", "dice game random chance board"],
93
+ ["🧱", "brick build wall infrastructure block foundation"],
94
+ ["🏗", "construction crane build infrastructure scaffolding"],
95
+ ["🏠", "home house main base building"],
96
+ ["🏢", "office building company work corporate"],
97
+ ["🏦", "bank finance money building"],
98
+ ["🏭", "factory industry production manufacturing plant"],
99
+ ["🏪", "store shop convenience retail"],
100
+ ["🏬", "store mall shopping department retail"],
101
+ ["💰", "money bag finance cash budget revenue funds wealth"],
102
+ ["💵", "cash money dollar bill payment"],
103
+ ["💳", "card credit payment bank billing"],
104
+ ["🪙", "coin money crypto token currency"],
105
+ ["💎", "diamond gem premium value jewel quality"],
106
+ ["🏆", "trophy win award champion success achievement"],
107
+ ["🥇", "medal gold first win rank"],
108
+ ["🏅", "medal award win sports achievement"],
109
+ ["🎖", "medal military honor award"],
110
+ ["🎁", "gift present box reward surprise"],
111
+ ["🎉", "party celebrate launch success tada confetti"],
112
+ ["🎊", "confetti celebrate party ball festive"],
113
+ ["🔔", "bell notification alert reminder ring"],
114
+ ["🔕", "mute silent bell off notification"],
115
+ ["⏰", "alarm clock time reminder wake schedule"],
116
+ ["⏱", "stopwatch timer time speed measure"],
117
+ ["⌛", "hourglass time wait timeout pending"],
118
+ ["⏳", "hourglass time wait loading pending"],
119
+ ["📅", "calendar date schedule event plan day"],
120
+ ["📆", "calendar date schedule month plan"],
121
+ ["🗓", "calendar schedule planner dates agenda"],
122
+ ["🕐", "clock time hour schedule"],
123
+ ["📌", "pushpin pin mark note important location"],
124
+ ["📎", "paperclip attach clip link file"],
125
+ ["🔗", "link chain url connect reference hyperlink"],
126
+ ["⛓", "chain links connect blockchain"],
127
+ ["🧵", "thread sew spool line discussion"],
128
+ ["🪡", "needle sew thread stitch"],
129
+ ["💬", "speech chat message talk comment discussion bubble"],
130
+ ["🗨", "speech chat message talk comment"],
131
+ ["🗯", "anger speech shout argue"],
132
+ ["💭", "thought think bubble idea dream"],
133
+ ["📨", "email mail message envelope send inbox"],
134
+ ["📧", "email mail message send inbox electronic"],
135
+ ["✉️", "envelope mail email message letter"],
136
+ ["📮", "mailbox post mail send"],
137
+ ["📬", "mailbox mail inbox received post"],
138
+ ["📞", "phone call telephone contact support"],
139
+ ["📱", "phone mobile smartphone app device"],
140
+ ["☎️", "phone telephone call landline"],
141
+ ["💻", "laptop computer code dev work machine"],
142
+ ["🖥", "desktop computer monitor screen pc workstation"],
143
+ ["⌨️", "keyboard type input code typing"],
144
+ ["🖱", "mouse click pointer cursor"],
145
+ ["🖨", "printer print paper output"],
146
+ ["💾", "save disk floppy storage backup"],
147
+ ["💿", "disc cd storage media"],
148
+ ["📀", "dvd disc media storage"],
149
+ ["🗜", "clamp compress vice squeeze"],
150
+ ["🧯", "extinguisher fire safety incident hotfix emergency"],
151
+ ["☁️", "cloud sky weather server hosting storage"],
152
+ ["⛅", "cloud sun partly weather"],
153
+ ["🌧", "rain cloud weather storm"],
154
+ ["⛈", "storm thunder rain weather lightning"],
155
+ ["🌩", "lightning storm cloud thunder weather"],
156
+ ["❄️", "snow cold snowflake freeze winter frozen"],
157
+ ["🌈", "rainbow colorful pride spectrum weather"],
158
+ ["🌞", "sun bright day sunny weather warm"],
159
+ ["🌝", "moon full bright night"],
160
+ ["🌙", "moon night crescent dark sleep"],
161
+ ["🌊", "wave water ocean sea flow surf"],
162
+ ["💧", "drop water droplet liquid"],
163
+ ["🔆", "bright light high brightness sun"],
164
+ ["🌐", "globe web internet world network global www"],
165
+ ["🌍", "earth world globe europe africa global"],
166
+ ["🌎", "earth world globe americas global"],
167
+ ["🌏", "earth world globe asia australia global"],
168
+ ["🧭", "compass direction navigate explore"],
169
+ ["🚩", "flag milestone marker report issue checkpoint"],
170
+ ["🏁", "checkered flag finish race goal done end"],
171
+ ["🏴", "flag black marker"],
172
+ ["🏳️", "flag white surrender"],
173
+ ["📐", "ruler measure design geometry precise angle"],
174
+ ["📏", "ruler measure straight length design"],
175
+ ["🧷", "pin safety attach clip"],
176
+ ["🔭", "telescope observe explore vision future astronomy"],
177
+ ["🔎", "search magnify find look zoom inspect"],
178
+ ["🔍", "search magnify find look zoom query"],
179
+ ["👁", "eye watch monitor view see observe"],
180
+ ["👀", "eyes watch look review peek monitor"],
181
+ ["🎧", "headphones audio music listen sound podcast"],
182
+ ["🎵", "music note song audio sound"],
183
+ ["🎶", "music notes song melody audio"],
184
+ ["🎙", "microphone podcast record voice audio studio"],
185
+ ["🎚", "level slider control mix audio"],
186
+ ["🎛", "knobs control mixer settings audio dials"],
187
+ ["📷", "camera photo picture snapshot image"],
188
+ ["📸", "camera photo flash picture snapshot"],
189
+ ["📹", "video camera record film movie"],
190
+ ["🎥", "movie camera film cinema video"],
191
+ ["🎬", "clapper film movie production action shoot"],
192
+ ["📺", "tv television screen media broadcast"],
193
+ ["🔋", "battery power energy charge level"],
194
+ ["🔌", "plug power electric connect socket"],
195
+ ["💤", "sleep zzz idle rest inactive paused"],
196
+ ["♻️", "recycle reuse green eco refresh cycle"],
197
+ ["🔄", "refresh sync reload cycle loop repeat update"],
198
+ ["🔁", "repeat loop cycle again"],
199
+ ["▶️", "play start run go resume"],
200
+ ["⏸", "pause hold stop wait"],
201
+ ["⏹", "stop end halt square"],
202
+ ["⏭", "next skip forward"],
203
+ ["⏮", "previous back rewind"],
204
+ ["✅", "check done complete ok success pass tick yes"],
205
+ ["☑️", "check box done ticked complete"],
206
+ ["✔️", "check tick done ok yes"],
207
+ ["❌", "cross no fail error wrong cancel delete"],
208
+ ["❎", "cross no cancel reject"],
209
+ ["➕", "plus add new create more"],
210
+ ["➖", "minus remove subtract less"],
211
+ ["❓", "question help unknown ask what"],
212
+ ["❗", "exclamation important alert attention"],
213
+ ["‼️", "exclamation urgent important double"],
214
+ ["💯", "hundred perfect score done full best"],
215
+ ["🆕", "new fresh badge release"],
216
+ ["🆗", "ok okay good approve"],
217
+ ["ℹ️", "info information help about"],
218
+ ["🤝", "handshake deal partner agreement team collaborate"],
219
+ ["👥", "people team group users members"],
220
+ ["👤", "person user profile account single"],
221
+ ["🧑‍💻", "developer coder engineer programmer dev technologist"],
222
+ ["👨‍💻", "developer coder engineer programmer dev"],
223
+ ["👩‍💻", "developer coder engineer programmer dev"],
224
+ ["🫂", "hug people support team care"],
225
+ ["🦾", "arm robotic strong mechanical power"],
226
+ ["💪", "muscle strong power strength effort"],
227
+ ["🙌", "hands celebrate praise success raise"],
228
+ ["👏", "clap applause praise bravo good"],
229
+ ["👍", "thumbs up good approve like yes ok"],
230
+ ["👎", "thumbs down bad reject dislike no"],
231
+ ["🫡", "salute respect acknowledge yes sir"],
232
+ ["🧑‍🔧", "mechanic engineer fix maintenance technician"],
233
+ ["🕵️", "detective investigate search spy inspect"],
234
+ ["🦸", "hero super power champion"],
235
+ ["🧙", "wizard magic spell mage"],
236
+ ["🐱", "cat pet animal kitten"],
237
+ ["🐶", "dog pet animal puppy"],
238
+ ["🦊", "fox animal clever orange"],
239
+ ["🐼", "panda animal bear cute"],
240
+ ["🐸", "frog animal green toad"],
241
+ ["🦉", "owl bird wise night animal"],
242
+ ["🦅", "eagle bird fly sky animal"],
243
+ ["🐝", "bee insect busy honey animal"],
244
+ ["🦋", "butterfly insect transform animal pretty"],
245
+ ["🐙", "octopus animal sea tentacles"],
246
+ ["🐳", "whale animal sea ocean big"],
247
+ ["🐬", "dolphin animal sea ocean"],
248
+ ["🦈", "shark animal sea ocean danger"],
249
+ ["🦄", "unicorn magic rare special fantasy"],
250
+ ["🐉", "dragon mythical fantasy power"],
251
+ ["🦖", "dinosaur trex rawr prehistoric"],
252
+ ["🍎", "apple fruit food red health"],
253
+ ["🍋", "lemon fruit food citrus yellow"],
254
+ ["🍊", "orange fruit food citrus"],
255
+ ["🍓", "strawberry fruit food berry red"],
256
+ ["🍌", "banana fruit food yellow"],
257
+ ["🍇", "grapes fruit food purple"],
258
+ ["🍉", "watermelon fruit food summer"],
259
+ ["🥝", "kiwi fruit food green"],
260
+ ["🍕", "pizza food slice italian"],
261
+ ["🍔", "burger food fast hamburger"],
262
+ ["🌮", "taco food mexican"],
263
+ ["🍜", "noodles ramen food soup bowl"],
264
+ ["🍣", "sushi food japanese fish"],
265
+ ["🍰", "cake dessert food sweet slice"],
266
+ ["🎂", "birthday cake celebrate party dessert"],
267
+ ["🍪", "cookie food sweet dessert"],
268
+ ["☕", "coffee drink cafe morning caffeine tea hot"],
269
+ ["🍵", "tea drink hot green matcha"],
270
+ ["🍺", "beer drink pub cheers"],
271
+ ["🍷", "wine drink glass red"],
272
+ ["🥂", "cheers toast celebrate champagne"],
273
+ ["🧊", "ice cube cold frozen chill"],
274
+ ["⚽", "soccer football ball sport game"],
275
+ ["🏀", "basketball ball sport game hoop"],
276
+ ["🏈", "football american ball sport"],
277
+ ["⚾", "baseball ball sport game"],
278
+ ["🎾", "tennis ball sport game"],
279
+ ["🏐", "volleyball ball sport game"],
280
+ ["🏓", "ping pong table tennis sport"],
281
+ ["🥊", "boxing fight glove sport"],
282
+ ["♟", "chess pawn strategy game play"],
283
+ ["🧗", "climb climbing mountain challenge"],
284
+ ["🏔", "mountain peak summit climb nature"],
285
+ ["⛰", "mountain peak nature summit"],
286
+ ["🌋", "volcano eruption lava mountain"],
287
+ ["🏕", "camp tent outdoor nature camping"],
288
+ ["🚗", "car vehicle drive auto transport"],
289
+ ["🚕", "taxi car cab transport"],
290
+ ["🚙", "suv car vehicle transport"],
291
+ ["🚌", "bus vehicle transport public"],
292
+ ["🚄", "train fast rail bullet transport speed"],
293
+ ["🚂", "train locomotive rail transport"],
294
+ ["✈️", "plane flight airplane travel fly transport"],
295
+ ["🚁", "helicopter fly air transport"],
296
+ ["🚢", "ship boat sea cruise transport"],
297
+ ["⛵", "sailboat boat sea sail"],
298
+ ["🚲", "bike bicycle cycle ride transport"],
299
+ ["🛴", "scooter kick ride transport"],
300
+ ["🏍", "motorcycle bike ride transport fast"],
301
+ ["🚦", "traffic light signal stop go"],
302
+ ["🛑", "stop sign halt red"],
303
+ ["⚓", "anchor ship sea stable base"],
304
+ ["🪝", "hook catch snag"],
305
+ ["🧲", "magnet attract pull force"],
306
+ ["⚖️", "scales balance justice legal fair law weigh"],
307
+ ["🔮", "crystal ball predict future magic forecast"],
308
+ ["🎰", "slot machine gamble luck random casino"],
309
+ ["🧿", "amulet eye protection charm"],
310
+ ["🪬", "hamsa protection charm hand"],
311
+ ["🕯", "candle light flame calm"],
312
+ ["🏮", "lantern light red festival"],
313
+ ["🧸", "teddy bear toy cute plush"],
314
+ ["🪁", "kite fly wind play sky"],
315
+ ["🎈", "balloon party celebrate float"],
316
+ ["🪄", "wand magic spell wizard trick"],
317
+ ["🧹", "broom clean sweep tidy cleanup"],
318
+ ["🧽", "sponge clean wash scrub"],
319
+ ["🧼", "soap clean wash hygiene"],
320
+ ["🪣", "bucket pail water clean"],
321
+ ["🗑", "trash bin delete remove garbage waste"],
322
+ ["♾", "infinity endless loop forever unlimited"],
323
+ ["🔱", "trident symbol power emblem"],
324
+ ["⚜️", "fleur symbol emblem heraldry"],
325
+ ["🧊", "ice block frozen cold"],
326
+ ["🪨", "rock stone solid hard"],
327
+ ["🧶", "yarn knit wool thread craft"],
328
+ ["🎀", "ribbon bow gift decorate pretty"],
329
+ ["🪢", "knot rope tie"],
330
+ ["📿", "beads prayer rosary necklace"],
331
+ ["🔆", "brightness bright light"],
332
+ ["⭕", "circle round o mark"],
333
+ ["🔺", "triangle up red shape"],
334
+ ["🔻", "triangle down red shape"],
335
+ ["🟢", "green circle ok go on active status"],
336
+ ["🟡", "yellow circle warn caution status"],
337
+ ["🔴", "red circle stop off error status live"],
338
+ ["🔵", "blue circle status info"],
339
+ ["🟣", "purple circle status"],
340
+ ["🟠", "orange circle status"],
341
+ ["⚫", "black circle dot status"],
342
+ ["⚪", "white circle dot status"],
343
+ ["🟥", "red square block status"],
344
+ ["🟩", "green square block status ok"],
345
+ ["🟦", "blue square block status"],
346
+ ["🟨", "yellow square block status"],
347
+ ["💗", "heart pink love like favorite"],
348
+ ["❤️", "heart red love like favorite"],
349
+ ["🧡", "heart orange love like"],
350
+ ["💛", "heart yellow love like"],
351
+ ["💚", "heart green love like eco"],
352
+ ["💙", "heart blue love like"],
353
+ ["💜", "heart purple love like"],
354
+ ["🖤", "heart black love dark"],
355
+ ["🤍", "heart white love pure"],
356
+ ];
357
+
358
+ /**
359
+ * Does this string already contain a real emoji? Then it is a literal icon the
360
+ * user typed, not a word to search for.
361
+ */
362
+ export function looksLikeEmoji(s) {
363
+ try {
364
+ return /\p{Extended_Pictographic}/u.test(String(s ?? ""));
365
+ } catch {
366
+ // A runtime without Unicode property escapes: fall back to "any non-ASCII".
367
+ return /[^\u0000-ÿ]/.test(String(s ?? ""));
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Emojis whose keywords match the query, best first. Ranking per emoji is the
373
+ * strongest match over the query's tokens: a keyword equal to a token (3) beats
374
+ * one that starts with it (2), which beats one that merely contains it (1).
375
+ * Deduped and capped at `limit`. An empty or whitespace query returns [].
376
+ */
377
+ export function searchEmoji(query, limit = 24) {
378
+ const q = String(query ?? "").toLowerCase().trim();
379
+ if (!q) return [];
380
+ const tokens = q.split(/\s+/).filter(Boolean);
381
+ const scored = [];
382
+ for (const [emoji, kw] of EMOJI) {
383
+ const words = kw.split(/\s+/);
384
+ let best = 0;
385
+ for (const t of tokens) {
386
+ for (const w of words) {
387
+ if (w === t) best = Math.max(best, 3);
388
+ else if (w.startsWith(t)) best = Math.max(best, 2);
389
+ else if (t.length >= 2 && w.includes(t)) best = Math.max(best, 1);
390
+ }
391
+ }
392
+ if (best) scored.push([emoji, best]);
393
+ }
394
+ scored.sort((a, b) => b[1] - a[1]);
395
+ const seen = new Set();
396
+ const out = [];
397
+ for (const [e] of scored) {
398
+ if (seen.has(e)) continue;
399
+ seen.add(e);
400
+ out.push(e);
401
+ if (out.length >= limit) break;
402
+ }
403
+ return out;
404
+ }
package/public/index.html CHANGED
@@ -734,9 +734,9 @@
734
734
  .rail-item.drop-target { border-color: var(--amber); border-style: dashed; }
735
735
  .rail-item.dragging { opacity: 0.4; }
736
736
  .rail-icon {
737
- flex: none; width: 24px; height: 24px; padding: 0;
737
+ flex: none; width: 28px; height: 28px; padding: 0;
738
738
  display: inline-flex; align-items: center; justify-content: center;
739
- border: 1px solid transparent; border-radius: 5px; background: none; font-size: 15px; line-height: 1;
739
+ border: 1px solid transparent; border-radius: 5px; background: none; font-size: 18px; line-height: 1;
740
740
  }
741
741
  .rail-item.editable .rail-icon:hover { border-color: var(--line); background: var(--bg-inset); }
742
742
  .rail-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -773,6 +773,10 @@
773
773
  body.rail-mini .rail-add, body.rail-mini .rail-del,
774
774
  body.rail-mini .rail-txt { display: none !important; }
775
775
  body.rail-mini .rail-item { justify-content: center; padding: 6px 0; }
776
+ /* In the collapsed dock the icon IS the group — the name lives only in the
777
+ tooltip — so make it noticeably bigger here. The 52px dock (40px inside
778
+ its padding) leaves room for a 38px target. */
779
+ body.rail-mini .rail-icon { width: 38px; height: 38px; font-size: 24px; border-radius: 8px; }
776
780
  body.rail-mini .rail-dot { position: absolute; top: 4px; right: 4px; }
777
781
 
778
782
  /* The agents column, narrowed to the selected group. */
@@ -795,10 +799,12 @@
795
799
  background: var(--menu-bg); border: 1px solid var(--line); border-radius: 8px;
796
800
  padding: 8px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); width: 212px;
797
801
  }
798
- .icon-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; margin-bottom: 8px; }
802
+ .icon-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; margin-top: 8px; max-height: 176px; overflow-y: auto; }
799
803
  .icon-grid button { border: 1px solid transparent; background: none; font-size: 18px; padding: 4px 0; border-radius: 5px; }
800
804
  .icon-grid button:hover, .icon-grid button.current { border-color: var(--line); background: var(--menu-hover); }
801
- .icon-picker input { width: 100%; font-size: 13px; }
805
+ .icon-grid:empty { display: none; }
806
+ .icon-empty { font-size: 12px; color: var(--text-dim); text-align: center; padding: 8px 2px 2px; }
807
+ .icon-picker input { width: 100%; font-size: 13px; box-sizing: border-box; }
802
808
 
803
809
  /* ── Body ────────────────────────────────────────────── */
804
810
  main { flex: 1; display: flex; min-height: 0; }
@@ -2552,8 +2558,9 @@
2552
2558
  <button id="newGroup" title="New agent group">+ <span class="rail-txt">new group</span></button>
2553
2559
  </nav>
2554
2560
  <div id="iconPicker" class="icon-picker" hidden>
2561
+ <input id="iconInput" placeholder="type a word (rocket, bug…) or an emoji" autocomplete="off" spellcheck="false">
2555
2562
  <div id="iconGrid" class="icon-grid"></div>
2556
- <input id="iconInput" placeholder="or type an emoji…" autocomplete="off" spellcheck="false">
2563
+ <div id="iconEmpty" class="icon-empty" hidden>No emoji for that word.</div>
2557
2564
  </div>
2558
2565
  <nav id="tabbar" aria-label="Agents">
2559
2566
  <div class="label side-label"><span class="sl-all">Agents</span><span class="sl-view" id="viewLabel"></span></div>
@@ -2749,6 +2756,9 @@
2749
2756
  window.resolveGroupView = resolveGroupView;
2750
2757
  window.groupMood = groupMood;
2751
2758
  window.cleanGroupIcon = cleanGroupIcon;
2759
+ import { searchEmoji, looksLikeEmoji } from "/emoji-search.js?v=__ASSET_V__";
2760
+ window.searchEmoji = searchEmoji;
2761
+ window.looksLikeEmoji = looksLikeEmoji;
2752
2762
  import { uploadFailure } from "/upload.js?v=__ASSET_V__";
2753
2763
  window.uploadFailure = uploadFailure;
2754
2764
  import { fmtExpiry, expiryWarning } from "/auth-expiry.js?v=__ASSET_V__";
@@ -2885,6 +2895,11 @@
2885
2895
  resolveGroupView: (saved, ids) => ((ids || []).map(String).includes(String(saved)) ? String(saved) : "ungrouped"),
2886
2896
  groupMood: (moods) => ["needs-answer", "unread", "working"].find((m) => (moods || []).includes(m)) || null,
2887
2897
  cleanGroupIcon: (t) => String(t ?? "").trim() || null,
2898
+ // The emoji keyword search feeds the icon picker, which opens well after
2899
+ // load; before the module lands, no suggestions (and typed text is treated
2900
+ // as a word, not an emoji) — both harmless, repainted on bridge-ready.
2901
+ searchEmoji: () => [],
2902
+ looksLikeEmoji: () => false,
2888
2903
  uploadFailure: (status, contentType) =>
2889
2904
  /json/i.test(contentType || "") ? null : status === 413 ? "too large (over the server's upload limit)" : `upload failed (HTTP ${status || "?"})`,
2890
2905
  };
@@ -4354,20 +4369,39 @@
4354
4369
  }
4355
4370
  closeIconPicker();
4356
4371
  }
4357
- function openIconPicker(group, anchor) {
4358
- iconTarget = group;
4372
+ // What the grid shows: the default set when the field is empty, the emoji the
4373
+ // user typed when the field already holds one, otherwise the emojis whose
4374
+ // keywords match the typed word(s). This is what turns the field from "type
4375
+ // an emoji" into "type a word and pick one" — the two-letters trap was that a
4376
+ // typed word ("rocket") was simply truncated by cleanGroupIcon to "ro".
4377
+ function iconCandidates(query) {
4378
+ const q = String(query || "").trim();
4379
+ if (!q) return { list: window.GROUP_ICONS || ["📁"], isDefault: true };
4380
+ if (window.looksLikeEmoji(q)) return { list: [window.cleanGroupIcon(q)].filter(Boolean), isDefault: false };
4381
+ return { list: window.searchEmoji(q), isDefault: false };
4382
+ }
4383
+ function paintIconGrid(query) {
4359
4384
  const grid = $("iconGrid");
4385
+ const { list, isDefault } = iconCandidates(query);
4386
+ const current = iconTarget && iconTarget.icon;
4360
4387
  grid.replaceChildren(
4361
- ...(window.GROUP_ICONS || ["📁"]).map((ic) => {
4388
+ ...list.map((ic) => {
4362
4389
  const b = document.createElement("button");
4363
4390
  b.type = "button";
4364
4391
  b.textContent = ic;
4365
- if (ic === group.icon) b.classList.add("current");
4392
+ if (isDefault && ic === current) b.classList.add("current");
4366
4393
  b.addEventListener("click", (e) => { e.stopPropagation(); setGroupIcon(ic); });
4367
4394
  return b;
4368
4395
  }),
4369
4396
  );
4397
+ // A searched word with no match: say so rather than leave a blank box.
4398
+ const q = String(query || "").trim();
4399
+ $("iconEmpty").hidden = !(q && !window.looksLikeEmoji(q) && list.length === 0);
4400
+ }
4401
+ function openIconPicker(group, anchor) {
4402
+ iconTarget = group;
4370
4403
  iconInput.value = "";
4404
+ paintIconGrid("");
4371
4405
  iconPicker.hidden = false;
4372
4406
  const r = anchor.getBoundingClientRect();
4373
4407
  iconPicker.style.left = Math.min(r.left, innerWidth - iconPicker.offsetWidth - 8) + "px";
@@ -4379,10 +4413,18 @@
4379
4413
  iconTarget = null;
4380
4414
  }
4381
4415
  iconPicker.addEventListener("click", (e) => e.stopPropagation());
4416
+ iconInput.addEventListener("input", () => paintIconGrid(iconInput.value));
4382
4417
  iconInput.addEventListener("keydown", (e) => {
4383
4418
  e.stopPropagation();
4384
- if (e.key === "Enter") { e.preventDefault(); setGroupIcon(iconInput.value); }
4385
- if (e.key === "Escape") closeIconPicker();
4419
+ if (e.key === "Escape") return closeIconPicker();
4420
+ if (e.key === "Enter") {
4421
+ e.preventDefault();
4422
+ const v = iconInput.value;
4423
+ // A literal emoji sets itself; a searched word takes the first match.
4424
+ if (window.looksLikeEmoji(v)) return setGroupIcon(v);
4425
+ const hit = window.searchEmoji(v)[0];
4426
+ if (hit) setGroupIcon(hit);
4427
+ }
4386
4428
  });
4387
4429
  document.addEventListener("click", () => { if (!iconPicker.hidden) closeIconPicker(); });
4388
4430