myrlin-workbook 0.9.26 → 0.9.28
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/logs/server.pid +1 -1
- package/package.json +6 -2
- package/scripts/build-lucide-bundle.mjs +244 -0
- package/scripts/build-material-bundle.mjs +108 -0
- package/src/core/td-adapter.js +2 -1
- package/src/state/store.js +2 -1
- package/src/web/public/app.js +398 -42
- package/src/web/public/index.html +8 -0
- package/src/web/public/styles.css +208 -16
- package/src/web/public/terminal.js +5 -1
- package/src/web/public/vendor/lucide.bundle.js +7 -0
- package/src/web/public/vendor/material-icons.bundle.js +8 -0
- package/src/web/server.js +180 -1
package/logs/server.pid
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
55976
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myrlin-workbook",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.28",
|
|
4
4
|
"description": "Browser-based project manager for Claude Code sessions - session discovery, multi-terminal, cost tracking, docs, and kanban board",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"mcp:visual-qa": "node src/mcp/visual-qa.js",
|
|
21
21
|
"gui:cdp": "node src/supervisor.js --cdp",
|
|
22
22
|
"postinstall": "node scripts/postinstall.js",
|
|
23
|
-
"restart": "bash scripts/restart-gui.sh"
|
|
23
|
+
"restart": "bash scripts/restart-gui.sh",
|
|
24
|
+
"build:icons": "node scripts/build-lucide-bundle.mjs && node scripts/build-material-bundle.mjs"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
|
@@ -46,17 +47,20 @@
|
|
|
46
47
|
"author": "Arthur",
|
|
47
48
|
"license": "AGPL-3.0-only",
|
|
48
49
|
"dependencies": {
|
|
50
|
+
"@lucide/lab": "^0.1.2",
|
|
49
51
|
"blessed": "^0.1.81",
|
|
50
52
|
"blessed-contrib": "^4.11.0",
|
|
51
53
|
"chalk": "^5.6.2",
|
|
52
54
|
"chrome-remote-interface": "^0.34.0",
|
|
53
55
|
"express": "^5.2.1",
|
|
56
|
+
"lucide": "^1.8.0",
|
|
54
57
|
"node-pty": "^1.1.0",
|
|
55
58
|
"qrcode": "^1.5.4",
|
|
56
59
|
"simple-git": "^3.33.0",
|
|
57
60
|
"ws": "^8.19.0"
|
|
58
61
|
},
|
|
59
62
|
"devDependencies": {
|
|
63
|
+
"@material-icons/svg": "^1.0.33",
|
|
60
64
|
"@playwright/test": "^1.58.2",
|
|
61
65
|
"@xterm/addon-fit": "^0.11.0",
|
|
62
66
|
"@xterm/addon-web-links": "^0.12.0",
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build script: generates public/vendor/lucide.bundle.js
|
|
4
|
+
* Imports curated icons from `lucide` and `@lucide/lab`, converts to SVG strings,
|
|
5
|
+
* and outputs a plain JS bundle that sets window.__lucideIcons.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, resolve } from 'path';
|
|
11
|
+
import vm from 'vm';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const ROOT = resolve(__dirname, '..');
|
|
15
|
+
const OUT = resolve(ROOT, 'src/web/public/vendor/lucide.bundle.js');
|
|
16
|
+
|
|
17
|
+
// ── Curated icon list ────────────────────────────────────────────────────────
|
|
18
|
+
// Format: { category: [[kebab-name, 'lucide' | 'lab'], ...], ... }
|
|
19
|
+
const ICON_CATEGORIES = {
|
|
20
|
+
'Folders & Files': [
|
|
21
|
+
['folder', 'lucide'],
|
|
22
|
+
['folder-open', 'lucide'],
|
|
23
|
+
['folder-code', 'lucide'],
|
|
24
|
+
['folder-git-2', 'lucide'],
|
|
25
|
+
['folder-sync', 'lucide'],
|
|
26
|
+
['folder-key', 'lucide'],
|
|
27
|
+
['folder-search', 'lucide'],
|
|
28
|
+
['folders', 'lucide'],
|
|
29
|
+
['file', 'lucide'],
|
|
30
|
+
['file-code', 'lucide'],
|
|
31
|
+
['file-text', 'lucide'],
|
|
32
|
+
['files', 'lucide'],
|
|
33
|
+
['notebook', 'lucide'],
|
|
34
|
+
['notebook-pen', 'lucide'],
|
|
35
|
+
],
|
|
36
|
+
'Code & Dev': [
|
|
37
|
+
['code', 'lucide'],
|
|
38
|
+
['code-xml', 'lucide'],
|
|
39
|
+
['square-terminal', 'lucide'],
|
|
40
|
+
['terminal', 'lucide'],
|
|
41
|
+
['git-branch', 'lucide'],
|
|
42
|
+
['git-merge', 'lucide'],
|
|
43
|
+
['git-commit-vertical', 'lucide'],
|
|
44
|
+
['git-fork', 'lucide'],
|
|
45
|
+
['braces', 'lucide'],
|
|
46
|
+
['brackets', 'lucide'],
|
|
47
|
+
['hash', 'lucide'],
|
|
48
|
+
['variable', 'lucide'],
|
|
49
|
+
],
|
|
50
|
+
'Infrastructure': [
|
|
51
|
+
['database', 'lucide'],
|
|
52
|
+
['database-zap', 'lucide'],
|
|
53
|
+
['server', 'lucide'],
|
|
54
|
+
['server-cog', 'lucide'],
|
|
55
|
+
['cpu', 'lucide'],
|
|
56
|
+
['hard-drive', 'lucide'],
|
|
57
|
+
['network', 'lucide'],
|
|
58
|
+
['wifi', 'lucide'],
|
|
59
|
+
['cloud', 'lucide'],
|
|
60
|
+
['cloud-upload', 'lucide'],
|
|
61
|
+
['globe', 'lucide'],
|
|
62
|
+
['layers', 'lucide'],
|
|
63
|
+
],
|
|
64
|
+
'Apps & Tools': [
|
|
65
|
+
['box', 'lucide'],
|
|
66
|
+
['package', 'lucide'],
|
|
67
|
+
['package-2', 'lucide'],
|
|
68
|
+
['workflow', 'lucide'],
|
|
69
|
+
['settings', 'lucide'],
|
|
70
|
+
['settings-2', 'lucide'],
|
|
71
|
+
['wrench', 'lucide'],
|
|
72
|
+
['hammer', 'lucide'],
|
|
73
|
+
['cog', 'lucide'],
|
|
74
|
+
['tool-case', 'lucide'],
|
|
75
|
+
['puzzle', 'lucide'],
|
|
76
|
+
['blocks', 'lucide'],
|
|
77
|
+
['plug', 'lucide'],
|
|
78
|
+
['cpu', 'lucide'],
|
|
79
|
+
],
|
|
80
|
+
'Design & Art': [
|
|
81
|
+
['palette', 'lucide'],
|
|
82
|
+
['brush', 'lucide'],
|
|
83
|
+
['paintbrush', 'lucide'],
|
|
84
|
+
['wand', 'lucide'],
|
|
85
|
+
['wand-sparkles', 'lucide'],
|
|
86
|
+
['sparkles', 'lucide'],
|
|
87
|
+
['pen-tool', 'lucide'],
|
|
88
|
+
['pencil', 'lucide'],
|
|
89
|
+
['scissors', 'lucide'],
|
|
90
|
+
['crop', 'lucide'],
|
|
91
|
+
['image', 'lucide'],
|
|
92
|
+
['camera', 'lucide'],
|
|
93
|
+
],
|
|
94
|
+
'Learning & Science': [
|
|
95
|
+
['book-open', 'lucide'],
|
|
96
|
+
['book', 'lucide'],
|
|
97
|
+
['scroll', 'lucide'],
|
|
98
|
+
['flask-conical', 'lucide'],
|
|
99
|
+
['flask-round', 'lucide'],
|
|
100
|
+
['microscope', 'lucide'],
|
|
101
|
+
['test-tube', 'lucide'],
|
|
102
|
+
['brain', 'lucide'],
|
|
103
|
+
['brain-circuit', 'lucide'],
|
|
104
|
+
['atom', 'lucide'],
|
|
105
|
+
['dna', 'lucide'],
|
|
106
|
+
['telescope', 'lucide'],
|
|
107
|
+
],
|
|
108
|
+
'People & Status': [
|
|
109
|
+
['user', 'lucide'],
|
|
110
|
+
['users', 'lucide'],
|
|
111
|
+
['user-round', 'lucide'],
|
|
112
|
+
['bot', 'lucide'],
|
|
113
|
+
['rocket', 'lucide'],
|
|
114
|
+
['star', 'lucide'],
|
|
115
|
+
['heart', 'lucide'],
|
|
116
|
+
['zap', 'lucide'],
|
|
117
|
+
['shield', 'lucide'],
|
|
118
|
+
['lock', 'lucide'],
|
|
119
|
+
['key', 'lucide'],
|
|
120
|
+
['flag', 'lucide'],
|
|
121
|
+
['trophy', 'lucide'],
|
|
122
|
+
['medal', 'lucide'],
|
|
123
|
+
['crown', 'lucide'],
|
|
124
|
+
],
|
|
125
|
+
'Home & Work': [
|
|
126
|
+
['house', 'lucide'],
|
|
127
|
+
['briefcase', 'lucide'],
|
|
128
|
+
['building', 'lucide'],
|
|
129
|
+
['building-2', 'lucide'],
|
|
130
|
+
['store', 'lucide'],
|
|
131
|
+
['landmark', 'lucide'],
|
|
132
|
+
['map-pin', 'lucide'],
|
|
133
|
+
['compass', 'lucide'],
|
|
134
|
+
['map', 'lucide'],
|
|
135
|
+
['globe-2', 'lucide'],
|
|
136
|
+
['mountain', 'lucide'],
|
|
137
|
+
],
|
|
138
|
+
'Media & Comms': [
|
|
139
|
+
['monitor', 'lucide'],
|
|
140
|
+
['laptop', 'lucide'],
|
|
141
|
+
['smartphone', 'lucide'],
|
|
142
|
+
['mail', 'lucide'],
|
|
143
|
+
['message-circle', 'lucide'],
|
|
144
|
+
['bell', 'lucide'],
|
|
145
|
+
['phone', 'lucide'],
|
|
146
|
+
['mic', 'lucide'],
|
|
147
|
+
['music', 'lucide'],
|
|
148
|
+
['headphones', 'lucide'],
|
|
149
|
+
['video', 'lucide'],
|
|
150
|
+
['radio', 'lucide'],
|
|
151
|
+
],
|
|
152
|
+
'Nature & Fun': [
|
|
153
|
+
['sun', 'lucide'],
|
|
154
|
+
['moon', 'lucide'],
|
|
155
|
+
['cloud-sun', 'lucide'],
|
|
156
|
+
['flame', 'lucide'],
|
|
157
|
+
['waves', 'lucide'],
|
|
158
|
+
['coffee', 'lucide'],
|
|
159
|
+
['leaf', 'lucide'],
|
|
160
|
+
['flower', 'lucide'],
|
|
161
|
+
['flower-2', 'lucide'],
|
|
162
|
+
['tree-pine', 'lucide'],
|
|
163
|
+
['trees', 'lucide'],
|
|
164
|
+
['snail', 'lucide'],
|
|
165
|
+
['fish', 'lucide'],
|
|
166
|
+
['rabbit', 'lucide'],
|
|
167
|
+
['turtle', 'lucide'],
|
|
168
|
+
['bird', 'lucide'],
|
|
169
|
+
['dog', 'lucide'],
|
|
170
|
+
['cat', 'lucide'],
|
|
171
|
+
],
|
|
172
|
+
'Lab Extras': [
|
|
173
|
+
['owl', 'lab'],
|
|
174
|
+
['planet', 'lab'],
|
|
175
|
+
['bee', 'lab'],
|
|
176
|
+
['venn', 'lab'],
|
|
177
|
+
['copy-code', 'lab'],
|
|
178
|
+
['grid-lines', 'lab'],
|
|
179
|
+
['farm', 'lab'],
|
|
180
|
+
['toolbox', 'lab'],
|
|
181
|
+
['toolbox-2', 'lab'],
|
|
182
|
+
['snowman', 'lab'],
|
|
183
|
+
['mountain-snow', 'lab'],
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// ── SVG serializer ────────────────────────────────────────────────────────────
|
|
188
|
+
function iconDataToSvg(iconData) {
|
|
189
|
+
const children = iconData.map(([tag, attrs]) => {
|
|
190
|
+
// Filter out non-SVG metadata attrs ('key' is a React/lucide internal)
|
|
191
|
+
const entries = Object.entries(attrs).filter(([k]) => k !== 'key');
|
|
192
|
+
const attrStr = entries.map(([k, v]) => `${k}="${v}"`).join(' ');
|
|
193
|
+
return `<${tag} ${attrStr}/>`;
|
|
194
|
+
}).join('');
|
|
195
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${children}</svg>`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
199
|
+
const icons = {}; // { "folder": "<svg>...</svg>", ... }
|
|
200
|
+
const categories = {}; // { "Folders & Files": ["folder", "folder-open", ...], ... }
|
|
201
|
+
const skipped = [];
|
|
202
|
+
|
|
203
|
+
for (const [category, entries] of Object.entries(ICON_CATEGORIES)) {
|
|
204
|
+
const catIcons = [];
|
|
205
|
+
for (const [name, pkg] of entries) {
|
|
206
|
+
// De-dup (e.g. 'cpu' appears twice in the list above)
|
|
207
|
+
if (icons[name]) { catIcons.push(name); continue; }
|
|
208
|
+
|
|
209
|
+
const iconDir = pkg === 'lab'
|
|
210
|
+
? resolve(ROOT, `node_modules/@lucide/lab/dist/esm/icons/${name}.js`)
|
|
211
|
+
: resolve(ROOT, `node_modules/lucide/dist/esm/icons/${name}.js`);
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const src = readFileSync(iconDir, 'utf8');
|
|
215
|
+
// Extract: const <Name> = <array>;\nexport { <Name> as default }
|
|
216
|
+
// Use greedy match up to the last ";" before the export statement
|
|
217
|
+
const match = src.match(/const \w+ = ([\s\S]+?);\s*\nexport/);
|
|
218
|
+
if (!match) { skipped.push(`${name} (parse fail)`); continue; }
|
|
219
|
+
// Evaluate JS object literal (has unquoted keys, not valid JSON)
|
|
220
|
+
const iconData = vm.runInNewContext(`(${match[1]})`);
|
|
221
|
+
icons[name] = iconDataToSvg(iconData);
|
|
222
|
+
catIcons.push(name);
|
|
223
|
+
} catch {
|
|
224
|
+
skipped.push(`${name} (${pkg})`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (catIcons.length > 0) categories[category] = catIcons;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Output ────────────────────────────────────────────────────────────────────
|
|
231
|
+
mkdirSync(resolve(ROOT, 'src/web/public/vendor'), { recursive: true });
|
|
232
|
+
|
|
233
|
+
const output = `/* lucide icons bundle — generated by scripts/build-lucide-bundle.mjs */
|
|
234
|
+
/* Includes ${Object.keys(icons).length} icons from lucide + @lucide/lab */
|
|
235
|
+
(function() {
|
|
236
|
+
window.__lucideIcons = ${JSON.stringify(icons)};
|
|
237
|
+
window.__lucideIconCategories = ${JSON.stringify(categories)};
|
|
238
|
+
document.dispatchEvent(new Event('lucide-ready'));
|
|
239
|
+
})();
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
writeFileSync(OUT, output);
|
|
243
|
+
console.log(`✓ Built ${Object.keys(icons).length} icons → src/web/public/vendor/lucide.bundle.js`);
|
|
244
|
+
if (skipped.length) console.log(` Skipped (not found): ${skipped.join(', ')}`);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build script: generates public/vendor/material-icons.bundle.js
|
|
4
|
+
* Includes ALL ~2191 icons from @material-icons/svg (baseline/filled variant),
|
|
5
|
+
* organized by their official categories from data.json.
|
|
6
|
+
*
|
|
7
|
+
* Icon names are stored with a "mi/" prefix when used in ws.icon so they
|
|
8
|
+
* can coexist with Lucide icon names without collision.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { dirname, resolve } from 'path';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const ROOT = resolve(__dirname, '..');
|
|
17
|
+
const ICONS_DIR = resolve(ROOT, 'node_modules/@material-icons/svg/svg');
|
|
18
|
+
const DATA_JSON = resolve(ROOT, 'node_modules/@material-icons/svg/data.json');
|
|
19
|
+
const OUT = resolve(ROOT, 'src/web/public/vendor/material-icons.bundle.js');
|
|
20
|
+
|
|
21
|
+
// Official category short-name → display name
|
|
22
|
+
const CATEGORY_LABELS = {
|
|
23
|
+
action: 'Action',
|
|
24
|
+
alert: 'Alert',
|
|
25
|
+
av: 'Audio & Video',
|
|
26
|
+
communication:'Communication',
|
|
27
|
+
content: 'Content',
|
|
28
|
+
device: 'Device',
|
|
29
|
+
editor: 'Editor',
|
|
30
|
+
file: 'File',
|
|
31
|
+
hardware: 'Hardware',
|
|
32
|
+
home: 'Home',
|
|
33
|
+
image: 'Image',
|
|
34
|
+
maps: 'Maps',
|
|
35
|
+
math: 'Math',
|
|
36
|
+
navigation: 'Navigation',
|
|
37
|
+
notification: 'Notification',
|
|
38
|
+
places: 'Places',
|
|
39
|
+
search: 'Search',
|
|
40
|
+
shopping: 'Shopping',
|
|
41
|
+
social: 'Social',
|
|
42
|
+
toggle: 'Toggle',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ── SVG processor ─────────────────────────────────────────────────────────────
|
|
46
|
+
function processSvg(raw) {
|
|
47
|
+
return raw
|
|
48
|
+
.replace(/width="24"/, 'width="14"')
|
|
49
|
+
.replace(/height="24"/, 'height="14"')
|
|
50
|
+
// Make color inherit from CSS (without this, icons always render black)
|
|
51
|
+
.replace('<svg ', '<svg fill="currentColor" ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Load icon metadata ────────────────────────────────────────────────────────
|
|
55
|
+
const data = JSON.parse(readFileSync(DATA_JSON, 'utf8'));
|
|
56
|
+
const iconMeta = data.icons; // [{ name, categories: [string], tags: [string] }]
|
|
57
|
+
|
|
58
|
+
// Build category → [name] map using official categories
|
|
59
|
+
const categoryMap = {}; // { 'Audio & Video': ['10k', ...], ... }
|
|
60
|
+
for (const icon of iconMeta) {
|
|
61
|
+
for (const cat of (icon.categories || [])) {
|
|
62
|
+
const label = CATEGORY_LABELS[cat] || cat;
|
|
63
|
+
if (!categoryMap[label]) categoryMap[label] = [];
|
|
64
|
+
categoryMap[label].push(icon.name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Process all icons ─────────────────────────────────────────────────────────
|
|
69
|
+
const icons = {}; // { name: '<svg>...' }
|
|
70
|
+
const categories = {}; // { 'Audio & Video': ['10k', ...] }
|
|
71
|
+
const skipped = [];
|
|
72
|
+
|
|
73
|
+
for (const icon of iconMeta) {
|
|
74
|
+
const svgPath = resolve(ICONS_DIR, icon.name, 'baseline.svg');
|
|
75
|
+
if (!existsSync(svgPath)) {
|
|
76
|
+
skipped.push(icon.name);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const raw = readFileSync(svgPath, 'utf8').trim();
|
|
81
|
+
icons[icon.name] = processSvg(raw);
|
|
82
|
+
} catch {
|
|
83
|
+
skipped.push(icon.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Build final categories (only include icons that were successfully loaded)
|
|
88
|
+
for (const [label, names] of Object.entries(categoryMap)) {
|
|
89
|
+
const valid = names.filter(n => icons[n]);
|
|
90
|
+
if (valid.length > 0) categories[label] = valid;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Output ────────────────────────────────────────────────────────────────────
|
|
94
|
+
mkdirSync(resolve(ROOT, 'src/web/public/vendor'), { recursive: true });
|
|
95
|
+
|
|
96
|
+
const output = `/* Material Icons bundle — generated by scripts/build-material-bundle.mjs */
|
|
97
|
+
/* Includes ${Object.keys(icons).length} icons from @material-icons/svg (baseline variant) */
|
|
98
|
+
/* Icon names are stored as "mi/<name>" in workspace records to avoid Lucide collisions */
|
|
99
|
+
(function() {
|
|
100
|
+
window.__materialIcons = ${JSON.stringify(icons)};
|
|
101
|
+
window.__materialIconCategories = ${JSON.stringify(categories)};
|
|
102
|
+
document.dispatchEvent(new Event('material-icons-ready'));
|
|
103
|
+
})();
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
writeFileSync(OUT, output);
|
|
107
|
+
console.log(`✓ Built ${Object.keys(icons).length} Material icons across ${Object.keys(categories).length} categories → src/web/public/vendor/material-icons.bundle.js`);
|
|
108
|
+
if (skipped.length) console.log(` Skipped (not found): ${skipped.join(', ')}`);
|
package/src/core/td-adapter.js
CHANGED
|
@@ -82,7 +82,8 @@ async function listIssues(cwd, filters = {}, binary = DEFAULT_TD_BINARY) {
|
|
|
82
82
|
if (filters.status) args.push('--status', filters.status);
|
|
83
83
|
const { stdout } = await runTd(binary, args, cwd);
|
|
84
84
|
const parsed = JSON.parse(stdout);
|
|
85
|
-
// td --json returns either an array
|
|
85
|
+
// td --json returns either an array, { issues: [...] }, or null (empty repo)
|
|
86
|
+
if (!parsed) return [];
|
|
86
87
|
return Array.isArray(parsed) ? parsed : (parsed.issues || []);
|
|
87
88
|
}
|
|
88
89
|
|
package/src/state/store.js
CHANGED
|
@@ -452,7 +452,7 @@ class Store extends EventEmitter {
|
|
|
452
452
|
|
|
453
453
|
// ─── Workspace CRUD ──────────────────────────────────────
|
|
454
454
|
|
|
455
|
-
createWorkspace({ name, description = '', color = 'cyan' }) {
|
|
455
|
+
createWorkspace({ name, description = '', color = 'cyan', icon = null }) {
|
|
456
456
|
const id = crypto.randomUUID();
|
|
457
457
|
const now = new Date().toISOString();
|
|
458
458
|
const workspace = {
|
|
@@ -460,6 +460,7 @@ class Store extends EventEmitter {
|
|
|
460
460
|
name,
|
|
461
461
|
description,
|
|
462
462
|
color,
|
|
463
|
+
icon,
|
|
463
464
|
sessions: [],
|
|
464
465
|
createdAt: now,
|
|
465
466
|
lastActive: now,
|