@galda/cli 0.10.112 → 0.10.113

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/CLAUDE.md CHANGED
@@ -57,6 +57,11 @@
57
57
  - **UI(app/index.html・app/theme.css 等)に触る前に、必ず `docs/design/DESIGN-RULES.md` を読むこと**。特に §1.5 レイアウトの憲法(10条)と §5.5 美学。条文に反する実装はせず、変えたい場合は理由を添えて仲田さんの決定を仰ぐ。
58
58
  - app/ のUIファイル編集時はhook(tools/design_rules_hook.sh)が自動でリマインドする。リマインドが出たら読んだ上で作業すること。
59
59
  - 実装時の詳細契約は `docs/design/CTO-HANDOFF.md`(CDO→CTO引き継ぎ書)に従う。**既存パーツで作れない新しい画面・コンポーネントが必要になったら、自作せず実装前にCDOへ依頼すること。**
60
+ - 🔴 **UIの文言は英語(Masa決定 2026-07-31)**。ボタン・ラベル・見出し・プレースホルダ・空状態・折りたたみの開閉ラベル・トースト/エラーなど**製品が書く文字は全て英語**。日本語のUI文字列を新しく足さない。
61
+ - 境界は**「誰の言葉か」**。製品が書くもの=英語。**ユーザーやworkerの発言をそのまま出している所(依頼文・返信・報告・質問の原文)は原文のまま**=翻訳も言語強制もしない。
62
+ - **コードのコメントは対象外**(日本語のままでよい)。
63
+ - 🟡 **例外=日英の出し分け(`failureLang`/`prefersJapaneseText`/`FAILURE_COPY`/`BILLING_COPY`/`AUTH_COPY`/`summarizeAttention`)は現状維持**(Masa決定 2026-07-31)。**依頼文の言語に追従するのは仕様**。「UIは英語」を根拠にこれらの `ja` を削除しない。英語化するのは**無条件に日本語が出る固定ラベル**だけ。
64
+ - 全文=`docs/design/DESIGN-RULES.md` §5.5「文章・コンテンツの品」。
60
65
 
61
66
  ## プロダクトの不変条件(実装前に読む・2026-07-30 追加)
62
67
 
