@galda/cli 0.10.48 → 0.10.49
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 +30 -11
- package/engine/server.mjs +25 -7
- package/package.json +1 -1
package/app/index.html
CHANGED
|
@@ -1329,13 +1329,17 @@
|
|
|
1329
1329
|
color:var(--ink2);font:inherit;font-size:12.5px;white-space:nowrap;overflow:hidden;cursor:pointer;text-align:left;width:100%;position:relative}
|
|
1330
1330
|
#fsRoot .fsproj:hover{background:var(--hover-s)}
|
|
1331
1331
|
#fsRoot .fsproj.on{background:var(--hover);color:var(--ink)}
|
|
1332
|
-
#fsRoot .fsproj i{width:4px;height:4px;border-radius:50%;background:var(--ink3);flex:0 0 4px}
|
|
1332
|
+
#fsRoot .fsproj i{width:4px;height:4px;border-radius:50%;background:var(--ink3);flex:0 0 4px;position:relative;top:1.5px} /* top:1.5px = optical nudge so the dot/running-ring centre sits on the name's x-height, not the line-box centre (Masa 2026-07-24, rail=D). Same shift for idle dot + running ring keeps the column aligned. */
|
|
1333
1333
|
#fsRoot .fsproj.on i{background:var(--green)}
|
|
1334
1334
|
/* running project = solid saturated green dot with a breathing glow — never dims to
|
|
1335
1335
|
grey, independent of selection (HANDOFF-v45 §11, flagship .proj.run port) */
|
|
1336
|
-
|
|
1336
|
+
/* running project = a spinning broken green ring (matches the Doing marker · flagship #316).
|
|
1337
|
+
Was a pulsing dot (fsrundot). Centre WITHOUT transform (spin's rotate would override a centring
|
|
1338
|
+
translate → drift bottom-right) via top/left:50% + negative-half margin; concentric with the 4px dot. */
|
|
1339
|
+
#fsRoot .fsproj.run i{background:none;box-shadow:none;position:relative}
|
|
1340
|
+
#fsRoot .fsproj.run i::after{content:"";position:absolute;top:50%;left:50%;width:7px;height:7px;margin:-3.5px 0 0 -3.5px;box-sizing:border-box;border-radius:50%;border:1.3px solid #22c55e;border-top-color:transparent;animation:spin 1.8s linear infinite}
|
|
1337
1341
|
@keyframes fsrundot{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--green) 50%,transparent)}50%{box-shadow:0 0 5px 1.5px color-mix(in srgb,var(--green) 35%,transparent)}}
|
|
1338
|
-
@media (prefers-reduced-motion:reduce){#fsRoot .fsproj.run i{animation:none}}
|
|
1342
|
+
@media (prefers-reduced-motion:reduce){#fsRoot .fsproj.run i::after{animation:none}}
|
|
1339
1343
|
#fsRoot .fsproj .pname{min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
1340
1344
|
#fsRoot .fsproj .num{margin-left:auto;font-size:10px;color:var(--ink3);flex:0 0 auto}
|
|
1341
1345
|
/* per-project counts (HANDOFF-v45 §10): To Do = rounded-square icon, Review = Scan
|
|
@@ -2276,10 +2280,13 @@
|
|
|
2276
2280
|
#fsRoot .trow .tt{color:color-mix(in srgb,var(--ink) 84%,var(--panel))} /* §13.2 soften To Do row name (not pure black); left chat feed keeps --ink */
|
|
2277
2281
|
#fsRoot .trow:hover{background:var(--hover-s)}
|
|
2278
2282
|
#fsRoot .trow.now{font-weight:500}
|
|
2279
|
-
#fsRoot .trow .st{width:13px;height:13px;border-radius:50%;border:1.5px solid var(--hair2);flex:0 0 13px}
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
#fsRoot .trow .st.run
|
|
2283
|
+
#fsRoot .trow .st{width:13px;height:13px;border-radius:50%;border:1.5px solid var(--hair2);flex:0 0 13px;position:relative;top:1px} /* top:1px = optical nudge so the whole status column (To Do/Doing/Failed/Done) centres on the row text, not the line-box (Masa 2026-07-24, board=C) */
|
|
2284
|
+
/* Doing marker = a broken green ring that spins (pattern B · flagship #316). Running only —
|
|
2285
|
+
.bad (failed) / .int (paused) stay static: no dead motion (DESIGN-RULES §5.5). Reuses @keyframes spin. */
|
|
2286
|
+
#fsRoot .trow .st.run{border-color:var(--green);border-top-color:transparent;background:none;box-shadow:0 0 6px color-mix(in srgb,var(--green) 40%,transparent);animation:spin 1.8s linear infinite}
|
|
2287
|
+
#fsRoot .trow .st.run.bad{border-color:var(--danger);background:radial-gradient(circle,rgba(var(--danger-rgb),.42),transparent 70%);animation:none;box-shadow:none}
|
|
2288
|
+
#fsRoot .trow .st.run.int{border-color:var(--amber);background:radial-gradient(circle,rgba(var(--amber-rgb),.4),transparent 70%);animation:none;box-shadow:none}
|
|
2289
|
+
@media (prefers-reduced-motion:reduce){#fsRoot .trow .st.run{animation:none}}
|
|
2283
2290
|
/* Doing-failure state word (HANDOFF-v45 §11): colour TEXT only, no badge/pill surface —
|
|
2284
2291
|
Failed = red, Interrupted = amber. */
|
|
2285
2292
|
#fsRoot .trow .stword{font-size:10.5px;font-weight:600;color:var(--danger);flex:0 0 auto}
|
|
@@ -2380,7 +2387,7 @@
|
|
|
2380
2387
|
/* GOTCHA (HANDOFF-v45 §11): the rule above wins over a plain .fsproj.run i by
|
|
2381
2388
|
specificity and greys the running dot out on the Gradient wallpaper rail — this
|
|
2382
2389
|
exception restores solid green so "running" never dims to grey. */
|
|
2383
|
-
body[data-theme="gradient"] #fsRoot .fsproj.run i{background
|
|
2390
|
+
body[data-theme="gradient"] #fsRoot .fsproj.run i{background:none}
|
|
2384
2391
|
body[data-theme="gradient"] #fsRoot .fsproj .num,body[data-theme="gradient"] #fsRoot .fsproj .pcount{color:rgba(20,23,31,.6)}
|
|
2385
2392
|
body[data-theme="gradient"] #fsRoot .fsproj:not(.on):hover{background:rgba(255,255,255,.34)}
|
|
2386
2393
|
body[data-theme="gradient"] #fsRoot .fsproj.on{background:rgba(255,255,255,.5);color:var(--ink)}
|
|
@@ -2517,7 +2524,7 @@
|
|
|
2517
2524
|
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj{color:rgba(255,255,255,.85);text-shadow:0 1px 3px rgba(0,0,0,.35)}
|
|
2518
2525
|
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj i{background:rgba(255,255,255,.6)}
|
|
2519
2526
|
/* same GOTCHA exception as the light Gradient rail, dark variant (HANDOFF-v45 §11) */
|
|
2520
|
-
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj.run i{background
|
|
2527
|
+
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj.run i{background:none}
|
|
2521
2528
|
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj .num,body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj .pcount{color:rgba(255,255,255,.6)}
|
|
2522
2529
|
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj.on{background:rgba(255,255,255,.15);color:#fff}
|
|
2523
2530
|
body[data-theme="gradient"][data-arcdark="1"] #fsRoot .fsproj:not(.on):hover{background:rgba(255,255,255,.1)}
|
|
@@ -3323,7 +3330,7 @@ const savedTheme = (() => {
|
|
|
3323
3330
|
if (v !== s) localStorage.setItem('theme', v);
|
|
3324
3331
|
return v;
|
|
3325
3332
|
})();
|
|
3326
|
-
const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], expanded: new Set(), openThreads: new Set(), queueOrder: {}, externalActivity: [], selectedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
|
|
3333
|
+
const state = { repos: null, repoHome: '', /* GET /api/repos — the folders a project can point at (Context Bar) */ projects: [], goals: [], tasks: [], act: {}, actAt: {}, todos: {}, chatTurns: {}, active: localStorage.getItem('sel:project') || 'default', editing: null, attach: [], connectCommand: '', models: ['sonnet'], agentModels: null, agentEfforts: null, agents: ['claude-code', 'codex'], expanded: new Set(), openThreads: new Set(), queueOrder: {}, externalActivity: [], selectedGoal: null, workflowColumns: {}, wfDraft: null, reviewDefinitions: {}, rdDraft: null, connectedApp: localStorage.getItem('sel:agent') || 'claude-code', reviewOpenGoal: null, reviewApprovals: {}, goalDetailOpen: null, qreplyOpen: new Set(), logCollapsed: localStorage.getItem('logCollapsed') !== '0', logEntries: [], layout: localStorage.getItem('layout') || 'flagship', theme: savedTheme, boardskin: localStorage.getItem('boardskin') || 'auto', summaryPattern: Number(localStorage.getItem('summaryPattern')) || 3, peraSpot: 0, skill: null, skillsCache: null, projectRules: {}, rulesOpen: false,
|
|
3327
3334
|
// per-message send overrides set via slash-commands (reset after each send,
|
|
3328
3335
|
// except model which is sticky in localStorage). palette = open command list.
|
|
3329
3336
|
msg: { mode: null, model: null, effort: null, skill: null, later: false, review: false },
|
|
@@ -6142,6 +6149,13 @@ function render(){
|
|
|
6142
6149
|
if (typeof SA !== 'undefined' && SA.open) saSync(); // keep the open See all Ledger live
|
|
6143
6150
|
const bv = $('botver'); if (bv && state.uiVersion) bv.textContent = 'build ' + String(state.uiVersion).slice(0, 8);
|
|
6144
6151
|
paintReplyNote(); // a reply's answer/question survives the re-render it caused
|
|
6152
|
+
// Persist the selected project so a reload (a real UI ship, or any reconnect)
|
|
6153
|
+
// restores the user's view instead of snapping back to 'default' (Masa
|
|
6154
|
+
// 2026-07-24 "勝手にデフォルトのプロジェクトに戻される"). Only store an id that
|
|
6155
|
+
// currently exists as a project, so onboarding/transient sentinels
|
|
6156
|
+
// ('__onboard') are never saved and the restore always lands on a real one;
|
|
6157
|
+
// an id that later disappears self-heals via the guards in refresh()/'projects'.
|
|
6158
|
+
if (state.projects.some((p) => p.id === state.active)) localStorage.setItem('sel:project', state.active);
|
|
6145
6159
|
}
|
|
6146
6160
|
|
|
6147
6161
|
// ============================================================================
|
|
@@ -6188,7 +6202,7 @@ function renderFsRail(){
|
|
|
6188
6202
|
const activeProject = state.projects.find((p) => p.id === state.active);
|
|
6189
6203
|
const activeLabel = $('fsProjBtnName');
|
|
6190
6204
|
if (activeLabel) activeLabel.textContent = activeProject?.name ?? 'Projects';
|
|
6191
|
-
|
|
6205
|
+
const html = state.projects.map((p) => {
|
|
6192
6206
|
const ptasks = state.tasks.filter((t) => t.projectId === p.id);
|
|
6193
6207
|
const pgoals = state.goals.filter((g) => g.projectId === p.id);
|
|
6194
6208
|
// running dot (HANDOFF-v45 §11): a REAL running task, independent of selection —
|
|
@@ -6202,6 +6216,11 @@ function renderFsRail(){
|
|
|
6202
6216
|
return `<button class="fsproj${p.id === state.active ? ' on' : ''}${running ? ' run' : ''}" data-fsproj="${p.id}"><i></i><span class="pname">${esc(p.name)}</span>${pcount}</button>`;
|
|
6203
6217
|
}).join('')
|
|
6204
6218
|
+ `<button class="fsprojadd" id="fsProjAdd" title="New project"><span class="pa-plus"></span><span class="pa-label">New project</span></button>`;
|
|
6219
|
+
// Skip the DOM swap when the rail is unchanged: re-creating .fsproj.run would restart the
|
|
6220
|
+
// spinning ring's CSS animation — a visible "jump" on every SSE-driven re-render (Masa 2026-07-24).
|
|
6221
|
+
if (html === renderFsRail._html && el.childElementCount) return;
|
|
6222
|
+
renderFsRail._html = html;
|
|
6223
|
+
el.innerHTML = html;
|
|
6205
6224
|
for (const b of el.querySelectorAll('[data-fsproj]')) b.onclick = (e) => {
|
|
6206
6225
|
if (e.target.closest('.pname.editing') || b.querySelector('.pname.editing')) return;
|
|
6207
6226
|
state.active = b.dataset.fsproj; document.body.classList.remove('projectsopen'); render();
|
package/engine/server.mjs
CHANGED
|
@@ -728,13 +728,22 @@ if (process.env.MANAGER_SEED_RUNNING_FIXTURE === '1') {
|
|
|
728
728
|
// res -> identity (see identityFor), captured at connection time so every
|
|
729
729
|
// SSE push can be scoped per-connection without re-deriving auth per event.
|
|
730
730
|
const sseClients = new Map();
|
|
731
|
-
// UI hot-reload: open tabs compare this stamp (index.html mtime
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
|
|
731
|
+
// UI hot-reload: open tabs compare this stamp (index.html mtime only) via
|
|
732
|
+
// /api/state and the 30s SSE ping, and reload themselves when a new UI ships
|
|
733
|
+
// — no manual reload after a hot-copy.
|
|
734
|
+
//
|
|
735
|
+
// It used to also fold in a per-process BOOT_STAMP (Date.now() at boot) so a
|
|
736
|
+
// bare restart would force a reload too. That backfired on the always-on
|
|
737
|
+
// resident: every relay-supervisor respawn / hot-copy restart, and every
|
|
738
|
+
// reconnect that landed on a *different* engine process (each with its own
|
|
739
|
+
// BOOT_STAMP), changed uiVersion and yanked a `location.reload()` on the 30s
|
|
740
|
+
// ping — the "reloads every 30-60s and resets to the default project" report
|
|
741
|
+
// (Masa 2026-07-24). mtime alone means a reload fires ONLY when app/index.html
|
|
742
|
+
// actually changes, and every process serving the same file agrees on the
|
|
743
|
+
// stamp, so restarts and multi-process relay churn no longer reload the tab.
|
|
735
744
|
function uiVersion() {
|
|
736
|
-
try { return
|
|
737
|
-
catch { return
|
|
745
|
+
try { return String(Math.round(statSync(join(ROOT, 'app', 'index.html')).mtimeMs)); }
|
|
746
|
+
catch { return '0'; }
|
|
738
747
|
}
|
|
739
748
|
|
|
740
749
|
// Broadcast to every connected client, unscoped — only for events that carry
|
|
@@ -4330,7 +4339,16 @@ const server = createServer(async (req, res) => {
|
|
|
4330
4339
|
try {
|
|
4331
4340
|
const { text } = JSON.parse(body);
|
|
4332
4341
|
if (!text?.trim()) return json(res, 400, { error: 'text required' });
|
|
4333
|
-
|
|
4342
|
+
// Reply picks a parked goal back up into the work queue. 'review' MUST be
|
|
4343
|
+
// here: a completed goal awaiting your approve/reject is 'review', and a
|
|
4344
|
+
// reply to it means "not done — keep working" (Masa 2026-07-24: replied to
|
|
4345
|
+
// an in-review card and it sat stuck in Review with no worker activity).
|
|
4346
|
+
// Without this flip the reply task was queued but the goal never left the
|
|
4347
|
+
// Review lane, so nothing looked like it was happening. Un-parking mirrors
|
|
4348
|
+
// the already-working 'blocked'/'failed'/etc. "Needs you" replies: the goal
|
|
4349
|
+
// flips to running and, since it still carries startedAt from its first run,
|
|
4350
|
+
// shows in Doing as "Working on it" — then the worker resumes its session.
|
|
4351
|
+
if (['review', 'partial', 'failed', 'interrupted', 'blocked'].includes(goal.status)) {
|
|
4334
4352
|
goal.status = 'running';
|
|
4335
4353
|
goal.blocked = null;
|
|
4336
4354
|
saveGoal(goal);
|
package/package.json
CHANGED