@takagaki/cortex-decisions-viewer 0.12.181 → 0.12.185

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/dist/render.js CHANGED
@@ -100,6 +100,8 @@ const confirmed_1 = require("./confirmed");
100
100
  const external_id_1 = require("./external-id");
101
101
  // 操作タブの[権限](#841)。生成物で、正本は infra/lambda/shared/permissions.mjs
102
102
  const permissions_data_1 = require("./permissions-data");
103
+ // Drive・Google Chat の追加の確認ページ(#1357)。本体からの開き方と、メッセージの型の綴りだけ引く
104
+ const google_confirm_page_1 = require("./google-confirm-page");
103
105
  // 操作タブの仮置き(#388)。判定の実体はあちら——ここは流し込みと配線だけ
104
106
  const pending_ops_1 = require("./pending-ops");
105
107
  /**
@@ -5590,6 +5592,17 @@ a.ds-tile:hover { text-decoration: none; }
5590
5592
  .ds-toggle-msg { font-size: 12px; color: var(--muted); }
5591
5593
  .ds-toggle-msg.is-ok { color: #1a7f37; }
5592
5594
  .ds-toggle-msg.is-error { color: #b42318; }
5595
+ /* Drive・Google Chat の追加の確認(cortex-tools #1357)。確認ページ(別ページ)と、行の「未確認」の印 */
5596
+ .gc-body { margin: 0; background: var(--bg, #fff); color: var(--fg, #1a1a1a); font: 14px/1.6 system-ui, sans-serif; }
5597
+ .gc-page { max-width: 460px; margin: 0 auto; padding: 24px 16px; }
5598
+ .gc-title { font-size: 18px; margin: 0 0 8px; }
5599
+ .gc-lead { margin: 0 0 16px; color: var(--muted); }
5600
+ .gc-status { min-height: 1.6em; margin: 0 0 16px; font-size: 13px; color: var(--muted); }
5601
+ .gc-status.is-ok { color: #1a7f37; }
5602
+ .gc-status.is-error { color: #b42318; }
5603
+ .gc-actions { display: flex; gap: 8px; }
5604
+ .gc-unverified { font-size: 12px; color: #9a6700; border: 1px solid #d4a72c; border-radius: 4px; padding: 0 6px; }
5605
+ .gc-verify-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
5593
5606
  /* Backlog の同期経路のスイッチ(#516)。role=switch の button に、トラックとつまみを描く。
5594
5607
  語(.ds-switch-label)は押している間も差し替えない(状態は下の 1 行が言う) */
5595
5608
  .ds-route { margin-top: 10px; }
@@ -5861,6 +5874,8 @@ a.ds-tile:hover { text-decoration: none; }
5861
5874
  .mt-shot / .mt-carousel-video と同じ「width:100%; height:auto」に任せ、
5862
5875
  枚数を切り替えるたびに高さが変わる(ガタつきはあるが、CSP安全性を優先する) */
5863
5876
  .mt-carousel { margin: 6px 0 0; max-width: 640px; }
5877
+ /* Backlog のモーダルの動画(#1353): 幅は480pxを上限に(狭い画面では100%まで縮む)・モーダルの左右中央に置く */
5878
+ .mt-carousel-center { max-width: 480px; margin-left: auto; margin-right: auto; }
5864
5879
  .mt-carousel-slide[hidden] { display: none; }
5865
5880
  .mt-carousel-slide .mt-shot { margin: 0; }
5866
5881
  .mt-carousel-video { display: block; width: 100%; height: auto; margin: 0; border: 1px solid var(--border); border-radius: 6px; background: #000; }
@@ -6225,6 +6240,156 @@ const CLIENT_JS = `
6225
6240
  });
6226
6241
  }
6227
6242
 
6243
+ // ---- Drive・Google Chat の追加の確認(cortex-tools #1357)----
6244
+ //
6245
+ // 栓が入っている間だけ働く(intake の google-confirm-info が enabled のとき)。切のあいだは info が { enabled: false } を返し、
6246
+ // 追加欄も行も今のまま。Google のトークンはここでは一切扱わない——確認ページ(別ページ)がポップアップで扱い、ここへは結果だけが戻る。
6247
+ // 注: ここは埋め込みJSのテンプレートリテラル内。コメントでもバッククォートを使わない
6248
+ var GC_PAGE = ${JSON.stringify(google_confirm_page_1.GOOGLE_CONFIRM_PAGE_FILE)};
6249
+ var GC_MSG = ${JSON.stringify(google_confirm_page_1.GOOGLE_CONFIRM_POST_TYPES)};
6250
+ var GC_UNFINISHED = "Google での確認が終わっていません。もう一度押してください";
6251
+ var GC_UNAVAILABLE = "今は確認できません。しばらくしてからもう一度";
6252
+ // サーバの op=state が返す googleConfirm(栓が切なら無い=null=今の追加欄のまま)。取れていない間も null
6253
+ var googleInfo = null;
6254
+ // 栓が入った(または切れた)ときに作り直す追加欄の口
6255
+ var googleSlotRefills = [];
6256
+ function googleInfoApply(next) {
6257
+ var info = next && typeof next === "object" && next.enabled === true ? next : null;
6258
+ if (googleInfoSigOf(info) === googleInfoSigOf(googleInfo)) return;
6259
+ googleInfo = info;
6260
+ googleSlotRefills = googleSlotRefills.filter(function (refill) { return refill(); });
6261
+ repaintOpenLists();
6262
+ }
6263
+ function googleInfoSigOf(i) {
6264
+ if (!i) return "";
6265
+ return JSON.stringify([i.enabled === true, i.ready === true, i.confirmed || null]);
6266
+ }
6267
+ function googleInfoSig() { return googleInfoSigOf(googleInfo); }
6268
+ function googleConfirmOn() { return !!googleInfo && googleInfo.enabled === true; }
6269
+ function googleConfirmReady() { return googleConfirmOn() && googleInfo.ready === true; }
6270
+ function googleConfirmedHas(kind, id) {
6271
+ var c = googleInfo && googleInfo.confirmed;
6272
+ return !!c && Array.isArray(c[kind]) && c[kind].indexOf(id) >= 0;
6273
+ }
6274
+ /**
6275
+ * 確認ページをポップアップで開き、結果を待つ。**ここで受け取るのは結果だけ**(トークンは来ない)。
6276
+ * 🔴 メッセージは同じオリジンの、いま開いたウィンドウからのものだけを受ける。閉じられたら、最後の失敗の理由(無ければ「終わっていません」)
6277
+ */
6278
+ function openGoogleConfirm(opts) {
6279
+ return new Promise(function (resolve) {
6280
+ var intake = SITE.intake;
6281
+ var win = window.open(new URL(GC_PAGE, location.href).href, "cortex-google-confirm", "popup,width=520,height=640");
6282
+ if (!win) { resolve({ ok: false, message: "ポップアップが開けませんでした。ブラウザのポップアップを許可して、もう一度押してください" }); return; }
6283
+ var done = false;
6284
+ var last = null;
6285
+ var timer = null;
6286
+ function end(r) {
6287
+ if (done) return;
6288
+ done = true;
6289
+ window.removeEventListener("message", onMsg);
6290
+ clearInterval(timer);
6291
+ resolve(r);
6292
+ }
6293
+ function onMsg(ev) {
6294
+ if (ev.origin !== location.origin || ev.source !== win || !ev.data) return;
6295
+ if (ev.data.type === GC_MSG.ready) {
6296
+ win.postMessage({
6297
+ type: GC_MSG.start,
6298
+ intake: { url: intake.url, token: intake.token },
6299
+ ticket: actorTicket(),
6300
+ kind: opts.kind,
6301
+ mode: opts.mode,
6302
+ url: opts.url || "",
6303
+ id: opts.id || "",
6304
+ }, location.origin);
6305
+ } else if (ev.data.type === GC_MSG.result) {
6306
+ if (ev.data.ok) end({ ok: true, message: ev.data.message, changed: ev.data.changed });
6307
+ else last = ev.data;
6308
+ }
6309
+ }
6310
+ window.addEventListener("message", onMsg);
6311
+ timer = setInterval(function () {
6312
+ if (win.closed) end({ ok: false, message: last && last.message ? last.message : GC_UNFINISHED });
6313
+ }, 500);
6314
+ });
6315
+ }
6316
+ /** 確認が済んだあとの後始末: 登録の一覧と確認済みの一覧(どちらも op=state)を取り直し、開いている一覧を描き直す */
6317
+ function afterGoogleConfirm() {
6318
+ loadLiveFleet(true);
6319
+ }
6320
+ /** 栓が入っている案件では追加欄を作り替える。切(または取れない)なら今の追加欄 */
6321
+ function googleAwareSlot(buildLegacy, buildPicker) {
6322
+ var slot = el("div", {});
6323
+ function fill() {
6324
+ while (slot.firstChild) slot.removeChild(slot.firstChild);
6325
+ slot.appendChild(googleConfirmOn() ? buildPicker(googleInfo) : buildLegacy());
6326
+ }
6327
+ fill();
6328
+ // 栓の状態が後から届いたとき(最初の op=state の応答)に作り直す。画面から外れたら外す
6329
+ googleSlotRefills.push(function () {
6330
+ if (!slot.isConnected) return false;
6331
+ fill();
6332
+ return true;
6333
+ });
6334
+ return slot;
6335
+ }
6336
+ /** Drive の追加欄(栓が入っているとき)。URL の貼り付けは無く、[Google で選ぶ]だけ */
6337
+ function googleDriveAddBox(info) {
6338
+ var msg = el("div", { class: "ds-toggle-msg" });
6339
+ var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "Google で選ぶ" });
6340
+ btn.addEventListener("click", async function () {
6341
+ if (info.ready !== true) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "✗ " + GC_UNAVAILABLE; return; }
6342
+ btn.disabled = true;
6343
+ msg.className = "ds-toggle-msg";
6344
+ msg.textContent = "Google の画面で選んでください…";
6345
+ var r = await openGoogleConfirm({ kind: "drive", mode: "add" });
6346
+ btn.disabled = false;
6347
+ msg.className = "ds-toggle-msg" + (r.ok ? " is-ok" : " is-error");
6348
+ msg.textContent = (r.ok ? "✓ " : "✗ ") + r.message;
6349
+ if (r.ok) afterGoogleConfirm();
6350
+ });
6351
+ return el("div", { class: "ds-addform", "data-adder": "google-drive" }, [
6352
+ el("div", { class: "ds-addform-title", text: "+ Google Drive のフォルダを追加" }),
6353
+ el("div", { class: "ds-toggle" }, [btn, msg]),
6354
+ ]);
6355
+ }
6356
+ /** Chat の[追加]を押したとき、栓が入っていれば確認ページを開く。扱ったら true(呼び手は通常の送信をしない) */
6357
+ function googleChatAddIntercept(url, msg, btn, onOk) {
6358
+ if (!googleConfirmOn()) return false;
6359
+ if (!googleConfirmReady()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "✗ " + GC_UNAVAILABLE; return true; }
6360
+ btn.disabled = true;
6361
+ msg.className = "ds-toggle-msg";
6362
+ msg.textContent = "Google の画面で確認してください…";
6363
+ openGoogleConfirm({ kind: "google-chat", mode: "add", url: url }).then(function (r) {
6364
+ btn.disabled = false;
6365
+ msg.className = "ds-toggle-msg" + (r.ok ? " is-ok" : " is-error");
6366
+ msg.textContent = (r.ok ? "✓ " : "✗ ") + r.message;
6367
+ if (r.ok) { onOk(); afterGoogleConfirm(); }
6368
+ });
6369
+ return true;
6370
+ }
6371
+ /** 今ある行の「未確認」の印と[確認する](確認済みの行には何も足さない・期限で外さない) */
6372
+ function appendGoogleVerifyUi(row, kind, id) {
6373
+ if (!googleConfirmReady() || googleConfirmedHas(kind, id)) return;
6374
+ var msg = el("span", { class: "ds-toggle-msg" });
6375
+ var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "確認する" });
6376
+ btn.addEventListener("click", async function () {
6377
+ btn.disabled = true;
6378
+ msg.className = "ds-toggle-msg";
6379
+ msg.textContent = "Google の画面で確認してください…";
6380
+ var r = await openGoogleConfirm({ kind: kind, mode: "confirm", id: id, url: id });
6381
+ btn.disabled = false;
6382
+ msg.className = "ds-toggle-msg" + (r.ok ? " is-ok" : " is-error");
6383
+ msg.textContent = (r.ok ? "✓ " : "✗ ") + r.message;
6384
+ if (r.ok) afterGoogleConfirm();
6385
+ });
6386
+ row.appendChild(el("div", { class: "gc-verify-row" }, [
6387
+ el("span", { class: "gc-unverified", title: "追加した人が、自分の Google で開けるかを確かめた記録がありません。押さなくても取り込みは続きます", text: "未確認" }),
6388
+ btn,
6389
+ msg,
6390
+ ]));
6391
+ }
6392
+
6228
6393
  // ---- ビルド時TSと共有する判定(唯一の正本は上の関数定義) ----
6229
6394
  //
6230
6395
  // **写経しない。** 接続マップ(ビルド時にSVGを描く)と画面(ブラウザで一覧を描く)が
@@ -6665,13 +6830,18 @@ ${story_1.splitByNameLinks.toString()}
6665
6830
  var FIGMA_SHOT_STEP1 = "${figma_guide_images_1.FIGMA_GUIDE_STEP1}";
6666
6831
  var FIGMA_SHOT_STEP2 = "${figma_guide_images_1.FIGMA_GUIDE_STEP2}";
6667
6832
  var FIGMA_SHOT_STEP3 = "${figma_guide_images_1.FIGMA_GUIDE_STEP3}";
6668
- // Backlog の手順動画のポスターと手順画像5枚(#1353)。理由は src/backlog-guide-images.ts
6833
+ // Backlog の手順動画のポスターと手順画像10枚(#1353)。理由は src/backlog-guide-images.ts
6669
6834
  var BACKLOG_SHOT_POSTER = "${backlog_guide_images_1.BACKLOG_GUIDE_POSTER}";
6670
6835
  var BACKLOG_SHOT_STEP1 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP1}";
6671
6836
  var BACKLOG_SHOT_STEP2 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP2}";
6672
6837
  var BACKLOG_SHOT_STEP3 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP3}";
6673
6838
  var BACKLOG_SHOT_STEP4 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP4}";
6674
6839
  var BACKLOG_SHOT_STEP5 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP5}";
6840
+ var BACKLOG_SHOT_STEP6 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP6}";
6841
+ var BACKLOG_SHOT_STEP7 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP7}";
6842
+ var BACKLOG_SHOT_STEP8 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP8}";
6843
+ var BACKLOG_SHOT_STEP9 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP9}";
6844
+ var BACKLOG_SHOT_STEP10 = "${backlog_guide_images_1.BACKLOG_GUIDE_STEP10}";
6675
6845
 
6676
6846
  /**
6677
6847
  * コピーボタンの動き(copyRow と copyInline の共通部分)。clipboard が使えない環境では
@@ -6801,7 +6971,9 @@ ${story_1.splitByNameLinks.toString()}
6801
6971
  // (配るのは動画1本だけ。ポスターは宣言側が持つ)。あれば <video poster> に付ける
6802
6972
  // - shots は今の meta.shots と同じ形。画像は data URI 埋め込みのまま(変更なし)
6803
6973
  //
6804
- // 🔴 **ここでも幅の数字を呼び出し側に書かせない**(CSS の .mt-carousel 1 か所だけ)。
6974
+ // - guide.counter … true なら画面の説明文を「手順 N / 全体」だけにする(画像の中に文言がある手順用・Backlog)。
6975
+ // 動画スライドの説明文は空(読み上げ用は video の aria-label)。無ければ従来どおり各スライドの見出し
6976
+ // 🔴 **ここでも幅の数字を呼び出し側に書かせない**(CSS の .mt-carousel と、中央寄せ用の .mt-carousel-center だけ)。
6805
6977
  // ⚠ **表示切替は hidden 属性(DOM プロパティ代入)だけ**。インラインの style を書き込む
6806
6978
  // 手段(CSP の検査・csp-compatible.test.mjs が禁じる書き方)は使わない。
6807
6979
  // ⚠ ハンドラは全部 addEventListener(インラインの on* 属性は配信先の CSP が実行しない)。
@@ -6825,12 +6997,16 @@ ${story_1.splitByNameLinks.toString()}
6825
6997
  slides.push({
6826
6998
  node: el("video", videoAttrs),
6827
6999
  caption: guide.video.alt || "",
7000
+ label: null,
6828
7001
  });
6829
7002
  }
6830
- (guide.shots || []).forEach(function (shot) {
7003
+ var shotList = guide.shots || [];
7004
+ shotList.forEach(function (shot, shotIdx) {
6831
7005
  slides.push({
6832
7006
  node: guideShot({ src: shot.src, width: shot.width, height: shot.height, alt: shot.alt }),
6833
7007
  caption: shot.heading || shot.alt || "",
7008
+ // counter: true の手順は、画面の説明文を「手順 N / 全体」だけにする(画像の中に文言があるため)
7009
+ label: guide.counter ? "手順 " + (shotIdx + 1) + " / " + shotList.length : null,
6834
7010
  });
6835
7011
  });
6836
7012
  if (!slides.length) return null;
@@ -6867,7 +7043,8 @@ ${story_1.splitByNameLinks.toString()}
6867
7043
  // フォーカスが隠れたスライドの中にあったら外枠へ戻す(キーボード操作が途切れない)
6868
7044
  if (document.activeElement && document.activeElement !== root && viewport.contains(document.activeElement)
6869
7045
  && slideEls[current] && !slideEls[current].contains(document.activeElement)) root.focus();
6870
- caption.textContent = slides[current].caption;
7046
+ // 動画スライドに label は無い(counter 手順では説明文を出さない)。counter の無い手順は従来どおり caption
7047
+ caption.textContent = guide.counter ? (slides[current].label || "") : slides[current].caption;
6871
7048
  dotEls.forEach(function (dotEl, idx) {
6872
7049
  dotEl.classList.toggle("is-active", idx === current);
6873
7050
  if (idx === current) dotEl.setAttribute("aria-current", "true"); else dotEl.removeAttribute("aria-current");
@@ -9820,16 +9997,18 @@ ${story_1.splitByNameLinks.toString()}
9820
9997
  var meta = owner ? githubOwnerMeta(owner) : null;
9821
9998
  if (meta && meta.source === "unknown") return null;
9822
9999
  // 🔴 リンクの宛先は、その組織について窓口が答えたアプリ(案件のテナントのアプリ・#1328 T-6)。
9823
- // 正本(op=state)が答えている画面で slug が無い組織は「分からない」回なので、**宛先を出さず文だけ**にする
10000
+ // 正本(op=state)が答えている画面で、自分の行にも同じ案件の他の行にも slug が無いときは、**宛先を出さず文だけ**にする
9824
10001
  // (当社のアプリへ落とすと、会社ごとのアプリの会社の画面で別のアプリを入れさせる)。
9825
10002
  // 当社のアプリの定数へ落とすのは、正本の答えがまだ無い画面(まとめ表だけ)のときだけ
9826
- var slug = githubAppSlugOf(meta);
10003
+ // その組織の行に slug が無いときは、同じ案件の正本の他の行から借りる(入れる App は組織でなく案件のテナントで決まる・#1359)
10004
+ var slug = githubAppSlugOf(meta) || githubTenantAppSlug();
9827
10005
  var linkable = !!slug || !LIVE_GH_OWNERS;
9828
10006
  var pick = row.gate === "unreachable" && meta && meta.appInstalled === true;
9829
10007
  var say = pick ? "GitHub App の設定で、このリポジトリを選んでください。" : "この組織に Cortex の GitHub App を入れてください。";
9830
10008
  if (!linkable) return pick ? [say] : [say, { br: true }, GITHUB_APP_RECHECK_TEXT];
9831
- // 部品には行の生の値を渡す(宛先の検めは githubAppLinkUrl が持つ。ここの slug はリンクを出すかの判断だけ)
9832
- var raw = meta && meta.appSlug;
10009
+ // 🔴 部品には**解決後の slug**を渡す(組織の行の値が無いのに元の値を渡すと、githubAppLinkUrl が当社の定数へ落ちる)。
10010
+ // 正本の答えが無い画面(まとめ表だけ)では slug が空のまま渡り、従来どおり定数へ落ちる
10011
+ var raw = slug || (meta && meta.appSlug);
9833
10012
  // 入れた後の一手は同じ段落の 1 行に添える(行の補足は最大 1 段)。選び直す方(pick)は入れ直しではないので添えない
9834
10013
  return pick
9835
10014
  ? [say, { link: "インストール設定を開く →", kind: "settings", slug: raw }]
@@ -11148,6 +11327,24 @@ ${story_1.splitByNameLinks.toString()}
11148
11327
  var url = s ? "https://github.com/apps/" + encodeURIComponent(s) + "/installations/new" : GITHUB_APP_INSTALL_URL;
11149
11328
  return ({ install: url, settings: url })[kind] || url;
11150
11329
  }
11330
+ /**
11331
+ * 同じ案件のテナントのアプリの slug。**案件単位の値(LIVE_GH_APP_SLUG・窓口が案件の会社で答えた値)を先に使う**——組織が 1 つだけで
11332
+ * 照会が断られた案件でも宛先が分かる(#1359)。無ければ、同じ案件の正本(LIVE_GH_OWNERS の行だけ。まとめ表の古い行は混ぜない)の
11333
+ * 有効な slug が 1 つ以上あり、**全部が同じ**ときだけ返す(食い違えば「分からない」=null=文だけ)。
11334
+ * 🔴 当社の定数には落とさない(#1328 T-6)。⚠ 埋め込みJSなので、正規表現は逆斜線なしで書く
11335
+ */
11336
+ function githubTenantAppSlug() {
11337
+ if (LIVE_GH_APP_SLUG) return LIVE_GH_APP_SLUG;
11338
+ var found = null;
11339
+ var list = LIVE_GH_OWNERS || [];
11340
+ for (var i = 0; i < list.length; i++) {
11341
+ var s = githubAppSlugOf(list[i]);
11342
+ if (!s) continue;
11343
+ if (found && found !== s) return null;
11344
+ found = s;
11345
+ }
11346
+ return found;
11347
+ }
11151
11348
  /** 行の slug(形が合わなければ null)。⚠ 埋め込みJSなので、正規表現は逆斜線なしで書く */
11152
11349
  function githubAppSlugOf(meta) {
11153
11350
  var v = meta && meta.appSlug;
@@ -11386,6 +11583,8 @@ ${story_1.splitByNameLinks.toString()}
11386
11583
  * (unsettled/pending)に組織の行が紛れ込む。
11387
11584
  */
11388
11585
  var LIVE_GH_OWNERS = null;
11586
+ /** 案件の会社の GitHub App の slug(op=state の registeredNow.githubAppSlug・組織ごとの行に slug が無い回の宛先・#1359) */
11587
+ var LIVE_GH_APP_SLUG = null;
11389
11588
  /** マージ済みの3キー(作り直しは setLive* だけが行う) */
11390
11589
  var MERGED = null;
11391
11590
  /** マージの対象になるキー。これ以外は素の値をそのまま返す */
@@ -12350,7 +12549,20 @@ ${story_1.splitByNameLinks.toString()}
12350
12549
  } else if (cap === "課題管理" && (!adapter || adapter === "backlog")) {
12351
12550
  // 🔴 **Backlog の説明文(c-07 の 2 本目・新設)。** 追加の欄の説明から外した 3 つを、ここに 1 回だけ置く:
12352
12551
  // 何が同期されるか・起票先は変わらないこと(2026-08-20 に追加の確認をやめたとき追加の欄へ移した文)・止め方
12353
- m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいる Backlog プロジェクトの課題・ドキュメント・Wiki を毎晩同期します。接続先を足しても、ビューアからの起票先は変わりません。やめるには「この接続先を外す」を押してください。" }));
12552
+ var backlogDesc = el("p", { class: "modal-desc", text: "ここに並んでいる Backlog プロジェクトの課題・ドキュメント・Wiki を毎晩同期します。接続先を足しても、ビューアからの起票先は変わりません。やめるには「この接続先を外す」を押してください。" });
12553
+ m.modal.appendChild(backlogDesc);
12554
+ // 🔴 **つなぎ方の動画(#1353)はモーダルの見出しのすぐ下(説明文より前)に置く。** 鍵の欄の「発行のしかた」の中に置くと、
12555
+ // 接続先が 0 件のあいだは鍵の欄ごと出ないので、いちばん要る最初のときに見えない。
12556
+ // **折りたたまない**(オーナー指摘・2026-10-19): 接続先の有無によらず、いつもそのまま出す。
12557
+ // 左右中央・幅の上限は CSS の .mt-carousel-center 1 か所。説明文は先に追加済みなので、説明文の前へ差し込む
12558
+ var backlogForm = SECRET_FORMS["backlog"];
12559
+ var backlogCarousel = backlogForm && backlogForm.guideInModal && backlogTargetsEditable()
12560
+ ? buildGuideCarousel("backlog", backlogForm.guide) : null;
12561
+ if (backlogCarousel) {
12562
+ backlogCarousel.classList.add("mt-carousel-center");
12563
+ backlogCarousel.setAttribute("data-guide-kind", "backlog");
12564
+ m.modal.insertBefore(backlogCarousel, backlogDesc);
12565
+ }
12354
12566
  } else if (cap === "開発") {
12355
12567
  // 🔴 **開発の開示文は「記録に使う・決定の抽出には使わない」(#660)。** 2026-09-18 の
12356
12568
  // オーナー決定でソースの性質は種類で固定になり、GitHub は「作業の記録」に固定された。
@@ -12839,7 +13051,7 @@ ${story_1.splitByNameLinks.toString()}
12839
13051
  // 作れない。判断③)。4件以上でも畳まない——畳みは汎用経路の仕様で、ここは通らない
12840
13052
  if (liveListWanted("google-chat")) pushCap("チャット", liveList(
12841
13053
  "チャット",
12842
- function () { return externalsSig("google-chat", null); },
13054
+ function () { return externalsSig("google-chat", googleInfoSig()); },
12843
13055
  function (box) {
12844
13056
  var items = liveExternals("google-chat");
12845
13057
  // 注記は**行より前**に置く(0件なら一覧の代わり・行があれば添え書き)
@@ -12847,7 +13059,12 @@ ${story_1.splitByNameLinks.toString()}
12847
13059
  if (noteGchat) box.appendChild(noteGchat);
12848
13060
  if (!items.length) return;
12849
13061
  var meta = DS_TYPE["google-chat"] || { icon: "💬", label: "Google Chat" };
12850
- dimUnconfirmedRows(items.map(function (s) { return extSourceCard(s, meta); }))
13062
+ dimUnconfirmedRows(items.map(function (s) {
13063
+ var card = extSourceCard(s, meta);
13064
+ // 追加した人が自分の Google で開けると確かめた記録が無い行(栓が入っているときだけ・#1357)
13065
+ appendGoogleVerifyUi(card, "google-chat", s.ref);
13066
+ return card;
13067
+ }))
12851
13068
  .forEach(function (n) { box.appendChild(n); });
12852
13069
  },
12853
13070
  ), "google-chat");
@@ -15911,7 +16128,7 @@ ${story_1.splitByNameLinks.toString()}
15911
16128
  // 「確かめられたか」も混ぜる(#409。混ぜないと注記の要否が変わっても作り直されない)
15912
16129
  // 「届くか」も混ぜる(#523 S1。混ぜないと、ストアの reachable が届いても札が付かない)
15913
16130
  return JSON.stringify([d.driveFolderIds || [], d.driveFolderNames || {}, d.driveState || "", d.drivePending || {}, registrationUnreadable(), registryUnconfirmed(),
15914
- reachSigOf("materials", d.driveFolderIds || [])]);
16131
+ reachSigOf("materials", d.driveFolderIds || []), googleInfoSig()]);
15915
16132
  }, function (listBox) {
15916
16133
  var src = driveNow();
15917
16134
  var folders = src.driveFolderIds || [];
@@ -15943,6 +16160,8 @@ ${story_1.splitByNameLinks.toString()}
15943
16160
  // **これまで画面に出す場所が1つも無かった**(共有が外れたことを、取り込みが
15944
16161
  // 失敗するまで言えなかった)
15945
16162
  appendReachableChip(row, "materials", id);
16163
+ // 追加した人が自分の Google で開けると確かめた記録が無い行(栓が入っているときだけ・#1357)
16164
+ appendGoogleVerifyUi(row, "drive", id);
15946
16165
  // **名前を出したときはIDも併記する。** 同名のフォルダを見分けられなくなるのと、
15947
16166
  // 「このIDを外す」という確認文と画面の表示が食い違うのを防ぐ
15948
16167
  if (fname) row.appendChild(el("span", { class: "ds-sync", text: id }));
@@ -15986,7 +16205,7 @@ ${story_1.splitByNameLinks.toString()}
15986
16205
  listBox.appendChild(el("p", { class: "ds-empty", text: "取り込み対象のフォルダはまだ登録されていません。" }));
15987
16206
  }
15988
16207
  }));
15989
- box.appendChild(addForm({
16208
+ box.appendChild(googleAwareSlot(function () { return addForm({
15990
16209
  title: "+ Google Drive のフォルダを追加",
15991
16210
  placeholder: "https://drive.google.com/drive/folders/…",
15992
16211
  namePlaceholder: "メモ(任意)",
@@ -15996,7 +16215,7 @@ ${story_1.splitByNameLinks.toString()}
15996
16215
  // (DS_GATE_CAP.materials.unreachable: 「このフォルダを〈取り込み用アカウント〉に閲覧者として共有してください。[コピー]」)。
15997
16216
  // 「中身が 共有資料 にコピーされ Markdown 化される・止め方」はモーダル上部の説明文が 1 回だけ持つ。
15998
16217
  build: function (url, name) { return { op: "add-drive-folder", url: url, name: name }; },
15999
- }));
16218
+ }); }, googleDriveAddBox));
16000
16219
  used = true;
16001
16220
  }
16002
16221
  }
@@ -16286,6 +16505,8 @@ ${story_1.splitByNameLinks.toString()}
16286
16505
  build: function (url) {
16287
16506
  return { op: "add-google-chat-space", url: url };
16288
16507
  },
16508
+ // 栓が入っているときは、[追加]で確認ページ(Google の承認)を開く(#1357)。切のときは今の送信
16509
+ googleChat: true,
16289
16510
  adder: "google-chat",
16290
16511
  }));
16291
16512
 
@@ -16940,6 +17161,8 @@ ${story_1.splitByNameLinks.toString()}
16940
17161
  }
16941
17162
  var added = main.value.trim();
16942
17163
  var addedName = name ? name.value.trim() : "";
17164
+ // Google Chat は、栓が入っていれば本人の Google で開けるかを確かめてから追加する(確認ページが intake を呼ぶ・#1357)
17165
+ if (opts.googleChat && googleChatAddIntercept(added, msg, btn, function () { main.value = ""; })) return;
16943
17166
  var payload = opts.build(added, addedName, wsSelect ? wsSelect.value : "");
16944
17167
  sendConfig(payload, msg, btn, function (j, info) {
16945
17168
  // 追加できたら入力欄を空にする(同じものを二度押ししないように)
@@ -17754,6 +17977,8 @@ ${story_1.splitByNameLinks.toString()}
17754
17977
  // 🔴 **registeredNow も fleet より先に読む。** まとめ表をまだ持たない案件
17755
17978
  // (=立ち上げ直後=この仕組みが一番効く案件)は下で throw するので、
17756
17979
  // ここより後に置くと**その案件だけ素通り**する
17980
+ // 追加の確認の栓と確認済みの一覧(#1357)。fleet より先に読む(立ち上げ直後の案件でも追加欄を作り替える)
17981
+ googleInfoApply(j && j.googleConfirm);
17757
17982
  LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
17758
17983
  LIVE_REGISTERED_AT = startedAt;
17759
17984
  LIVE_STATE = sanitizeStateNow(j && j.stateNow);
@@ -17769,6 +17994,7 @@ ${story_1.splitByNameLinks.toString()}
17769
17994
  LIVE_SECRETS = sanitizeSecretsNow(j && j.secretsNow);
17770
17995
  LIVE_CAPS = sanitizeCapabilitiesNow(j && j.capabilitiesNow);
17771
17996
  LIVE_GH_OWNERS = sanitizeGithubOwnersNow(j && j.registeredNow && j.registeredNow.githubOwners);
17997
+ LIVE_GH_APP_SLUG = githubAppSlugOf({ appSlug: j && j.registeredNow && j.registeredNow.githubAppSlug });
17772
17998
  MERGED = null;
17773
17999
  if (!j || !j.fleet) throw new Error("no fleet");
17774
18000
  LIVE_FLEET = normalizeFleet(j.fleet);
@@ -18287,38 +18513,68 @@ ${story_1.splitByNameLinks.toString()}
18287
18513
  "(前の担当者のアカウントが無効になると同期が止まります)。",
18288
18514
  },
18289
18515
  // **手順の動画+画像(#1353)。** 動画の実 URL は guide に置かない(SITE.guideAssets.backlog.video・
18290
- // build.ts の GUIDE_VIDEO_SOURCES が注入する)。動画が無い配信先では画像5枚だけで成立する。
18291
- // 画像の並びは動画の流れ(URL をコピー → キーを発行 → 貼る → つながる)に揃える。
18516
+ // build.ts の GUIDE_VIDEO_SOURCES が注入する)。動画が無い配信先では画像10枚だけで成立する。
18517
+ // 画像の並びは手順の順(01→10: URL を貼る欄 → URL をコピー → キーを発行 → 貼る → つながる)。
18518
+ // 動画+画像は鍵の欄ではなく、モーダルの上(見出しの下・説明文の前)に出す(openCapabilityModal の Backlog の項)
18519
+ guideInModal: true,
18292
18520
  guide: {
18521
+ // 画像の説明文(カルーセルの下)は「手順 N / 10」だけを出す(画像の中に文言が入っているため)。
18522
+ // 読み上げ用の説明は各画像の alt と動画の aria-label に持たせる(画面には出さない)
18523
+ counter: true,
18293
18524
  video: {
18294
18525
  alt: "Backlog を Cortex につなぐ手順の動画(約1分30秒)。Backlog の URL のコピー、API キーの発行、操作タブへの貼り付け、接続OK の確認までを画面で見せます。",
18295
18526
  poster: BACKLOG_SHOT_POSTER,
18296
18527
  },
18297
18528
  shots: [
18298
18529
  {
18299
- src: BACKLOG_SHOT_STEP1, width: "1481", height: "993",
18300
- heading: "1. Backlog のアドレスバーから URL をコピー(操作タブの「接続先を追加」に貼る)",
18301
- alt: "Backlogのプロジェクトホーム。ブラウザのアドレスバーに cm1.backlog.jp/projects/PJ_CORTEX の形のURLが出ている。",
18530
+ src: BACKLOG_SHOT_STEP1, width: "860", height: "370",
18531
+ heading: "1. Cortex の欄に Backlog の URL を貼る",
18532
+ alt: "手順 1。Cortex の Backlog のモーダル。「接続先を追加」の URL の入力欄が赤枠で示され、「ここに Backlog の URL を貼ります」と書かれている。",
18533
+ },
18534
+ {
18535
+ src: BACKLOG_SHOT_STEP2, width: "1481", height: "993",
18536
+ heading: "2. Backlog のホームで URL をコピー",
18537
+ alt: "手順 2。Backlog のプロジェクトホーム。アドレスバーの URL が赤枠で示され、「プロジェクトのホームで、この URL をコピー」と書かれている。",
18538
+ },
18539
+ {
18540
+ src: BACKLOG_SHOT_STEP3, width: "1080", height: "440",
18541
+ heading: "3. 貼り付けて追加",
18542
+ alt: "手順 3。URL を貼った入力欄と「追加」ボタンが赤く囲まれ、「貼り付けて[追加]を押す」と書かれている。",
18543
+ },
18544
+ {
18545
+ src: BACKLOG_SHOT_STEP4, width: "860", height: "560",
18546
+ heading: "4. 接続先が並んだら API キーへ",
18547
+ alt: "手順 4。接続先の行が並び、「入れる」ボタンが赤く囲まれて、「接続先が並んだら、次は API キー」と書かれている。",
18548
+ },
18549
+ {
18550
+ src: BACKLOG_SHOT_STEP5, width: "1152", height: "622",
18551
+ heading: "5. 右上のアイコンから個人設定",
18552
+ alt: "手順 5。Backlog の右上のアイコンのメニューで「個人設定」が赤枠で示されている。",
18553
+ },
18554
+ {
18555
+ src: BACKLOG_SHOT_STEP6, width: "1152", height: "622",
18556
+ heading: "6. 左のメニューの API",
18557
+ alt: "手順 6。Backlog の個人設定の左のメニューで「API」が赤枠で示されている。",
18302
18558
  },
18303
18559
  {
18304
- src: BACKLOG_SHOT_STEP2, width: "1536", height: "766",
18305
- heading: "2. 個人設定 → API のメモに Cortex と入れて登録",
18306
- alt: "Backlogの個人設定のAPI画面。新しいAPIキーを発行する欄のメモに Cortex と入力されていて、登録ボタンを押す前の状態。",
18560
+ src: BACKLOG_SHOT_STEP7, width: "1536", height: "766",
18561
+ heading: "7. メモに Cortex と入れて登録",
18562
+ alt: "手順 7。API の設定画面。メモ欄に「Cortex」と入っていて、「登録」ボタンが赤く囲まれている。",
18307
18563
  },
18308
18564
  {
18309
- src: BACKLOG_SHOT_STEP3, width: "1536", height: "766",
18310
- heading: "3. 発行された API キーをコピー",
18311
- alt: "登録されたAPIキーの一覧に、メモが Cortex の行が増えている。キーは先頭と末尾の文字だけ見え、行のコピーボタンでコピーできる。",
18565
+ src: BACKLOG_SHOT_STEP8, width: "1536", height: "766",
18566
+ heading: "8. 発行された API キーをコピー",
18567
+ alt: "手順 8。登録された API キーの一覧で、メモが Cortex の行のコピーボタンが赤く囲まれ、「発行された API キーをコピー」と書かれている。",
18312
18568
  },
18313
18569
  {
18314
- src: BACKLOG_SHOT_STEP4, width: "1456", height: "819",
18315
- heading: "4. 操作タブの Backlog の入力欄に貼って保存",
18316
- alt: "Cortexの操作タブのBacklogのモーダル。API キーを入れる欄(表示は「トークンを貼り付け」)にカーソルがあり、その下に保存ボタンがある。",
18570
+ src: BACKLOG_SHOT_STEP9, width: "860", height: "620",
18571
+ heading: "9. 入れる → キーを貼って保存",
18572
+ alt: "手順 9。Cortex のモーダルで「入れる」を押して出た入力欄と「保存」ボタンが赤く囲まれ、「キーを貼って[保存]」と書かれている。",
18317
18573
  },
18318
18574
  {
18319
- src: BACKLOG_SHOT_STEP5, width: "1568", height: "773",
18320
- heading: "5. 「接続OK」と出れば完了",
18321
- alt: "Backlogのモーダルの接続先の行に、緑の「接続OK」の表示が出ている。登録済みと保存しましたの表示も出ている。",
18575
+ src: BACKLOG_SHOT_STEP10, width: "690", height: "455",
18576
+ heading: "10. 接続を確認して接続OK",
18577
+ alt: "手順 10。接続先の行に緑の「接続OK」が出ていて、「接続を確認」ボタンと一緒に赤く囲まれている。",
18322
18578
  },
18323
18579
  ],
18324
18580
  },
@@ -18543,7 +18799,9 @@ ${story_1.splitByNameLinks.toString()}
18543
18799
  // **手順の動画+画像(#547・#1353)。** 文章(guidance)の下に足す。
18544
18800
  // meta.guide を持つ種別(Figma)はカルーセル1つに乗せる。meta.guide も meta.shots も
18545
18801
  // 持たない種別(Backlog)ではこの節は素通りする
18546
- if (meta.guide) {
18802
+ if (meta.guide && meta.guideInModal) {
18803
+ // モーダルの上に出している種別(Backlog)は、ここには重ねない
18804
+ } else if (meta.guide) {
18547
18805
  var carousel = buildGuideCarousel(kind, meta.guide);
18548
18806
  if (carousel) guidanceBox.appendChild(carousel);
18549
18807
  } else if (meta.shots) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.181",
3
+ "version": "0.12.185",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -15,7 +15,7 @@
15
15
  "dist",
16
16
  "assets",
17
17
  "!assets/guides/backlog-poster.jpg",
18
- "!assets/guides/backlog-shots"
18
+ "!assets/guides/backlog-annotated"
19
19
  ],
20
20
  "engines": {
21
21
  "node": ">=18"