claudeck 1.4.0 → 1.4.1
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 +6 -6
- package/package.json +1 -1
- package/plugins/claude-editor/manifest.json +10 -0
- package/plugins/linear/manifest.json +10 -0
- package/plugins/repos/manifest.json +10 -0
- package/public/css/ui/right-panel.css +207 -0
- package/public/css/ui/settings.css +75 -0
- package/public/index.html +7 -0
- package/public/js/components/settings-modal.js +65 -0
- package/public/js/core/events.js +11 -0
- package/public/js/core/plugin-loader.js +96 -11
- package/public/js/core/store.js +11 -0
- package/public/js/main.js +1 -0
- package/public/js/panels/assistant-bot.js +16 -0
- package/public/js/panels/dev-docs.js +2 -2
- package/public/js/panels/memory.js +1 -0
- package/public/js/ui/context-gauge.js +10 -1
- package/public/js/ui/header-dropdowns.js +30 -0
- package/public/js/ui/input-meta.js +13 -6
- package/public/js/ui/max-turns.js +6 -3
- package/public/js/ui/model-selector.js +1 -0
- package/public/js/ui/permissions.js +1 -0
- package/public/js/ui/tab-sdk.js +395 -176
- package/public/style.css +1 -0
- package/server/memory-optimizer.js +17 -13
- package/server/routes/marketplace.js +316 -0
- package/server/ws-handler.js +22 -15
- package/server.js +18 -0
- package/plugins/event-stream/client.css +0 -207
- package/plugins/event-stream/client.js +0 -271
- package/plugins/sudoku/client.css +0 -196
- package/plugins/sudoku/client.js +0 -329
- package/plugins/tasks/client.css +0 -414
- package/plugins/tasks/client.js +0 -394
- package/plugins/tasks/server.js +0 -116
- package/plugins/tic-tac-toe/client.css +0 -167
- package/plugins/tic-tac-toe/client.js +0 -241
package/plugins/sudoku/client.js
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
// Sudoku — Tab SDK plugin
|
|
2
|
-
// A mini sudoku game to play while waiting for tasks to finish
|
|
3
|
-
import { registerTab } from '/js/ui/tab-sdk.js';
|
|
4
|
-
|
|
5
|
-
registerTab({
|
|
6
|
-
id: 'sudoku',
|
|
7
|
-
title: 'Sudoku',
|
|
8
|
-
icon: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/></svg>',
|
|
9
|
-
lazy: true,
|
|
10
|
-
|
|
11
|
-
init(ctx) {
|
|
12
|
-
const root = document.createElement('div');
|
|
13
|
-
root.className = 'sudoku-tab';
|
|
14
|
-
root.style.cssText = 'display:flex;flex-direction:column;flex:1;overflow:hidden;';
|
|
15
|
-
|
|
16
|
-
// ── State ──
|
|
17
|
-
let board = []; // current board (0 = empty)
|
|
18
|
-
let solution = []; // full solution
|
|
19
|
-
let fixed = []; // true if cell was part of the puzzle
|
|
20
|
-
let selectedCell = null;
|
|
21
|
-
let errors = new Set();
|
|
22
|
-
let timerInterval = null;
|
|
23
|
-
let seconds = 0;
|
|
24
|
-
let gameWon = false;
|
|
25
|
-
|
|
26
|
-
// ── Sudoku Generator ──
|
|
27
|
-
function shuffle(arr) {
|
|
28
|
-
for (let i = arr.length - 1; i > 0; i--) {
|
|
29
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
30
|
-
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
31
|
-
}
|
|
32
|
-
return arr;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isValid(grid, row, col, num) {
|
|
36
|
-
for (let i = 0; i < 9; i++) {
|
|
37
|
-
if (grid[row][i] === num || grid[i][col] === num) return false;
|
|
38
|
-
}
|
|
39
|
-
const br = Math.floor(row / 3) * 3, bc = Math.floor(col / 3) * 3;
|
|
40
|
-
for (let r = br; r < br + 3; r++)
|
|
41
|
-
for (let c = bc; c < bc + 3; c++)
|
|
42
|
-
if (grid[r][c] === num) return false;
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function solve(grid) {
|
|
47
|
-
for (let r = 0; r < 9; r++) {
|
|
48
|
-
for (let c = 0; c < 9; c++) {
|
|
49
|
-
if (grid[r][c] === 0) {
|
|
50
|
-
const nums = shuffle([1,2,3,4,5,6,7,8,9]);
|
|
51
|
-
for (const n of nums) {
|
|
52
|
-
if (isValid(grid, r, c, n)) {
|
|
53
|
-
grid[r][c] = n;
|
|
54
|
-
if (solve(grid)) return true;
|
|
55
|
-
grid[r][c] = 0;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function generatePuzzle(clues = 36) {
|
|
66
|
-
const grid = Array.from({length: 9}, () => Array(9).fill(0));
|
|
67
|
-
solve(grid);
|
|
68
|
-
solution = grid.map(r => [...r]);
|
|
69
|
-
|
|
70
|
-
board = grid.map(r => [...r]);
|
|
71
|
-
fixed = Array.from({length: 9}, () => Array(9).fill(true));
|
|
72
|
-
|
|
73
|
-
const cells = shuffle([...Array(81).keys()]);
|
|
74
|
-
const toRemove = 81 - clues;
|
|
75
|
-
for (let i = 0; i < toRemove; i++) {
|
|
76
|
-
const r = Math.floor(cells[i] / 9), c = cells[i] % 9;
|
|
77
|
-
board[r][c] = 0;
|
|
78
|
-
fixed[r][c] = false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── Timer ──
|
|
83
|
-
function startTimer() {
|
|
84
|
-
stopTimer();
|
|
85
|
-
seconds = 0;
|
|
86
|
-
gameWon = false;
|
|
87
|
-
timerInterval = setInterval(() => {
|
|
88
|
-
if (!gameWon) {
|
|
89
|
-
seconds++;
|
|
90
|
-
const el = root.querySelector('.sudoku-timer');
|
|
91
|
-
if (el) el.textContent = formatTime(seconds);
|
|
92
|
-
}
|
|
93
|
-
}, 1000);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function stopTimer() {
|
|
97
|
-
if (timerInterval) { clearInterval(timerInterval); timerInterval = null; }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function formatTime(s) {
|
|
101
|
-
const m = Math.floor(s / 60);
|
|
102
|
-
return `${m}:${String(s % 60).padStart(2, '0')}`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Render ──
|
|
106
|
-
function render() {
|
|
107
|
-
const gridEl = root.querySelector('.sudoku-grid');
|
|
108
|
-
if (!gridEl) return;
|
|
109
|
-
gridEl.innerHTML = '';
|
|
110
|
-
|
|
111
|
-
for (let r = 0; r < 9; r++) {
|
|
112
|
-
for (let c = 0; c < 9; c++) {
|
|
113
|
-
const cell = document.createElement('div');
|
|
114
|
-
cell.className = 'sudoku-cell';
|
|
115
|
-
cell.dataset.row = r;
|
|
116
|
-
cell.dataset.col = c;
|
|
117
|
-
|
|
118
|
-
if (fixed[r][c]) cell.classList.add('fixed');
|
|
119
|
-
if (selectedCell && selectedCell[0] === r && selectedCell[1] === c) cell.classList.add('selected');
|
|
120
|
-
if (errors.has(`${r},${c}`)) cell.classList.add('error');
|
|
121
|
-
|
|
122
|
-
// Highlight same number
|
|
123
|
-
if (selectedCell && board[r][c] !== 0 && board[selectedCell[0]][selectedCell[1]] === board[r][c]) {
|
|
124
|
-
cell.classList.add('highlight');
|
|
125
|
-
}
|
|
126
|
-
// Highlight same row/col/box
|
|
127
|
-
if (selectedCell) {
|
|
128
|
-
const [sr, sc] = selectedCell;
|
|
129
|
-
const sameRow = r === sr;
|
|
130
|
-
const sameCol = c === sc;
|
|
131
|
-
const sameBox = Math.floor(r/3) === Math.floor(sr/3) && Math.floor(c/3) === Math.floor(sc/3);
|
|
132
|
-
if (sameRow || sameCol || sameBox) cell.classList.add('in-scope');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Box borders
|
|
136
|
-
if (c % 3 === 0 && c !== 0) cell.classList.add('box-left');
|
|
137
|
-
if (r % 3 === 0 && r !== 0) cell.classList.add('box-top');
|
|
138
|
-
|
|
139
|
-
cell.textContent = board[r][c] || '';
|
|
140
|
-
gridEl.appendChild(cell);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Update remaining count
|
|
145
|
-
const remaining = board.flat().filter(v => v === 0).length;
|
|
146
|
-
const remEl = root.querySelector('.sudoku-remaining');
|
|
147
|
-
if (remEl) remEl.textContent = `${remaining} empty`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function checkErrors() {
|
|
151
|
-
errors.clear();
|
|
152
|
-
for (let r = 0; r < 9; r++) {
|
|
153
|
-
for (let c = 0; c < 9; c++) {
|
|
154
|
-
if (board[r][c] === 0) continue;
|
|
155
|
-
const val = board[r][c];
|
|
156
|
-
// Check row
|
|
157
|
-
for (let i = 0; i < 9; i++) {
|
|
158
|
-
if (i !== c && board[r][i] === val) { errors.add(`${r},${c}`); errors.add(`${r},${i}`); }
|
|
159
|
-
}
|
|
160
|
-
// Check col
|
|
161
|
-
for (let i = 0; i < 9; i++) {
|
|
162
|
-
if (i !== r && board[i][c] === val) { errors.add(`${r},${c}`); errors.add(`${i},${c}`); }
|
|
163
|
-
}
|
|
164
|
-
// Check box
|
|
165
|
-
const br = Math.floor(r/3)*3, bc = Math.floor(c/3)*3;
|
|
166
|
-
for (let rr = br; rr < br+3; rr++)
|
|
167
|
-
for (let cc = bc; cc < bc+3; cc++)
|
|
168
|
-
if ((rr !== r || cc !== c) && board[rr][cc] === val) { errors.add(`${r},${c}`); errors.add(`${rr},${cc}`); }
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function checkWin() {
|
|
174
|
-
if (board.flat().some(v => v === 0)) return false;
|
|
175
|
-
if (errors.size > 0) return false;
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function newGame(difficulty) {
|
|
180
|
-
const clues = difficulty === 'easy' ? 42 : difficulty === 'medium' ? 33 : 26;
|
|
181
|
-
generatePuzzle(clues);
|
|
182
|
-
selectedCell = null;
|
|
183
|
-
errors.clear();
|
|
184
|
-
gameWon = false;
|
|
185
|
-
startTimer();
|
|
186
|
-
render();
|
|
187
|
-
const msg = root.querySelector('.sudoku-message');
|
|
188
|
-
if (msg) msg.textContent = '';
|
|
189
|
-
// Update active difficulty button
|
|
190
|
-
root.querySelectorAll('.sudoku-diff-btn').forEach(b => {
|
|
191
|
-
b.classList.toggle('active', b.dataset.diff === difficulty);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── Build DOM ──
|
|
196
|
-
root.innerHTML = `
|
|
197
|
-
<div class="sudoku-header">
|
|
198
|
-
<div class="sudoku-controls">
|
|
199
|
-
<button class="sudoku-diff-btn active" data-diff="easy">Easy</button>
|
|
200
|
-
<button class="sudoku-diff-btn" data-diff="medium">Medium</button>
|
|
201
|
-
<button class="sudoku-diff-btn" data-diff="hard">Hard</button>
|
|
202
|
-
</div>
|
|
203
|
-
<div class="sudoku-info">
|
|
204
|
-
<span class="sudoku-timer">0:00</span>
|
|
205
|
-
<span class="sudoku-remaining">0 empty</span>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
<div class="sudoku-board-wrap">
|
|
209
|
-
<div class="sudoku-grid"></div>
|
|
210
|
-
</div>
|
|
211
|
-
<div class="sudoku-numpad"></div>
|
|
212
|
-
<div class="sudoku-actions">
|
|
213
|
-
<button class="sudoku-action-btn" data-action="hint">Hint</button>
|
|
214
|
-
<button class="sudoku-action-btn" data-action="erase">Erase</button>
|
|
215
|
-
<button class="sudoku-action-btn" data-action="check">Check</button>
|
|
216
|
-
<button class="sudoku-action-btn" data-action="new">New Game</button>
|
|
217
|
-
</div>
|
|
218
|
-
<div class="sudoku-message"></div>
|
|
219
|
-
`;
|
|
220
|
-
|
|
221
|
-
// Number pad
|
|
222
|
-
const numpad = root.querySelector('.sudoku-numpad');
|
|
223
|
-
for (let n = 1; n <= 9; n++) {
|
|
224
|
-
const btn = document.createElement('button');
|
|
225
|
-
btn.className = 'sudoku-num-btn';
|
|
226
|
-
btn.textContent = n;
|
|
227
|
-
btn.addEventListener('click', () => placeNumber(n));
|
|
228
|
-
numpad.appendChild(btn);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function placeNumber(n) {
|
|
232
|
-
if (!selectedCell || gameWon) return;
|
|
233
|
-
const [r, c] = selectedCell;
|
|
234
|
-
if (fixed[r][c]) return;
|
|
235
|
-
board[r][c] = n;
|
|
236
|
-
checkErrors();
|
|
237
|
-
render();
|
|
238
|
-
if (checkWin()) {
|
|
239
|
-
gameWon = true;
|
|
240
|
-
stopTimer();
|
|
241
|
-
const msg = root.querySelector('.sudoku-message');
|
|
242
|
-
if (msg) msg.textContent = `Solved in ${formatTime(seconds)}!`;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// ── Grid click ──
|
|
247
|
-
root.querySelector('.sudoku-grid').addEventListener('click', (e) => {
|
|
248
|
-
const cell = e.target.closest('.sudoku-cell');
|
|
249
|
-
if (!cell || gameWon) return;
|
|
250
|
-
selectedCell = [+cell.dataset.row, +cell.dataset.col];
|
|
251
|
-
render();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// ── Action buttons ──
|
|
255
|
-
root.querySelector('.sudoku-actions').addEventListener('click', (e) => {
|
|
256
|
-
const btn = e.target.closest('.sudoku-action-btn');
|
|
257
|
-
if (!btn) return;
|
|
258
|
-
const action = btn.dataset.action;
|
|
259
|
-
|
|
260
|
-
if (action === 'new') {
|
|
261
|
-
const activeDiff = root.querySelector('.sudoku-diff-btn.active');
|
|
262
|
-
newGame(activeDiff ? activeDiff.dataset.diff : 'easy');
|
|
263
|
-
} else if (action === 'erase') {
|
|
264
|
-
if (!selectedCell) return;
|
|
265
|
-
const [r, c] = selectedCell;
|
|
266
|
-
if (!fixed[r][c]) { board[r][c] = 0; checkErrors(); render(); }
|
|
267
|
-
} else if (action === 'hint') {
|
|
268
|
-
if (!selectedCell || gameWon) return;
|
|
269
|
-
const [r, c] = selectedCell;
|
|
270
|
-
if (fixed[r][c] || board[r][c] === solution[r][c]) return;
|
|
271
|
-
board[r][c] = solution[r][c];
|
|
272
|
-
fixed[r][c] = true;
|
|
273
|
-
checkErrors();
|
|
274
|
-
render();
|
|
275
|
-
if (checkWin()) {
|
|
276
|
-
gameWon = true;
|
|
277
|
-
stopTimer();
|
|
278
|
-
const msg = root.querySelector('.sudoku-message');
|
|
279
|
-
if (msg) msg.textContent = `Solved in ${formatTime(seconds)}!`;
|
|
280
|
-
}
|
|
281
|
-
} else if (action === 'check') {
|
|
282
|
-
checkErrors();
|
|
283
|
-
render();
|
|
284
|
-
const msg = root.querySelector('.sudoku-message');
|
|
285
|
-
if (msg) msg.textContent = errors.size === 0 ? 'No errors found!' : `${errors.size} conflicting cells`;
|
|
286
|
-
setTimeout(() => { if (msg) msg.textContent = ''; }, 2000);
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// ── Difficulty buttons ──
|
|
291
|
-
root.querySelector('.sudoku-controls').addEventListener('click', (e) => {
|
|
292
|
-
const btn = e.target.closest('.sudoku-diff-btn');
|
|
293
|
-
if (!btn) return;
|
|
294
|
-
newGame(btn.dataset.diff);
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
// ── Keyboard input ──
|
|
298
|
-
root.addEventListener('keydown', (e) => {
|
|
299
|
-
if (!selectedCell || gameWon) return;
|
|
300
|
-
const [r, c] = selectedCell;
|
|
301
|
-
|
|
302
|
-
if (e.key >= '1' && e.key <= '9') {
|
|
303
|
-
placeNumber(+e.key);
|
|
304
|
-
} else if (e.key === 'Backspace' || e.key === 'Delete' || e.key === '0') {
|
|
305
|
-
if (!fixed[r][c]) { board[r][c] = 0; checkErrors(); render(); }
|
|
306
|
-
} else if (e.key === 'ArrowUp' && r > 0) { selectedCell = [r-1, c]; render(); }
|
|
307
|
-
else if (e.key === 'ArrowDown' && r < 8) { selectedCell = [r+1, c]; render(); }
|
|
308
|
-
else if (e.key === 'ArrowLeft' && c > 0) { selectedCell = [r, c-1]; render(); }
|
|
309
|
-
else if (e.key === 'ArrowRight' && c < 8) { selectedCell = [r, c+1]; render(); }
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Make root focusable for keyboard events
|
|
313
|
-
root.tabIndex = 0;
|
|
314
|
-
|
|
315
|
-
// Start first game
|
|
316
|
-
newGame('easy');
|
|
317
|
-
|
|
318
|
-
return root;
|
|
319
|
-
},
|
|
320
|
-
|
|
321
|
-
onActivate() {
|
|
322
|
-
const root = document.querySelector('.sudoku-tab');
|
|
323
|
-
if (root) root.focus();
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
onDestroy() {
|
|
327
|
-
// Timer cleanup handled by closure
|
|
328
|
-
},
|
|
329
|
-
});
|