package/app/index.html CHANGED
@@ -2325,7 +2325,7 @@
2325
2325
  .redesign .gh-del .ghm{color:#f85149}
2326
2326
  .redesign .gh-hi{border-radius:2px;padding:0 1px}
2327
2327
  .redesign .gh-add .gh-hi{background:rgba(46,204,113,.55)} .redesign .gh-del .gh-hi{background:rgba(248,81,73,.5)}
2328
- /* huge-diff footer (spec §7): "…+N 行 · GitHub で開く ↗" */
2328
+ /* huge-diff footer (spec §7): "…+N 行 · Open on GitHub ↗" */
2329
2329
  .redesign .ghfoot{margin-top:8px;font-family:var(--mono);font-size:11.5px;color:var(--ink3);display:flex;gap:2px;align-items:center;flex-wrap:wrap}
2330
2330
  .redesign .ghfoot .ghopen{color:var(--ink2);text-decoration:none}
2331
2331
  .redesign .ghfoot .ghopen:hover{color:var(--blue)}
@@ -2378,6 +2378,10 @@
2378
2378
  .redesign .txr .w{flex:0 0 46px;font-size:11px;color:var(--ink3)}
2379
2379
  .redesign .txr.you .w,.redesign .txr.cl .w{color:var(--ink);font-weight:600}
2380
2380
  .redesign .txr .m{flex:1;min-width:0;color:var(--ink2)}
2381
+ /* The existing left-hand Activity / Conversation disclosure owns the full text.
2382
+ When it is closed, its preview is capped at three rendered lines; opening the
2383
+ same › control reveals the unchanged transcript. */
2384
+ .redesign .lacc:not([open]) .lfirst .m{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden}
2381
2385
  .redesign .txr.cl .m{color:var(--ink)}
2382
2386
  .redesign .txr.tool .m{color:var(--ink2);font-family:var(--mono);font-size:12px}
2383
2387
  .redesign .txr.tool .m b{color:var(--ink);font-weight:600}
@@ -3051,7 +3055,7 @@
3051
3055
  <div class="spacer"></div>
3052
3056
  <!-- Feedback 7: layout switcher — flip the arrangement live. Increment 1:
3053
3057
  現行 / タスク中央 / 3列カンバン (CSS reflow). 中央フロー・1列 = increment 2. -->
3054
- <div class="laysel" id="laysel" title="レイアウトを切り替え">
3058
+ <div class="laysel" id="laysel" title="Switch layout">
3055
3059
  <button class="laybtn" data-layout="bflow">B・Center-flow</button>
3056
3060
  <button class="laybtn" data-layout="flagship">Flagship</button>
3057
3061
  <button class="laybtn" data-layout="current">現行</button>
@@ -3060,7 +3064,7 @@
3060
3064
  </div>
3061
3065
  <!-- theme switch — the 5 release themes, English registered names only (v3 §6.5;
3062
3066
  Glass archived 2026-07-07 · legacy "dark" no longer selectable). -->
3063
- <div class="laysel" id="themesel" title="テーマを切り替え">
3067
+ <div class="laysel" id="themesel" title="Switch theme">
3064
3068
  <button class="laybtn" data-theme="studio">Studio</button>
3065
3069
  <button class="laybtn" data-theme="gradient">Gradient</button>
3066
3070
  <button class="laybtn" data-theme="banff">Banff</button>
@@ -3348,12 +3352,12 @@
3348
3352
  <div class="overlay revfloat" id="summaryOverlay">
3349
3353
  <div class="modal peramodal">
3350
3354
  <div class="perahead">
3351
- <div class="peratitle">レビューサマリ<span class="peracount" id="peraCount">0</span></div>
3355
+ <div class="peratitle">Review summary<span class="peracount" id="peraCount">0</span></div>
3352
3356
  <div class="peratabs" id="peraTabs"></div>
3353
3357
  <button class="mbtn ghost" id="peraClose">Close</button>
3354
3358
  </div>
3355
3359
  <div class="peraask">
3356
- <input type="text" id="peraAsk" placeholder="質問する… 例: 今クリティカルなのは? / 失敗だけまとめて / 優先度順に">
3360
+ <input type="text" id="peraAsk" placeholder="Ask… e.g. What's critical now? / Just the failures / Sorted by priority">
3357
3361
  <button type="button" class="askbtn" id="peraAskBtn" aria-label="Ask"><svg viewBox="0 0 24 24"><path d="M12 19V5M5 12l7-7 7 7"/></svg></button>
3358
3362
  </div>
3359
3363
  <div class="peraanswer" id="peraAnswer" hidden></div>
@@ -3703,7 +3707,7 @@ const TASK_PRIORITIES = ['高', '中', '低'];
3703
3707
  function prioBadge(t){
3704
3708
  const p = TASK_PRIORITIES.includes(t.priority) ? t.priority : '中';
3705
3709
  return `<div class="priosel" data-priosel="${t.id}">
3706
- <button type="button" class="priobtn prio-${p}" data-priobtn="${t.id}" title="優先度を変更">${p}</button>
3710
+ <button type="button" class="priobtn prio-${p}" data-priobtn="${t.id}" title="Change priority">${p}</button>
3707
3711
  <div class="priomenu" data-priomenu="${t.id}">${TASK_PRIORITIES.map((x) =>
3708
3712
  `<button type="button" class="prioopt${x === p ? ' active' : ''}" data-priotask="${t.id}" data-val="${x}">${x}</button>`).join('')}</div>
3709
3713
  </div>`;
@@ -3721,7 +3725,7 @@ const DEFAULT_REVIEW_DEFINITION = {
3721
3725
  };
3722
3726
  // Shown (faint) under Review when no definition is set yet, so it's obvious the
3723
3727
  // line is editable and what it's for.
3724
- const REVIEW_DEF_HINT = '「英語でプルリクエストを作成して」など、ゴールの完了定義を設定できます';
3728
+ const REVIEW_DEF_HINT = 'e.g. "Write the pull request in English" — set what done means for a goal';
3725
3729
  function reviewDefinitionFor(projectId){ return state.reviewDefinitions[projectId] ?? DEFAULT_REVIEW_DEFINITION; }
3726
3730
  // A saved definition on disk may predate the reviewCard/language fields (old
3727
3731
  // review-definitions.json entries are never re-validated on server load), so
@@ -4233,7 +4237,8 @@ function saShotsListHtml(shots){
4233
4237
  if (!list.length) return '';
4234
4238
  if (list.length === 1) return saShotFigHtml(list[0]);
4235
4239
  const older = list.slice(0, -1);
4236
- return `${saShotFigHtml(list[list.length - 1])}<details class="rshotfold"><summary>ほかの${older.length}枚を表示</summary>${older.map(saShotFigHtml).join('')}</details>`;
4240
+ const label = `Show ${older.length} older screenshot${older.length === 1 ? '' : 's'}`;
4241
+ return `${saShotFigHtml(list[list.length - 1])}<details class="rshotfold"><summary>${label}</summary>${older.map(saShotFigHtml).join('')}</details>`;
4237
4242
  }
4238
4243
  function saPlainPairHtml(pair){
4239
4244
  const col = (sh, label) => `<div class="ba-col"><span class="balabel">${label}</span><img class="proofimg" src="${esc(sh.url)}" alt="${esc(label)}" loading="lazy"></div>`;
@@ -4296,7 +4301,7 @@ function reviewDigestHtml(rows){
4296
4301
  </div>
4297
4302
  </div>`;
4298
4303
  };
4299
- return `<div class="digestcard"><div class="digesthead">今のレビューまとめ<span class="digestcount">${rows.length}</span></div>
4304
+ return `<div class="digestcard"><div class="digesthead">Pending reviews<span class="digestcount">${rows.length}</span></div>
4300
4305
  <div class="digestrows">${rows.map(item).join('')}</div></div>`;
4301
4306
  }
4302
4307
 
@@ -4411,7 +4416,7 @@ function reviewActionsHtml(g){
4411
4416
  <div class="rvhead"><span class="dot"></span>レビュー — ${esc(sum.headline)} · ${esc(sum.detail)}</div>
4412
4417
  <div class="rvwhat">${esc(sum.what)}</div>
4413
4418
  <div class="rvitems">${items}</div>
4414
- <button type="button" class="rvopen reqopen" data-goal="${g.id}">レビューチェックリストを開く →</button>
4419
+ <button type="button" class="rvopen reqopen" data-goal="${g.id}">Open the review checklist →</button>
4415
4420
  </div>`;
4416
4421
  }
4417
4422
 
@@ -4432,7 +4437,7 @@ function goalRow(g){
4432
4437
  // shouldShowGoalSummary/GOAL_COMPLETE_STATUSES と揃える(そちらは純関数として
4433
4438
  // テスト済み — この行はブラウザ側の複製)。
4434
4439
  const summary = GOAL_COMPLETE_STATUSES.includes(g.status) && g.summary
4435
- ? `<div class="gsummary"><div class="gslabel"><span class="dot"></span>Manager結論サマリ</div>${esc(g.summary)}</div>` : '';
4440
+ ? `<div class="gsummary"><div class="gslabel"><span class="dot"></span>Manager summary</div>${esc(g.summary)}</div>` : '';
4436
4441
  const pr = g.pr ? `<a class="prlink" href="${esc(g.pr)}" target="_blank">${g.status === 'review' ? 'In review — open the Pull Request' : 'Open the Pull Request'}</a>`
4437
4442
  : g.prError ? (() => {
4438
4443
  const meta = (() => { const raw = g.prErrorClass || inferErrorClass(`PR step failed: ${g.prError}`, g.agent); const C = FAILURE_COPY[failureLang()]; return { ...raw, title: C.infra.title, body: C.infra[raw.reason] || C.infra.pr, copy: C }; })();
@@ -4464,7 +4469,7 @@ function goalRow(g){
4464
4469
  function extRow(c){
4465
4470
  const short = esc((c.hash ?? '').slice(0, 7));
4466
4471
  const when = c.date ? new Date(c.date).toLocaleString() : '';
4467
- return `<div class="extrow"><span class="exthash">${short}</span><span class="extsubj">${esc(c.subject ?? '')} · Claude Code CLIより</span>${when ? `<span class="extdate">${esc(when)}</span>` : ''}</div>`;
4472
+ return `<div class="extrow"><span class="exthash">${short}</span><span class="extsubj">${esc(c.subject ?? '')} · from Claude Code CLI</span>${when ? `<span class="extdate">${esc(when)}</span>` : ''}</div>`;
4468
4473
  }
4469
4474
 
4470
4475
  // Mirrors engine/lib.mjs mergeGoalTimeline() — kept in sync manually since
@@ -4753,7 +4758,7 @@ function renderTasks(){
4753
4758
  // half-finished item leaves Attention for good instead of piling up. With
4754
4759
  // no parent goal we fall back to skipping just the task.
4755
4760
  const clear = g
4756
- ? `<button class="retrybtn skip" data-archivegoal="${g.id}" title="このゴールを片付ける(再作業なし)">Archive</button>`
4761
+ ? `<button class="retrybtn skip" data-archivegoal="${g.id}" title="Retire this goal (no rework)">Archive</button>`
4757
4762
  : `<button class="retrybtn skip" data-skipbtn="${t.id}">Dismiss</button>`;
4758
4763
  cond = `${reason} <button class="retrybtn" data-retrybtn="${t.id}">Retry</button> ${clear}`;
4759
4764
  } else if (t.reply && g) {
@@ -4881,7 +4886,7 @@ function renderTasks(){
4881
4886
  const dAllClear = dGreen === digestCount && !dBlocked;
4882
4887
  // S5 summary (deterministic — no LLM). ⤢ opens the ペライチ dashboard above.
4883
4888
  const reviewDigest = digestCount
4884
- ? `<div class="s5sum" id="summaryEntry" title="Open the review summary (ペライチ)">
4889
+ ? `<div class="s5sum" id="summaryEntry" title="Open the review summary">
4885
4890
  <span class="s5exp"><svg width="13" height="13" viewBox="0 0 24 24"><path d="M15 4h5v5M20 4l-7 7M9 20H4v-5M4 20l7-7"/></svg></span>
4886
4891
  <span class="s5ring" style="background:conic-gradient(var(--green) 0 ${dPct}%, var(--hair2) ${dPct}% 100%)"><span class="s5in">${dGreen}/${digestCount}</span></span>
4887
4892
  <span class="s5tx"><span class="s5hl">${dAllClear ? 'Ready to review' : `${dGreen} of ${digestCount} ready`}</span><span class="s5sub">${dAllClear ? 'all green' : dGreen === digestCount ? 'blocked needs attention' : 'some failing'} · ${dBlocked ? `${dBlocked} blocked` : 'none blocked'}</span></span>
@@ -4905,7 +4910,7 @@ function renderTasks(){
4905
4910
  // (see saItemHtml's carriesBody) instead — the list row stays just the ask.
4906
4911
  return `<div class="task revrow" data-goal="${g.id}"><div class="ic pending"></div><div class="tx"><div class="name">${esc(checkLine)} ${goalSourceBadge(g.source)}</div>
4907
4912
  <div class="cond">${prChipHtml(g.pr, g.prClosed, 'prbig')}</div>
4908
- <div class="qreplyrow" data-qreplyrow="${g.id}"${state.qreplyOpen.has(g.id) ? '' : ' hidden'}><input type="text" data-qreplyinput="${g.id}" placeholder="直したい点を書いて送信(Todoに戻して再作業)"><button type="button" class="qreplysend" data-qreplysend="${g.id}" aria-label="Send">${ICONS.up}</button></div></div>
4913
+ <div class="qreplyrow" data-qreplyrow="${g.id}"${state.qreplyOpen.has(g.id) ? '' : ' hidden'}><input type="text" data-qreplyinput="${g.id}" placeholder="What should change? Sends it back to To Do for rework"><button type="button" class="qreplysend" data-qreplysend="${g.id}" aria-label="Send">${ICONS.up}</button></div></div>
4909
4914
  <div class="revquick">
4910
4915
  <button type="button" class="qbtn ok" data-qapprove="${g.id}" title="Approve">${ICONS.check}</button>
4911
4916
  <button type="button" class="qbtn no" data-qdismiss="${g.id}" title="Send back">${ICONS.close}</button>
@@ -5057,7 +5062,7 @@ function renderTasks(){
5057
5062
  // A question keeps the row open: the answer lands under this very box.
5058
5063
  if (body.outcome !== 'answer') state.qreplyOpen.delete(Number(gid));
5059
5064
  await refresh();
5060
- } catch { showErr('送信に失敗しました'); }
5065
+ } catch { showErr('Could not send.'); }
5061
5066
  };
5062
5067
  for (const b of $('tasklist').querySelectorAll('[data-qreplysend]')) {
5063
5068
  b.onclick = (e) => { e.stopPropagation(); sendQReply(b.dataset.qreplysend, $('tasklist').querySelector(`[data-qreplyinput="${b.dataset.qreplysend}"]`)); };
@@ -5223,8 +5228,8 @@ function openGoalheadReply(id, btn){
5223
5228
  wrap.appendChild(inp); wrap.appendChild(snd); head.insertAdjacentElement('afterend', wrap); inp.focus();
5224
5229
  const send = async () => { const t = inp.value.trim(); if (!t) return;
5225
5230
  let r; try { r = await fetch(withKey(`/api/goals/${id}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text: t })) }); }
5226
- catch { showErr('未開始のゴールは編集を使ってください'); return; }
5227
- if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh(); };
5231
+ catch { showErr("This goal hasn't started — use Edit instead."); return; }
5232
+ if (!r.ok) showErr("This goal hasn't started — use Edit instead."); await refresh(); };
5228
5233
  inp.onkeydown = (e) => { if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); send(); } if (e.key === 'Escape') wrap.remove(); };
5229
5234
  snd.onclick = send;
5230
5235
  }
@@ -5249,8 +5254,8 @@ function wirePriority(){
5249
5254
  try {
5250
5255
  const r = await fetch(withKey(`/api/tasks/${b.dataset.priotask}/priority`), { method: 'PUT',
5251
5256
  headers: { 'content-type': 'application/json' }, body: JSON.stringify({ priority: b.dataset.val }) });
5252
- if (!r.ok) showErr('優先度の変更に失敗しました。'); else await refresh();
5253
- } catch { showErr('優先度の変更に失敗しました。'); }
5257
+ if (!r.ok) showErr('Could not change the priority.'); else await refresh();
5258
+ } catch { showErr('Could not change the priority.'); }
5254
5259
  };
5255
5260
  }
5256
5261
  }
@@ -5395,21 +5400,21 @@ function renderRdEditor(){
5395
5400
  <span class="rdhint">Any task whose headless-browser check didn't pass (or never ran) forces Review too.</span></span></label>
5396
5401
  <label class="rdrow rddesc"><span><span class="rdlabel">Review instructions</span>
5397
5402
  <span class="rdhint">Goes into the pull request body, so whoever reviews it knows what to look for. The worker doesn't read this while it works — instructions for the AI belong in your project's CLAUDE.md / AGENTS.md.</span>
5398
- <textarea class="rdtext" id="rdDescription" maxlength="500" rows="2" placeholder="例:「変更前後のスクショを見比べて、余白が揃っているか確認してください」など、レビューする人に伝えたいこと">${esc(state.rdDraft.description ?? '')}</textarea></span></label>
5403
+ <textarea class="rdtext" id="rdDescription" maxlength="500" rows="2" placeholder="e.g. "Compare the before/after screenshots and check the spacing lines up" — what the reviewer should look at">${esc(state.rdDraft.description ?? '')}</textarea></span></label>
5399
5404
 
5400
- <div class="rdsub">レビューカードに出す項目 / Review card fields</div>
5405
+ <div class="rdsub">Review card fields</div>
5401
5406
  <label class="rdrow"><input type="checkbox" id="rdCardScreenshots"${state.rdDraft.reviewCard.screenshots ? ' checked' : ''}>
5402
- <span><span class="rdlabel">スクショ / Screenshots</span>
5403
- <span class="rdhint">レビューカードに証跡(スクショ/GIF)を表示する。</span></span></label>
5407
+ <span><span class="rdlabel">Screenshots</span>
5408
+ <span class="rdhint">Show the evidence (screenshots/GIFs) on the review card.</span></span></label>
5404
5409
 
5405
- <div class="rdsub">出力言語 / Output language</div>
5410
+ <div class="rdsub">Output language</div>
5406
5411
  <label class="rdrow"><select id="rdLanguage">
5407
5412
  <option value="auto"${state.rdDraft.language === 'auto' ? ' selected' : ''}>Auto (match request)</option>
5408
5413
  <option value="ja"${state.rdDraft.language === 'ja' ? ' selected' : ''}>日本語</option>
5409
5414
  <option value="en"${state.rdDraft.language === 'en' ? ' selected' : ''}>English</option>
5410
5415
  </select>
5411
5416
  <span><span class="rdlabel">Review language</span>
5412
- <span class="rdhint">レビュー要約(確認/変更)などManagerが生成する文言の言語。</span></span></label>`;
5417
+ <span class="rdhint">Language for the text the Manager writes, such as the review summary (What to check / What changed).</span></span></label>`;
5413
5418
  $('rdDefaultWantsPR').addEventListener('change', (e) => { state.rdDraft.defaultWantsPR = e.target.checked; });
5414
5419
  $('rdRequireVerify').addEventListener('change', (e) => { state.rdDraft.requireVerifyPass = e.target.checked; });
5415
5420
  $('rdDescription').addEventListener('input', (e) => { state.rdDraft.description = e.target.value; });
@@ -6315,7 +6320,7 @@ async function sendReviewDismiss(text){
6315
6320
  // these paths, so passing them in the reply text is enough — no API change.
6316
6321
  const imgs = reviewAttach.slice();
6317
6322
  const fullText = imgs.length
6318
- ? `${text}${text ? '\n\n' : ''}添付画像(workerはこのパスをReadで確認できます): ${imgs.map((a) => a.path).join(', ')}`
6323
+ ? `${text}${text ? '\n\n' : ''}Attached images (the worker can open these paths with Read): ${imgs.map((a) => a.path).join(', ')}`
6319
6324
  : text;
6320
6325
  try {
6321
6326
  const body = await replyToGoal(goal.id, fullText, '#reviewFixInput');
@@ -7204,7 +7209,7 @@ function renderFsBoard(){
7204
7209
  // flagship mock; a new goal enters via the composer). Earlier this was To-Do-only, which read
7205
7210
  // as "position is off" against the Artifact (Masa 2026-07-18).
7206
7211
  const col = (name, stg, count, html, stage) =>
7207
- `<section class="fsbcol" data-stage="${stage}" style="--stg:${stg}"><div class="fsbcolhd"><span class="fsbdot"></span><span class="fsbcolnm">${name}</span>${stage === 'doing' ? `<button class="fsbcpause" data-fspause="1" aria-pressed="${paused ? 'true' : 'false'}" title="${paused ? 'Resume — 再開' : 'Pause — 一時停止'}">${paused ? '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>' : '<svg viewBox="0 0 24 24"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>'}</button>` : ''}<span class="fsbcnt">${count}</span><button class="fsbcadd" data-fsnew="1" title="New goal"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></button></div><div class="fsbbody">${html || '<div class="fsbempty">—</div>'}</div><button class="fsbadd" data-fsnew="1" title="New goal"><span class="paplus"></span>New</button></section>`;
7212
+ `<section class="fsbcol" data-stage="${stage}" style="--stg:${stg}"><div class="fsbcolhd"><span class="fsbdot"></span><span class="fsbcolnm">${name}</span>${stage === 'doing' ? `<button class="fsbcpause" data-fspause="1" aria-pressed="${paused ? 'true' : 'false'}" title="${paused ? 'Resume' : 'Pause'}">${paused ? '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>' : '<svg viewBox="0 0 24 24"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>'}</button>` : ''}<span class="fsbcnt">${count}</span><button class="fsbcadd" data-fsnew="1" title="New goal"><svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg></button></div><div class="fsbbody">${html || '<div class="fsbempty">—</div>'}</div><button class="fsbadd" data-fsnew="1" title="New goal"><span class="paplus"></span>New</button></section>`;
7208
7213
  // Depth card (CDO, Masa 2026-07-17): #NN + title, colour on the dot only, and a continuous
7209
7214
  // stroke + n/m ONLY while the goal is actually in flight. The column already names the
7210
7215
  // status, so the card never repeats it. Stopped/failed carry NO bar (a dead bar would claim
@@ -7412,7 +7417,7 @@ function renderFsReview(){
7412
7417
  const rnote = g0?.retest?.passed ? `<span class="rnote">✓ ${esc(g0.retest.note)}</span>` : '';
7413
7418
  const incompleteNote = r.incompleteRequirementCount ? `<span class="rnote">${r.incompleteRequirementCount} requirement${r.incompleteRequirementCount === 1 ? '' : 's'} need review</span>` : '';
7414
7419
  const infraNote = r.verificationInfraError?.detail
7415
- ? `<span class="rnote">検証基盤エラー · <button type="button" class="retrybtn" data-goalreverify="${r.goalIds[0]}">再検証</button></span>`
7420
+ ? `<span class="rnote">Verification system error · <button type="button" class="retrybtn" data-goalreverify="${r.goalIds[0]}">Re-verify</button></span>`
7416
7421
  : '';
7417
7422
  const conflictNote = g0?.conflictsWith?.length
7418
7423
  ? `<span class="rstat conflict">Potential conflict with ${esc(g0.conflictsWith.map((id) => `#${id}`).join(', '))}</span>` : '<span class="rstat">In review</span>';
@@ -7660,7 +7665,7 @@ function renderFsTodo(){
7660
7665
  }).join('')
7661
7666
  + finalizingGoals.map((g) => `<div class="drow"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword wait">Finalizing</span>${fsActs(g.id)}</div></div>`).join('')
7662
7667
  // 一時停止したゴールは Doing に「Paused」1行(アンバー)で留める+Resume。走行/中断行は上と needsRows で抑止済み。
7663
- + pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume — 再開">Resume</button></div></div>`).join('');
7668
+ + pgoals.filter((g) => pausedGoalIds.has(g.id)).map((g) => `<div class="drow"><div class="trow now" data-fsgoalrow="${g.id}"><span class="st run int"></span>${noSpan(g.id)}<span class="tt">${tt(g.reviewSummary?.check || g.plan?.[0] || g.text, 60)}</span><span class="stword int">Paused</span><button class="ab txt" data-fspause="1" title="Resume">Resume</button></div></div>`).join('');
7664
7669
  // NEEDS YOU: stopped/errored work that used to fold into Doing, now under its own caption so
7665
7670
  // it no longer reads as "still in flight" (Masa 2026-07-19). Three kinds share it, each
7666
7671
  // keeping the affordances it already had — nothing about the failed-task and blocked-goal
@@ -7743,7 +7748,7 @@ function renderFsTodo(){
7743
7748
  : `<span class="num fstdlabel">${nextCount} next · ${doingCount} doing${later.length ? ` · ${later.length} later` : ''}</span>`;
7744
7749
  el.innerHTML = `<div class="lhd sechd" id="fsTodoHd"><span class="hd fstdlabel">To Do</span>${_todoNum}<span class="fold fstdlabel">▾</span></div>
7745
7750
  <div class="tl">
7746
- ${(doingRows || paused) ? `<span class="tlcap tlcap-doing" style="padding-top:0">Doing<button class="tlcap-pause" data-fspause="1" aria-pressed="${paused ? 'true' : 'false'}" title="${paused ? 'Resume — 再開' : 'Pause — 一時停止'}">${paused ? '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>' : '<svg viewBox="0 0 24 24"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>'}</button></span>${doingRows}` : ''}
7751
+ ${(doingRows || paused) ? `<span class="tlcap tlcap-doing" style="padding-top:0">Doing<button class="tlcap-pause" data-fspause="1" aria-pressed="${paused ? 'true' : 'false'}" title="${paused ? 'Resume' : 'Pause'}">${paused ? '<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>' : '<svg viewBox="0 0 24 24"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>'}</button></span>${doingRows}` : ''}
7747
7752
  ${needsRows ? `<span class="tlcap needs"${(doingRows || paused) ? '' : ' style="padding-top:0"'}>Needs you</span>${needsRows}` : ''}
7748
7753
  ${rejected.length ? `<span class="tlcap rejcap"${(doingRows || paused) || needsRows ? '' : ' style="padding-top:0"'}>Dismissed<span class="num">${rejected.length}</span></span>${rejected.map((g) => `<div class="trow up rej" data-fsgoalrow="${g.id}"><span class="st rej"></span>${noSpan(g.id)}<span class="tt">${tt(g.text, 70)}</span><span class="acts"><button class="ab" data-fsreopen="${g.id}" title="Reopen — back to Review">${FS_ICONS.reopen}</button><button class="ab" data-fsrejdel="${g.id}" title="Delete — discard (closes the PR)">${FS_ICONS.del}</button></span></div>`).join('')}` : ''}
7749
7754
  ${(parkedRows || upRows) ? `<span class="tlcap"${(doingRows || paused) || needsRows || rejected.length ? '' : ' style="padding-top:0"'}>Next up</span>${parkedRows}${upRows}` : ''}
@@ -7833,8 +7838,8 @@ function renderFsTodo(){
7833
7838
  for (const b of el.querySelectorAll('[data-fsreply]')) b.onclick = (e) => { e.stopPropagation();
7834
7839
  fsInlineInput(b.closest('.trow'), '', 'Reply to this goal…', async (text) => {
7835
7840
  let r; try { r = await fetch(withKey(`/api/goals/${b.dataset.fsreply}/reply`), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(replyWorkerPayload({ text })) }); }
7836
- catch { showErr('未開始のゴールは編集を使ってください'); return; }
7837
- if (!r.ok) showErr('未開始のゴールは編集を使ってください'); await refresh();
7841
+ catch { showErr("This goal hasn't started — use Edit instead."); return; }
7842
+ if (!r.ok) showErr("This goal hasn't started — use Edit instead."); await refresh();
7838
7843
  }); };
7839
7844
  for (const b of el.querySelectorAll('[data-fsedit]')) b.onclick = (e) => { e.stopPropagation();
7840
7845
  const g = state.goals.find((x) => x.id === Number(b.dataset.fsedit));
@@ -9392,7 +9397,7 @@ async function send(){
9392
9397
  if (bind) {
9393
9398
  const batt = state.attach.slice();
9394
9399
  const fullText = batt.length
9395
- ? `${text}${text ? '\n\n' : ''}添付画像(workerはこのパスをReadで確認できます): ${batt.map((a) => a.path).join(', ')}`
9400
+ ? `${text}${text ? '\n\n' : ''}Attached images (the worker can open these paths with Read): ${batt.map((a) => a.path).join(', ')}`
9396
9401
  : text;
9397
9402
  // A bound send belongs to this goal's persisted thread. Do not also add it
9398
9403
  // to the center conversation feed: that made a task reply appear below the
@@ -9479,14 +9484,14 @@ async function askQuestion(){
9479
9484
  if (!q) return;
9480
9485
  const btn = $('peraAskBtn'), out = $('peraAnswer');
9481
9486
  inp.disabled = true; if (btn) btn.disabled = true;
9482
- out.hidden = false; out.className = 'peraanswer loading'; out.textContent = '考え中…';
9487
+ out.hidden = false; out.className = 'peraanswer loading'; out.textContent = 'Thinking…';
9483
9488
  try {
9484
9489
  const r = await fetch(withKey('/api/ask'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ question: q }) });
9485
9490
  const body = await r.json().catch(() => ({}));
9486
- if (!r.ok) throw new Error(body.error || '質問に失敗しました');
9487
- out.className = 'peraanswer'; out.textContent = body.answer || '(回答なし)';
9491
+ if (!r.ok) throw new Error(body.error || 'Could not ask.');
9492
+ out.className = 'peraanswer'; out.textContent = body.answer || '(no answer)';
9488
9493
  } catch (e) {
9489
- out.className = 'peraanswer error'; out.textContent = e.message || '質問に失敗しました';
9494
+ out.className = 'peraanswer error'; out.textContent = e.message || 'Could not ask.';
9490
9495
  } finally {
9491
9496
  inp.disabled = false; if (btn) btn.disabled = false;
9492
9497
  }
@@ -9513,8 +9518,8 @@ function peraThumb(r, cls = ''){
9513
9518
  }
9514
9519
  function peraActs(r){
9515
9520
  const ids = r.goalIds.join(',');
9516
- const okTitle = r.plan ? 'Approve plan — execute' : '承認';
9517
- return `<span class="pgo"><button class="pbtn ok" data-peraok="${ids}" title="${okTitle}">✓</button><button class="pbtn no" data-perano="${ids}" title="差し戻し">✕</button></span>`;
9521
+ const okTitle = r.plan ? 'Approve plan — execute' : 'Approve';
9522
+ return `<span class="pgo"><button class="pbtn ok" data-peraok="${ids}" title="${okTitle}">✓</button><button class="pbtn no" data-perano="${ids}" title="Send back">✕</button></span>`;
9518
9523
  }
9519
9524
  // Plan-review body: the proposed plan, shown verbatim in the card so the
9520
9525
  // reviewer approves (→ execute) or dismisses (→ revise) with the plan in view.
@@ -9674,7 +9679,7 @@ function latestGoalRunForOutcome(goalId, tasks, outcome){
9674
9679
  const cutoff = outcome ? taskSortTime(outcome) : Infinity;
9675
9680
  const prev = runs.find((t) => t.id !== outcome?.id && taskSortTime(t) <= cutoff) || (!outcome ? runs[0] : null);
9676
9681
  if (!prev) return null;
9677
- const note = '前回のpreviewを継承しています: 最新replyは新しいpreviewを宣言しませんでした。 / Inherited previous preview: the latest reply did not declare a new preview.';
9682
+ const note = 'Inherited previous preview: the latest reply did not declare a new preview.';
9678
9683
  return {
9679
9684
  taskId: prev.id,
9680
9685
  attemptId: taskAttemptId(outcome) || taskAttemptId(prev),
@@ -9928,7 +9933,7 @@ function saLedgerRows(){
9928
9933
  // 3-for-2 replacement, an add with no matching del, a rewritten line where almost
9929
9934
  // nothing survives — all keep today's behaviour exactly (tint only, no span).
9930
9935
  // How many body rows the diff really has, ignoring the display cap. A big change used
9931
- // to be cut at 80 rows with nothing said (SPEC §7 asks for "…+N 行 · GitHub で開く ↗"
9936
+ // to be cut at 80 rows with nothing said (SPEC §7 asks for "…+N 行 · Open on GitHub ↗"
9932
9937
  // precisely so a reviewer is never shown a slice that looks like the whole thing).
9933
9938
  // Same skip rules as saUnifiedToGh below — headers and @@ markers are not rows.
9934
9939
  function saDiffRowCount(diff){
@@ -10092,7 +10097,7 @@ function saMsgRowsHtml(msgs, expanded){
10092
10097
  }
10093
10098
 
10094
10099
  // Structured GitHub diff (REVIEW-CARD-SPEC §7): renders {o,n,t,c,h} rows with a
10095
- // word-level highlight (h) and, for a huge change, a "…+N 行 · GitHub で開く ↗"
10100
+ // word-level highlight (h) and, for a huge change, a "…+N 行 · Open on GitHub ↗"
10096
10101
  // footer (only linking to GitHub when a PR exists). Mirrors the flagship artifact's
10097
10102
  // ghDiffHtml so the app pixel-matches. `more` = extra unshown line count; `pr` = url.
10098
10103
  // Request fold threshold (Masa 2026-07-27): Request is supposed to be the user's short
@@ -10128,7 +10133,7 @@ function saGhDiffRowsHtml(rows, more, pr, goalId){
10128
10133
  } else if (l.h) { const h = esc(l.h); code = code.replace(h, `<span class="gh-hi">${h}</span>`); }
10129
10134
  return `<div class="ghl ${cls}"><span class="gho">${l.o ?? ''}</span><span class="ghn">${l.n ?? ''}</span><span class="ghm">${mk}</span><span class="ghc">${code}</span></div>`;
10130
10135
  }).join('');
10131
- const foot = more ? `<div class="ghfoot"><span>…+${more} 行</span>${pr ? ` · <a class="ghopen" href="${esc(pr)}" target="_blank" rel="noopener">GitHub で開く ↗</a>` : ''}</div>` : '';
10136
+ const foot = more ? `<div class="ghfoot"><span>…+${more} more lines</span>${pr ? ` · <a class="ghopen" href="${esc(pr)}" target="_blank" rel="noopener">Open on GitHub ↗</a>` : ''}</div>` : '';
10132
10137
  if (goalId != null && rows.length > SA_DIFF_FOLD_OVER) {
10133
10138
  const open = SA.diffOpen.has(goalId);
10134
10139
  const label = open ? 'Collapse' : `Show all <span class="n">${rows.length}</span> lines`;
@@ -10441,7 +10446,7 @@ function saItemHtml(it, i){
10441
10446
  const reportBody = it.report ? `<div class="rreport">${mdLite(it.report, { images: false })}</div>` : '';
10442
10447
  const infra = it.verificationInfraError;
10443
10448
  const infraBody = infra?.detail
10444
- ? `<div class="rinfra"><p class="rbody">${esc(infra.detail)}</p><button type="button" class="retrybtn" data-saact="reverify:${i}">再検証</button></div>`
10449
+ ? `<div class="rinfra"><p class="rbody">${esc(infra.detail)}</p><button type="button" class="retrybtn" data-saact="reverify:${i}">Re-verify</button></div>`
10445
10450
  : '';
10446
10451
  // ── Screenshots (P2, Masa 2026-07-22): the pictures the AI took of its own result.
10447
10452
  // Full width, at their own aspect — a four-variant comparison sheet is unreadable
@@ -10465,7 +10470,7 @@ function saItemHtml(it, i){
10465
10470
  if (it.recording) proofInner += saVideoFigHtml(it.recordingUrl);
10466
10471
  }
10467
10472
  // ── What changed (spec §4/§7): description + files summary (always) + the diff, which
10468
- // peeks then "…+N 行 · GitHub で開く ↗" for a huge change (no GitHub link when there's no
10473
+ // peeks then "…+N 行 · Open on GitHub ↗" for a huge change (no GitHub link when there's no
10469
10474
  // PR). Structured recorded rows (ghRows) win over a git/client unified diff. Dropped
10470
10475
  // entirely for a conversational task. ──
10471
10476
  // No prose here (P1, Masa 2026-07-22): reviewSummary.changed was the intake AI writing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@galda/cli",
3
- "version": "0.10.112",
3
+ "version": "0.10.113",
4
4
  "type": "module",
5
5
  "description": "Galda - hand off work to Claude Code or Codex, get proof back. Runs on your existing subscription, no extra API cost.",
6
6
  "scripts": {