@galda/cli 0.10.76 → 0.10.78
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/app/index.html +22 -2
- package/engine/lib.mjs +1 -1
- package/engine/server.mjs +16 -0
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -733,6 +733,12 @@
|
|
|
733
733
|
@keyframes saFinishDraw{to{stroke-dashoffset:0}}
|
|
734
734
|
@keyframes saFinishFade{0%,72%{opacity:1}100%{opacity:0}}
|
|
735
735
|
@media (prefers-reduced-motion:reduce){.sa-finish,.sa-finish-mark,.sa-finish-mark path{animation:none}.sa-finish{background:transparent}}
|
|
736
|
+
/* A PR-less approval is just as complete as a merged PR. Keep this transient
|
|
737
|
+
celebration fixed above the board so it never changes document height or
|
|
738
|
+
scroll position. */
|
|
739
|
+
.approve-finish{position:fixed;inset:0;z-index:10000;display:grid;place-items:center;pointer-events:none;
|
|
740
|
+
background:rgba(0,0,0,.28);backdrop-filter:blur(2px);animation:saFinishFade .9s ease both}
|
|
741
|
+
.approve-finish .sa-finish-mark{width:112px;height:112px}
|
|
736
742
|
|
|
737
743
|
/* connect modal */
|
|
738
744
|
/* A confirm dialog (deleteConfirmOverlay etc.) must always sit above other
|
|
@@ -5594,6 +5600,20 @@ function finishOpenReview(goal){
|
|
|
5594
5600
|
const delay = reduce ? 350 : 950;
|
|
5595
5601
|
setTimeout(() => { layer.remove(); saClose(); render(); }, delay);
|
|
5596
5602
|
}
|
|
5603
|
+
// PR-less review approval: use the same high-signal green completion mark as
|
|
5604
|
+
// the merged-PR path, but do not close or reflow the review surface. The canvas
|
|
5605
|
+
// burst starts at the actual Approve control, making the reward feel causal.
|
|
5606
|
+
function celebrateDirectApprove(cx, cy){
|
|
5607
|
+
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
5608
|
+
if (!reduce) confettiBurst(cx, cy);
|
|
5609
|
+
const layer = document.createElement('div');
|
|
5610
|
+
layer.className = 'approve-finish';
|
|
5611
|
+
layer.setAttribute('role', 'status');
|
|
5612
|
+
layer.setAttribute('aria-label', 'Approved');
|
|
5613
|
+
layer.innerHTML = '<div class="sa-finish-mark"><svg viewBox="0 0 64 64" aria-hidden="true"><path d="M17 33l10 10 21-23"/></svg></div>';
|
|
5614
|
+
document.body.appendChild(layer);
|
|
5615
|
+
setTimeout(() => layer.remove(), reduce ? 350 : 950);
|
|
5616
|
+
}
|
|
5597
5617
|
async function setRequirementStatus(goalId, taskId, status){
|
|
5598
5618
|
const approvals = (state.reviewApprovals[goalId] ??= {});
|
|
5599
5619
|
approvals[taskId] = approvals[taskId] === status ? undefined : status; // click again to undo
|
|
@@ -10415,7 +10435,7 @@ $('seeall').addEventListener('click', async (e) => {
|
|
|
10415
10435
|
// instead of celebrating/advancing; saCall already upserted the still-'review' goal.
|
|
10416
10436
|
const g = state.goals.find((x) => x.id === it.goalId);
|
|
10417
10437
|
if (g && g.status === 'review' && g.pr) { confirmMerge(g); return; }
|
|
10418
|
-
|
|
10438
|
+
celebrateDirectApprove(bx.left + bx.width / 2, bx.top + bx.height / 2); saAdvanceFocus(it.goalId); }); }
|
|
10419
10439
|
if (k === 'dis') return saAct(it, 2, 'reject').then((ok) => { if (ok) saAdvanceFocus(it.goalId); }); // 07-20c: Reject → Rejected column
|
|
10420
10440
|
if (k === 'arch') return saAct(it, 4, 'archive').then((ok) => { if (ok) saAdvanceFocus(it.goalId); });
|
|
10421
10441
|
if (k === 'rev') return saAct(it, 5, 'revert').then((ok) => { if (ok) saAdvanceFocus(it.goalId); });
|
|
@@ -10478,7 +10498,7 @@ document.addEventListener('keydown', (e) => {
|
|
|
10478
10498
|
if (g && g.status === 'review' && g.pr) { confirmMerge(g); return; } // 07-20c: PR goal → merge dialog
|
|
10479
10499
|
const el = $('seeall').querySelector(`.sa-item[data-gid="${gid}"] .sa-app`);
|
|
10480
10500
|
const box = el?.getBoundingClientRect();
|
|
10481
|
-
|
|
10501
|
+
celebrateDirectApprove(box ? box.left + box.width / 2 : innerWidth / 2, box ? box.top + box.height / 2 : innerHeight / 3);
|
|
10482
10502
|
saAdvanceFocus(gid);
|
|
10483
10503
|
});
|
|
10484
10504
|
} else saAct(it, 2, 'reject').then((ok) => { if (ok) saAdvanceFocus(gid); }); // 07-20c: D = Reject
|
package/engine/lib.mjs
CHANGED
|
@@ -2843,7 +2843,7 @@ export function replayQueueLog(rawText, reviewDefinitions = {}) {
|
|
|
2843
2843
|
try {
|
|
2844
2844
|
const e = JSON.parse(line);
|
|
2845
2845
|
if (e.kind === 'goal') { goals = goals.filter((g) => g.id !== e.id); if (!e.deleted) goals.push(e); }
|
|
2846
|
-
else { tasks = tasks.filter((t) => t.id !== e.id); tasks.push(e); }
|
|
2846
|
+
else if (e.kind === 'task') { tasks = tasks.filter((t) => t.id !== e.id); if (!e.deleted) tasks.push(e); }
|
|
2847
2847
|
} catch { /* skip a corrupt line rather than lose the whole log */ }
|
|
2848
2848
|
}
|
|
2849
2849
|
for (const t of tasks) {
|
package/engine/server.mjs
CHANGED
|
@@ -2589,6 +2589,12 @@ function installPreviewReaper() {
|
|
|
2589
2589
|
async function startPreview(task) {
|
|
2590
2590
|
installPreviewReaper();
|
|
2591
2591
|
const { cmd, url, dir } = task.run;
|
|
2592
|
+
// Attached static artifacts are already served by this Manager at /upload.
|
|
2593
|
+
// They never need a child process; treating them like a Manager app made
|
|
2594
|
+
// "Open it" fail even though the HTML artifact was ready to view.
|
|
2595
|
+
if (!cmd && typeof url === 'string' && url.startsWith('/upload/')) {
|
|
2596
|
+
return { url, reused: false, started: false };
|
|
2597
|
+
}
|
|
2592
2598
|
const live = previews.get(task.id);
|
|
2593
2599
|
if (live && live.proc.exitCode === null) {
|
|
2594
2600
|
live.expires = Date.now() + PREVIEW_IDLE_MS;
|
|
@@ -4159,6 +4165,16 @@ const server = createServer(async (req, res) => {
|
|
|
4159
4165
|
|| tasks.some((t) => t.projectId === project.id && ['running', 'queued'].includes(t.status));
|
|
4160
4166
|
if (active) return json(res, 409, { error: 'cannot delete a project with active work' });
|
|
4161
4167
|
const idx = projects.findIndex((p) => p.id === project.id);
|
|
4168
|
+
// A project id can be reused after deletion (for example "New project").
|
|
4169
|
+
// Remove its complete history before freeing that id, otherwise its old
|
|
4170
|
+
// Review cards appear in the newly created project.
|
|
4171
|
+
const removedGoals = goals.filter((g) => g.projectId === project.id);
|
|
4172
|
+
const removedGoalIds = new Set(removedGoals.map((g) => g.id));
|
|
4173
|
+
const removedTasks = tasks.filter((t) => t.projectId === project.id || removedGoalIds.has(t.goalId));
|
|
4174
|
+
goals = goals.filter((g) => g.projectId !== project.id);
|
|
4175
|
+
tasks = tasks.filter((t) => t.projectId !== project.id && !removedGoalIds.has(t.goalId));
|
|
4176
|
+
for (const g of removedGoals) logAppend(JSON.stringify({ kind: 'goal', id: g.id, deleted: true }));
|
|
4177
|
+
for (const t of removedTasks) logAppend(JSON.stringify({ kind: 'task', id: t.id, deleted: true }));
|
|
4162
4178
|
projects.splice(idx, 1);
|
|
4163
4179
|
queues.delete(project.id);
|
|
4164
4180
|
saveProjects();
|
package/package.json
CHANGED