harness-bujang 0.5.4 → 0.5.6

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/dist/index.js CHANGED
@@ -1198,8 +1198,9 @@ const ROLES = {
1198
1198
  };
1199
1199
 
1200
1200
  const ROOMS = [
1201
- // Top-level
1202
- { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', 'consultant', '\uBD80\uC7A5'] },
1201
+ // Top-level \u2014 kept narrow on purpose so the "smallest matching room wins"
1202
+ // filter routes director\u2192principal reports to \uB300\uD45C \uBCF4\uACE0 (not \uACF5\uB3D9\uB300\uD45C).
1203
+ { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', '\uBD80\uC7A5'] },
1203
1204
  { id: '\uACF5\uB3D9\uB300\uD45C', name: '\uACF5\uB3D9\uB300\uD45C', icon: '\u2B50', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', '\uBD80\uC7A5'] },
1204
1205
  { id: 'consultant', name: '\uCEE8\uC124\uD134\uD2B8', icon: '\u{1F91D}', members: ['consultant', '\uBD80\uC7A5'] },
1205
1206
  // Engineering teams
@@ -1344,7 +1345,7 @@ function render() {
1344
1345
  html += '</div>';
1345
1346
  html += '</div>';
1346
1347
 
1347
- html += '<div class="flex-1 overflow-y-auto">';
1348
+ html += '<div id="room-list" class="flex-1 overflow-y-auto">';
1348
1349
  // When 'unread' filter is active, only show rooms with unread > 0.
1349
1350
  const visibleRooms = state.filter === 'unread'
1350
1351
  ? ROOMS.filter((r) => unreadByRoom[r.id] > 0)
@@ -1431,8 +1432,27 @@ function render() {
1431
1432
  }
1432
1433
  html += '</div>';
1433
1434
 
1435
+ // Save scroll positions BEFORE replacing innerHTML \u2014 otherwise every 2s poll
1436
+ // resets the sidebar room-list scroll to top, and the conversation re-snaps
1437
+ // to the bottom even if the user was reading older messages.
1438
+ const oldRoomList = document.getElementById('room-list');
1439
+ const oldConv = document.getElementById('conversation');
1440
+ const savedRoomScroll = oldRoomList ? oldRoomList.scrollTop : 0;
1441
+ let savedConvScroll = null;
1442
+ let convWasAtBottom = true;
1443
+ if (oldConv) {
1444
+ savedConvScroll = oldConv.scrollTop;
1445
+ // "At bottom" within ~80px \u2014 if user was at bottom we keep them at bottom
1446
+ // (so new messages stay visible). If they scrolled up, preserve position.
1447
+ convWasAtBottom = (oldConv.scrollHeight - oldConv.scrollTop - oldConv.clientHeight) < 80;
1448
+ }
1449
+
1434
1450
  root.innerHTML = html;
1435
1451
 
1452
+ // Restore scroll positions.
1453
+ const newRoomList = document.getElementById('room-list');
1454
+ if (newRoomList) newRoomList.scrollTop = savedRoomScroll;
1455
+
1436
1456
  // Re-bind filter buttons (\uC804\uCCB4 / \uC548\uC77D\uC74C).
1437
1457
  document.querySelectorAll('[data-filter]').forEach((el) => {
1438
1458
  el.addEventListener('click', () => {
@@ -1455,11 +1475,20 @@ function render() {
1455
1475
  });
1456
1476
  });
1457
1477
 
1458
- // Auto-scroll the selected room to the bottom on first render of that room.
1478
+ // Conversation scroll handling:
1479
+ // - First render of a room \u2192 scroll to bottom (latest msg visible)
1480
+ // - User was at bottom \u2192 keep at new bottom (so new msgs auto-show)
1481
+ // - User scrolled up to read history \u2192 preserve their position
1459
1482
  const conv = document.getElementById('conversation');
1460
- if (conv && conv.dataset.scrolled !== state.selectedRoom) {
1461
- conv.scrollTop = conv.scrollHeight;
1462
- conv.dataset.scrolled = state.selectedRoom;
1483
+ if (conv) {
1484
+ if (conv.dataset.scrolled !== state.selectedRoom) {
1485
+ conv.scrollTop = conv.scrollHeight;
1486
+ conv.dataset.scrolled = state.selectedRoom;
1487
+ } else if (convWasAtBottom) {
1488
+ conv.scrollTop = conv.scrollHeight;
1489
+ } else if (savedConvScroll !== null) {
1490
+ conv.scrollTop = savedConvScroll;
1491
+ }
1463
1492
  }
1464
1493
  }
1465
1494
 
@@ -2171,7 +2200,7 @@ async function main() {
2171
2200
  break;
2172
2201
  case "--version":
2173
2202
  case "-v":
2174
- console.log("0.5.4");
2203
+ console.log("0.5.6");
2175
2204
  break;
2176
2205
  case "--help":
2177
2206
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-bujang",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Install the Harness-Bujang multi-agent harness into any project — Director, 7 specialist teams, real-time chat-room UI. Korean and English personas. Works with Claude Code, Cursor, Cline, Aider, or any tool that reads .claude/agents/.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -51,8 +51,10 @@ const ROLES: Record<string, { icon: string; color: string; bg: string; label?: s
51
51
  };
52
52
 
53
53
  const ROOMS = [
54
- // Top
55
- { id: '대표님', name: '대표 보고', icon: '👔', members: ['대표님', '공동대표', 'consultant', '부장'] },
54
+ // Top — kept narrow on purpose so director→principal reports route to 대표 보고
55
+ // (not 공동대표). The "smallest matching room wins" filter would otherwise
56
+ // funnel them to 공동대표 because that room also includes both 부장 and 대표님.
57
+ { id: '대표님', name: '대표 보고', icon: '👔', members: ['대표님', '부장'] },
56
58
  { id: '공동대표', name: '공동대표', icon: '⭐', members: ['대표님', '공동대표', '부장'] },
57
59
  { id: 'consultant', name: '컨설턴트', icon: '🤝', members: ['consultant', '부장'] },
58
60
  // Engineering