Skip to content

Commit 8919fb6

Browse files
committed
Enh(viewer): multiselect in picker
1 parent f356116 commit 8919fb6

5 files changed

Lines changed: 389 additions & 34 deletions

File tree

.cursor/rules/viewer.mdc

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ Here's how the metadata.json file looks like:
101101
"players": [
102102
{
103103
"agent": "mini",
104-
"name": "gpt-5-mini-more-prescriptive",
104+
"name": "sonnet-4-more-prescriptive",
105105
"config": {
106106
"agent": {
107107
"system_template": "",
@@ -145,12 +145,12 @@ Here's how the metadata.json file looks like:
145145
},
146146
"agents": [
147147
{
148-
"name": "gpt-5-mini-more-prescriptive",
148+
"name": "sonnet-4-more-prescriptive",
149149
"player_unique_id": "38f5a945-2a14-4cfb-9a41-e3ccceb7d3e2",
150150
"created_timestamp": 1757982729,
151151
"config": {
152152
"agent": "mini",
153-
"name": "gpt-5-mini-more-prescriptive",
153+
"name": "sonnet-4-more-prescriptive",
154154
"config": {
155155
"agent": {
156156
"system_template": "...,
@@ -164,7 +164,7 @@ Here's how the metadata.json file looks like:
164164
}
165165
},
166166
"initial_commit_hash": "dc7bdb934a2f1bba88d19136ff5caa6ff789dda7",
167-
"branch_name": "PvpTournament.BattleSnake.250915203207.gpt-5-mini-more-prescriptive",
167+
"branch_name": "PvpTournament.BattleSnake.250915203207.sonnet-4-more-prescriptive",
168168
"round_tags": {}
169169
},
170170
{
@@ -213,36 +213,53 @@ The application has two main pages:
213213

214214
### Mouse Navigation
215215

216-
* **Left-click**: Default action (navigate in same tab)
217-
* **Middle-click**: Open in new tab (works on all buttons and game rows)
218-
* **Ctrl+click or Cmd+click**: Open in new tab (alternative to middle-click)
216+
* **Left-click in checkbox column**: Toggle checkbox selection
217+
* **Left-click elsewhere on game row**: Navigate to viewer in same tab
218+
* **Middle-click on game row**: Open in new tab
219+
* **Ctrl+click or Cmd+click on game row**: Open in new tab (alternative to middle-click)
220+
* **Drag over checkbox column**: Multi-select games by dragging over checkboxes
221+
* **Left-click on Open button**: Navigate to viewer in same tab
222+
* **Middle-click on Open button**: Open in new tab
223+
* **Ctrl+click or Cmd+click on Open button**: Open in new tab (alternative to middle-click)
224+
* **Left-click on ↗ button**: Always open in new tab
219225

220226
## Game Picker Design (`/picker`)
221227

222228
### Features
223229

224230
* **Hierarchical Tree Structure**: Shows all folders recursively with proper indentation
225231
* **Game Detection**: Any folder containing `metadata.json` is considered a game folder
232+
* **Multi-Selection Support**: Checkboxes allow selecting multiple games for batch operations
233+
* **Batch Actions**: Action dropdown with options like "Copy pathnames" for selected games
234+
* **Readme Display**: Shows the first line of each game's `readme.txt` file in the table
226235
* **Folder Collapse/Expand**: Click intermediate folders to minimize/expand their contents
227236
- Collapsed folders show 📂 icon and blue accent border
228237
- Expanded folders show 📁 icon
229238
- Children of collapsed folders are hidden
230239
* **Visual Distinction**:
231-
- Game folders: 🎮 icon, clickable, blue accent color
240+
- Game folders: 🎮 icon, clickable for selection, blue accent color
232241
- Intermediate folders: 📁/📂 icon, clickable to collapse, muted appearance
233242
* **Tree Indentation**: Uses spaces and tree characters (└─) to show folder hierarchy
234243
* **Round Count Display**: Shows number of rounds for each game session
235-
* **Multiple Navigation Options**:
236-
- Click game row: Navigate to viewer (middle-click for new tab)
244+
* **Model Display**: Shows full model names without truncation
245+
* **Selection Behavior**:
246+
- Click in checkbox column: Toggle checkbox selection
247+
- Drag over checkbox column: Multi-select games by dragging
248+
- Click elsewhere on game row: Navigate to viewer (supports middle-click)
237249
- "Open" button: Navigate to viewer (supports middle-click)
238250
- "↗" button: Always open viewer in new tab
251+
* **Batch Operations**:
252+
- "Select All" checkbox to toggle all games
253+
- Styled action dropdown with custom arrow and hover effects for batch operations on selected games
254+
- Copy pathnames copies selected game paths as space-separated string to clipboard
239255

240256
### Layout
241257

242-
* Clean table structure with three columns: Game Session, Rounds, Action
243-
* Responsive design that adapts to mobile screens
258+
* Full-width table structure with six columns: Select, Game Session, Note, Rounds, Models, Action
259+
* Responsive design that adapts to mobile screens (hides some columns on mobile)
244260
* Hover effects and visual feedback
245261
* Base directory path display at the top
262+
* Control bar with selection controls and action dropdown
246263

247264
## Trajectory Viewer Design (`/`)
248265

codeclash/viewer/app.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ def get_models_from_metadata(log_dir: Path) -> list[str]:
8686
return []
8787

8888

89+
def get_readme_first_line(log_dir: Path) -> str:
90+
"""Extract the first line from readme.txt if it exists"""
91+
readme_file = log_dir / "readme.txt"
92+
if not readme_file.exists():
93+
return ""
94+
95+
try:
96+
content = readme_file.read_text().strip()
97+
if not content:
98+
return ""
99+
# Get the first non-empty line
100+
lines = content.split("\n")
101+
for line in lines:
102+
line = line.strip()
103+
if line:
104+
return line
105+
return ""
106+
except (OSError, UnicodeDecodeError):
107+
return ""
108+
109+
89110
def get_agent_info_from_metadata(metadata: dict[str, Any]) -> list[AgentInfo]:
90111
"""Extract detailed agent information from metadata"""
91112
agents = []
@@ -160,13 +181,15 @@ def scan_directory(directory: Path, relative_path: str = ""):
160181
if is_game_folder(item):
161182
round_count = get_round_count_from_metadata(item)
162183
models = get_models_from_metadata(item)
184+
readme_first_line = get_readme_first_line(item)
163185
game_folders.add(current_relative)
164186
all_folders.append(
165187
{
166188
"name": current_relative,
167189
"full_path": str(item),
168190
"round_count": round_count,
169191
"models": models,
192+
"readme_first_line": readme_first_line,
170193
"is_game": True,
171194
"depth": depth,
172195
"parent": relative_path if relative_path else None,
@@ -180,6 +203,7 @@ def scan_directory(directory: Path, relative_path: str = ""):
180203
"full_path": str(item),
181204
"round_count": None,
182205
"models": [],
206+
"readme_first_line": "",
183207
"is_game": False,
184208
"depth": depth,
185209
"parent": relative_path if relative_path else None,

codeclash/viewer/static/css/picker.css

Lines changed: 140 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
/* Game Picker Styles */
22

33
.picker-container {
4-
max-width: 1400px;
5-
margin: 2rem auto;
4+
max-width: none;
5+
width: 100%;
6+
margin: 2rem 0;
67
padding: 0 1rem;
78
}
89

@@ -51,20 +52,74 @@
5152
overflow: hidden;
5253
}
5354

55+
.table-controls {
56+
background-color: var(--bg-tertiary);
57+
padding: 1rem;
58+
border-bottom: 1px solid var(--border-color);
59+
display: flex;
60+
justify-content: space-between;
61+
align-items: center;
62+
gap: 1rem;
63+
}
64+
65+
.selection-controls {
66+
display: flex;
67+
align-items: center;
68+
gap: 0.5rem;
69+
}
70+
71+
.selection-controls input[type="checkbox"] {
72+
margin: 0;
73+
}
74+
75+
.selection-controls label {
76+
font-weight: 500;
77+
color: var(--text-primary);
78+
}
79+
80+
.action-controls select {
81+
padding: 0.5rem 1rem 0.5rem 0.8rem;
82+
border: 2px solid var(--border-color);
83+
border-radius: 0.5rem;
84+
background-color: var(--bg-secondary);
85+
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6,9 12,15 18,9'%3e%3c/polyline%3e%3c/svg%3e");
86+
background-repeat: no-repeat;
87+
background-position: right 0.7rem center;
88+
background-size: 1rem;
89+
appearance: none;
90+
color: var(--text-primary);
91+
font-size: 0.9rem;
92+
font-weight: 500;
93+
cursor: pointer;
94+
transition: all 0.2s ease;
95+
min-width: 160px;
96+
}
97+
98+
.action-controls select:hover {
99+
border-color: var(--accent-color);
100+
background-color: var(--bg-primary);
101+
}
102+
103+
.action-controls select:focus {
104+
outline: none;
105+
border-color: var(--accent-color);
106+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
107+
}
108+
54109
.table-header {
55110
background-color: var(--bg-tertiary);
56111
padding: 1rem;
57112
border-bottom: 1px solid var(--border-color);
58113
display: grid;
59-
grid-template-columns: 1fr 100px 180px 140px;
114+
grid-template-columns: 60px 2.5fr 3fr 100px 3fr 140px;
60115
gap: 1rem;
61116
font-weight: 600;
62117
color: var(--text-primary);
63118
}
64119

65120
.game-row {
66121
display: grid;
67-
grid-template-columns: 1fr 100px 180px 140px;
122+
grid-template-columns: 60px 2.5fr 3fr 100px 3fr 140px;
68123
gap: 1rem;
69124
padding: 0.75rem 1rem;
70125
border-bottom: 1px solid var(--border-color);
@@ -110,6 +165,65 @@
110165
font-style: italic;
111166
}
112167

168+
.checkbox-cell {
169+
display: flex;
170+
align-items: center;
171+
justify-content: center;
172+
width: 60px;
173+
user-select: none;
174+
padding: 8px;
175+
}
176+
177+
.checkbox-cell:hover {
178+
background-color: var(--bg-primary);
179+
border-radius: 0.25rem;
180+
}
181+
182+
.checkbox-placeholder {
183+
width: 16px;
184+
height: 16px;
185+
}
186+
187+
.game-checkbox {
188+
width: 20px;
189+
height: 20px;
190+
cursor: pointer;
191+
accent-color: var(--accent-color);
192+
transform: scale(1.5);
193+
transition: all 0.2s ease;
194+
}
195+
196+
.game-checkbox:hover {
197+
transform: scale(1.6);
198+
}
199+
200+
.game-checkbox:focus {
201+
outline: 2px solid var(--accent-color);
202+
outline-offset: 2px;
203+
border-radius: 2px;
204+
}
205+
206+
.readme-cell {
207+
display: flex;
208+
align-items: center;
209+
min-width: 0;
210+
padding: 0.25rem;
211+
}
212+
213+
.readme-text {
214+
font-size: 0.8rem;
215+
color: var(--text-primary);
216+
word-wrap: break-word;
217+
overflow-wrap: break-word;
218+
line-height: 1.3;
219+
}
220+
221+
.readme-empty {
222+
color: var(--text-muted);
223+
font-size: 0.8rem;
224+
font-style: italic;
225+
}
226+
113227
.game-name-cell {
114228
display: flex;
115229
align-items: center;
@@ -170,9 +284,9 @@
170284
align-items: center;
171285
justify-content: flex-start;
172286
gap: 0.25rem;
173-
width: 180px;
174287
flex-wrap: wrap;
175288
padding: 0.25rem;
289+
min-width: 0;
176290
}
177291

178292
.model-tag {
@@ -183,9 +297,6 @@
183297
font-size: 0.7rem;
184298
font-weight: 500;
185299
white-space: nowrap;
186-
max-width: 120px;
187-
overflow: hidden;
188-
text-overflow: ellipsis;
189300
flex-shrink: 0;
190301
}
191302

@@ -196,10 +307,10 @@
196307
}
197308

198309
.action-cell {
199-
width: 140px;
200310
display: flex;
201311
justify-content: center;
202312
gap: 0.5rem;
313+
min-width: 140px;
203314
}
204315

205316
.open-button {
@@ -264,29 +375,38 @@
264375
padding: 0 0.5rem;
265376
}
266377

378+
.table-controls {
379+
flex-direction: column;
380+
align-items: flex-start;
381+
gap: 0.5rem;
382+
}
383+
267384
.table-header,
268385
.game-row {
269-
grid-template-columns: 1fr 80px 120px;
386+
grid-template-columns: 40px 1fr 80px;
270387
gap: 0.5rem;
271388
}
272389

273-
.rounds-cell {
274-
width: 80px;
390+
.checkbox-cell {
391+
width: 40px;
275392
}
276393

277-
.models-cell {
278-
width: 120px;
279-
justify-content: flex-start;
394+
.game-checkbox {
395+
transform: scale(1.3);
280396
}
281397

398+
.game-checkbox:hover {
399+
transform: scale(1.4);
400+
}
401+
402+
.readme-cell,
403+
.models-cell,
282404
.action-cell {
283-
display: none; /* Hide action buttons on mobile, click row instead */
405+
display: none; /* Hide on mobile to save space */
284406
}
285407

286-
.model-tag {
287-
font-size: 0.6rem;
288-
padding: 0.1rem 0.25rem;
289-
max-width: 80px;
408+
.rounds-cell {
409+
width: 80px;
290410
}
291411

292412
.game-name {

0 commit comments

Comments
 (0)