@takagaki/cortex-decisions-viewer 0.12.160 → 0.12.162

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/build.js CHANGED
@@ -287,15 +287,11 @@ function resolveKnowledgeRoot(decisionsDirAbs) {
287
287
  return null;
288
288
  }
289
289
  /**
290
- * 案件アイコン(#348)で受け付ける拡張子。**探索の順でもある**(2枚あれば先頭を使う)。
290
+ * 案件アイコン(#348)で拾う拡張子。**探索の順でもある**(2枚あれば先頭を使う)。
291
291
  *
292
- * **Lambda 側(infra/lambda/intake/logic.mjs の PROJECT_ICON_EXTS)と同じ並びにすること。**
293
- * 片方だけ増やす・並べ替えると、intake が書いた名前を build が拾わない(=設定したのに
294
- * 画面に出ない)か、2枚あるときに intake と build が別の1枚を選ぶ。
295
- * 突き合わせは viewer/test/project-icon-parity.test.mjs が機械的に見張る。
296
- *
297
- * `.jpeg` は入れない——intake は `.jpeg` を `jpg` へ正規化して書くので、
298
- * リポジトリにその名前は生まれない(拾うと「どちらが正か」が2つになる)。
292
+ * 置き場は案件リポの `Cortex/icon.<ext>`。画面と intake からアイコンを置く・外す口は外した(cortex-engine #1781・
293
+ * 仕様 common.screen-ops)ので、置くのは人が GitHub で直接。ポータルの取り込み(`v/icon.*`)も同じ 3 つを読む。
294
+ * `.jpeg` は入れない(前の intake は `jpg` へ正規化して書いていた・拾うと「どちらが正か」が2つになる)。
299
295
  */
300
296
  exports.PROJECT_ICON_EXTS = ["png", "jpg", "webp"];
301
297
  /**
package/dist/render.js CHANGED
@@ -4714,8 +4714,8 @@ a:hover { text-decoration: underline; }
4714
4714
  別の切れ方をすると「設定したのと違う絵が出ている」と読まれる */
4715
4715
  .site-icon-slot { position: relative; flex: 0 0 auto; display: flex; align-items: center; }
4716
4716
  /* 大きさは案件名(18px・太字)の文字の高さに揃える(0.12.25 の 28px はオーナー実機で
4717
- 「小さくて見にくい」)。**丸は overflow: hidden にしない**——右下の「+」バッジを
4718
- 丸の外へ少しはみ出させるため。画像側(.site-icon-img)を丸く切る */
4717
+ 「小さくて見にくい」)。画像側(.site-icon-img)を丸く切る。
4718
+ 🔴 丸は見せるだけで押せない(仕様 common.screen-ops)ので、ホバーの効果・カーソル・メニューは持たない */
4719
4719
  .site-icon {
4720
4720
  position: relative;
4721
4721
  width: 36px; height: 36px; border-radius: 50%;
@@ -4729,59 +4729,7 @@ a:hover { text-decoration: underline; }
4729
4729
  display: flex; align-items: center; justify-content: center;
4730
4730
  width: 100%; height: 100%; border-radius: 50%;
4731
4731
  }
4732
- /* **「+」はホバー時だけ、右下に小さく重ねる**(常時は出さない)。
4733
- 受け口が無い案件(span.site-icon)には付かない(置けない口に誘いを出さない) */
4734
- .site-icon-plus {
4735
- position: absolute; right: -2px; bottom: -2px;
4736
- width: 16px; height: 16px; border-radius: 50%;
4737
- display: flex; align-items: center; justify-content: center;
4738
- background: var(--accent); color: #fff; font-size: 12px; font-weight: 700; line-height: 1;
4739
- box-shadow: 0 0 0 2px #fff;
4740
- opacity: 0; transition: opacity 0.12s ease;
4741
- }
4742
- button.site-icon:hover .site-icon-plus,
4743
- button.site-icon[aria-expanded="true"] .site-icon-plus { opacity: 1; }
4744
- /* 設定済みの丸は画像だけ。**押せることはホバーで出す**——常時ラベルを重ねると
4745
- 選んだロゴが読めなくなる(アイコンを設定した意味が薄れる) */
4746
- .site-icon-hover {
4747
- position: absolute; top: 0; left: 0; right: 0; bottom: 0; border-radius: 50%;
4748
- display: flex; align-items: center; justify-content: center;
4749
- background: rgba(0, 0, 0, 0.55); color: #fff;
4750
- font-size: 11px; font-weight: 600; line-height: 1;
4751
- opacity: 0; transition: opacity 0.12s ease;
4752
- }
4753
- button.site-icon:hover .site-icon-hover,
4754
- button.site-icon[aria-expanded="true"] .site-icon-hover { opacity: 1; }
4755
- /* **押せることはホバーで示す。** 受け口が無い案件の丸は span なので、この効果は付かない
4756
- ——押せない丸に指差しカーソルやリングを出すのが、画面の嘘のうちで一番たちが悪い */
4757
- button.site-icon { cursor: pointer; }
4758
- button.site-icon:hover, button.site-icon[aria-expanded="true"] { box-shadow: 0 0 0 2px var(--accent); }
4759
- /* **保存中(#378)は丸の上に .busy-overlay(共通部品)が載る。** その間はホバーの「変更」「+」
4760
- もリングも出さない(スピナーと重なって読めない・押せるように見せない)。
4761
- ホバー規則と同じ強さなので、**この行はホバー規則より後に置く** */
4762
- button.site-icon[aria-busy="true"] { cursor: wait; box-shadow: none; }
4763
- button.site-icon[aria-busy="true"] .site-icon-hover,
4764
- button.site-icon[aria-busy="true"] .site-icon-plus { opacity: 0; }
4765
4732
  .site-icon-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
4766
- /* 丸を押したときだけ出るメニュー。スロットを基準に浮かせる(ヘッダーは sticky なので
4767
- ページを送っても位置がずれない) */
4768
- .site-icon-menu {
4769
- position: absolute; top: 44px; left: 0; z-index: 30; min-width: 220px;
4770
- background: #fff; border: 1px solid var(--border); border-radius: 10px;
4771
- box-shadow: 0 8px 24px rgba(0,0,0,.12); padding: 8px;
4772
- display: flex; flex-direction: column; gap: 6px;
4773
- }
4774
- .site-icon-menu[hidden] { display: none; }
4775
- .site-icon-menu button {
4776
- text-align: left; padding: 8px 10px; border-radius: 8px;
4777
- border: 1px solid var(--border); background: #fff; font-size: 13px; cursor: pointer;
4778
- }
4779
- .site-icon-menu button:hover { background: #f3f6fb; }
4780
- .site-icon-menu button:disabled { opacity: .6; cursor: default; }
4781
- /* 結果の行はメニューの中に置く(丸の横に出すとヘッダーの高さに押し込まれて読めない) */
4782
- .site-icon-status { font-size: 12.5px; padding: 2px 4px; }
4783
- .site-icon-status:empty { display: none; }
4784
- .site-icon-status-error { color: #b91c1c; }
4785
4733
  .record-count { color: var(--muted); font-size: 13px; }
4786
4734
  .ask-ai-btn {
4787
4735
  margin-left: auto; flex: 0 0 auto;
@@ -8479,7 +8427,7 @@ ${story_1.splitByNameLinks.toString()}
8479
8427
 
8480
8428
  var sec = el("section", { class: "tut-section" });
8481
8429
  sec.appendChild(el("h2", { class: "tut-section-head", text: "はじめに — このプロジェクトを掴む" + challenges.length + "ステップ" }));
8482
- sec.appendChild(el("p", { class: "tut-intro", text: "AIS Viewer と Claude Code を使ってこのプロジェクトを掴むための" + challenges.length + "ステップです。各ボタンを押すと、質問が入力済みの状態で Claude Code Web が開きます(このリポジトリへのアクセス権が必要です)。" }));
8430
+ sec.appendChild(el("p", { class: "tut-intro", text: "ビューアと Claude Code を使ってこのプロジェクトを掴むための" + challenges.length + "ステップです。各ボタンを押すと、質問が入力済みの状態で Claude Code Web が開きます(このリポジトリへのアクセス権が必要です)。" }));
8483
8431
 
8484
8432
  // 進捗ヘッダ({完了数}/5・バー・全完了で祝福)
8485
8433
  var doneCount = challenges.filter(function (c) { return tutIsDone(c.n); }).length;
@@ -8518,121 +8466,10 @@ ${story_1.splitByNameLinks.toString()}
8518
8466
 
8519
8467
  // ---------- 案件アイコン(#348) ----------
8520
8468
  //
8521
- // ポータルの案件一覧はカードにアイコンを出す。置き場はこの案件のリポジトリの
8522
- // Cortex/icon.<ext> で、**設定する口はこのビューアにしかない**——ポータル側に
8523
- // 置くと「誰の案件か」の判定を先に作らねばならず、案件を知っている人が
8524
- // その場で直せる形にならない。
8525
- //
8526
- // **出す場所はヘッダーの案件名の左**(0.12.24 の Home 本文から移した・#348)。
8527
- // ヘッダーは全タブ共通なので、どのタブを開いていても同じ場所に居る。
8528
- // 押せる口はメニューの中に畳む——常時見えるボタンは、たまにしか使わない操作には重い。
8529
- //
8530
- // 上限はサーバ側(logic.mjs の PROJECT_ICON_MAX_BYTES)と同じ値。画面の門は
8531
- // 「送る前に断る」ためのもので、正本はサーバ側
8532
- var PROJECT_ICON_MAX_BYTES = 1024 * 1024;
8533
-
8534
- /* ---- 反映待ち(pending)----------------------------------------------------
8535
- * 静的サイトなので、保存の commit が入っても**次のビルドまで SiteData は古いまま**。
8536
- * 何もしないと「入れたのにまだ頭文字のまま」「外したのにまだ絵が出る」と見える。
8537
- * 0.12.26 まではその待ち時間を「数分後に反映されます」の文で説明していたが、
8538
- * オーナー実機で却下された(待たされている事実は説明されても消えない)。
8539
- *
8540
- * そこで**削除のトゥームストーンと同じ型**で、保存できたことをこのブラウザに覚える。
8541
- * 覚えるのは押した本人のブラウザだけで、他の端末・他人には次のビルドで届く。
8542
- *
8543
- * ⚠ **キーは案件キー**(リポジトリのスラグではない)。同じオリジンに配られる
8544
- * ポータルの一覧カードが、同じキーでこの仮画像を読んで先に出すため。
8545
- *
8546
- * ⚠ **いつまでも仮画像を出し続けない。** 本物が焼き直された後も残していると、
8547
- * 別の画像に差し替えたつもりの人に、いつまでも古い仮画像が見え続ける
8548
- * (破棄の条件は iconPendingStale=トゥームストーンと同じ判定)。
8549
- *
8550
- * ⚠ 反映待ちの入れ物に p という変数名を使わないこと。fleet-passthrough の検査は
8551
- * その名前のドット参照を「艦隊データの欄」として走査するので、
8552
- * ここで使うと build.ts の受け渡し漏れに化けて赤くなる
8553
- */
8554
- function iconPendingKey() {
8555
- return SITE.intake ? "cortex-icon-pending:" + SITE.intake.projectKey : "";
8556
- }
8557
- /** 覚えている反映待ち(無い・壊れている・形が違うなら null) */
8558
- function readIconPending() {
8559
- var key = iconPendingKey();
8560
- if (!key) return null;
8561
- try {
8562
- var raw = localStorage.getItem(key);
8563
- if (!raw) return null;
8564
- var pend = JSON.parse(raw);
8565
- if (!pend || typeof pend !== "object") return null;
8566
- // 形の版と案件キーは**ポータルの読み側(usePendingProjectIcon)と同じ検査**にする。
8567
- // 将来 v:2 を出したとき、古い画面が新しい形を読み違えないため
8568
- if (pend.v !== 1 || pend.projectKey !== SITE.intake.projectKey) return null;
8569
- if (pend.kind === "remove") return pend;
8570
- // set は仮画像そのものが要る。**data:image/ 以外は載せない**(外部URLを持ち込まない・#110。
8571
- // ポータル側と同じ検査)
8572
- if (pend.kind === "set" && typeof pend.dataUrl === "string" && pend.dataUrl.indexOf("data:image/") === 0) return pend;
8573
- return null;
8574
- } catch (e) { return null; }
8575
- }
8576
- function writeIconPending(entry) {
8577
- var key = iconPendingKey();
8578
- if (!key) return;
8579
- // 1MB の画像を data: にすると 1.4MB 前後。localStorage の枠を超えることがあるので、
8580
- // 失敗しても操作は成功したまま進める(次のビルドで本物が出る)
8581
- try { localStorage.setItem(key, JSON.stringify(entry)); } catch (e) {}
8582
- }
8583
- function clearIconPending() {
8584
- var key = iconPendingKey();
8585
- if (!key) return;
8586
- try { localStorage.removeItem(key); } catch (e) {}
8587
- }
8588
- /**
8589
- * この反映待ちはもう用済みか(=保存より後のビルドが来ている)。
8590
- * **判定はトゥームストーン(tombstoneStale)と同じ**——同じ性質の待ちなので、
8591
- * 破棄の条件を2つ持つと、片方だけ直したときに挙動が割れる。
8592
- */
8593
- function iconPendingStale(pend, nowMs) {
8594
- if (!pend) return false;
8595
- return pendingStale(pend.at, pend.gen0, nowMs);
8596
- }
8597
- /** いま覚えている反映待ちを記録する(保存・解除に共通の形) */
8598
- function rememberIconPending(kind, dataUrl) {
8599
- var entry = { v: 1, projectKey: SITE.intake ? SITE.intake.projectKey : "", kind: kind, at: Date.now(), gen0: SITE.generatedAt || "" };
8600
- if (dataUrl) entry.dataUrl = dataUrl;
8601
- writeIconPending(entry);
8602
- }
8603
- /** 選んだ画像を data: にする。読めなければ空文字(仮画像は出さず、本物のビルドを待つ) */
8604
- function fileToDataUrl(file) {
8605
- return new Promise(function (resolve) {
8606
- try {
8607
- var r = new FileReader();
8608
- r.onload = function () { resolve(typeof r.result === "string" ? r.result : ""); };
8609
- r.onerror = function () { resolve(""); };
8610
- r.readAsDataURL(file);
8611
- } catch (e) { resolve(""); }
8612
- });
8613
- }
8614
-
8615
- /** いま画面に出すべきアイコン(無ければ null)。**反映待ちがビルド時の値に優先する** */
8616
- function projectIconCurrent() {
8617
- var pend = readIconPending();
8618
- if (pend) return pend.kind === "remove" ? null : { file: "", src: pend.dataUrl };
8619
- var q = SITE.projectIcon;
8620
- // ビルドが同梱した画像はサイト内の相対パス。**外部URLは載せない**
8621
- return q ? { file: q.file, src: q.file } : null;
8622
- }
8623
-
8624
- /**
8625
- * 名前から拡張子を引く(jpeg は jpg へ寄せる)。許可外・拡張子なしは null。
8626
- * **サーバ側(logic.mjs の projectIconExtOf)と同じ判断**を画面でも先にやるのは、
8627
- * 通らないと分かっているファイルを1MB 送ってから断らないため
8628
- */
8629
- function projectIconExtOfName(name) {
8630
- var m = /\\.([A-Za-z0-9]+)$/.exec(String(name || ""));
8631
- if (!m) return null;
8632
- var e = m[1].toLowerCase();
8633
- if (e === "jpeg") e = "jpg";
8634
- return e === "png" || e === "jpg" || e === "webp" ? e : null;
8635
- }
8469
+ // ポータルの案件一覧はカードにアイコンを出す。置き場はこの案件のリポジトリの Cortex/icon.<ext> で、
8470
+ // ビルドが同梱した画像(SITE.projectIcon)を**ヘッダーの案件名の左に見せるだけ**にする。
8471
+ // 🔴 **画面からアイコンを置く・外す操作は持たない**(仕様 common.screen-ops「案件のアイコンの操作は持たない」・cortex-engine #1781)。
8472
+ // 変えたいときはリポジトリの Cortex/icon.<ext> を GitHub で直す
8636
8473
 
8637
8474
  // いま出ているトーストを消すタイマ。**1本だけ持つ**——連続で出したときに
8638
8475
  // 古いタイマが残っていると、2つ目が出た 0.2 秒後に消える
@@ -8676,35 +8513,6 @@ ${story_1.splitByNameLinks.toString()}
8676
8513
  });
8677
8514
  }
8678
8515
 
8679
- // ヘッダーの丸のメニューが開いているか。**DOM ではなくこの変数が正本。**
8680
- // 保存・解除のあとに丸を描き直すので、開閉を DOM だけに持たせると描き直しで畳まれ、
8681
- // 「✓ 保存しました」と保存先のリンクが一度も見えないまま消える
8682
- var siteIconMenuOpen = false;
8683
- // document に付ける開閉のリスナーは1組だけ。描き直しのたびに足すと Escape 1回で
8684
- // 同じ処理が何度も走る(見た目は同じなので気づけない)
8685
- var siteIconGlobalBound = false;
8686
- // 保存中か(画像を選ぶ/変える/外す の3段が走っている間)。**DOM ではなくこの変数が正本**
8687
- // (#378)。描き直しで丸が作り直されても、renderSiteIcon がこれを見てオーバーレイを付け直す。
8688
- // setProjectIcon / removeProjectIcon の入口で見て、走っている間の2回目を捨てる(二重送信防止)
8689
- var siteIconSaving = false;
8690
- // いま丸に載っているオーバーレイの外し方(無ければ null)
8691
- var siteIconBusyEnd = null;
8692
-
8693
- /**
8694
- * 丸の保存中の見た目を付ける/外す。丸は disabled にして押せなくする
8695
- * (メニューの開閉もここで止まる。丸のクリックは siteIconSaving でも見る)
8696
- */
8697
- function setSiteIconBusy(circle, on) {
8698
- if (siteIconBusyEnd) { siteIconBusyEnd(); siteIconBusyEnd = null; }
8699
- if (!circle) return;
8700
- if (on) {
8701
- siteIconBusyEnd = beginOverlaySpinner(circle, "保存中…");
8702
- circle.disabled = true;
8703
- } else {
8704
- circle.disabled = false;
8705
- }
8706
- }
8707
-
8708
8516
  /**
8709
8517
  * 案件名の頭文字1字。**ポータルの ProjectIcon.tsx(initialOf)と同じ取り方**にする
8710
8518
  * ——同じ案件がポータルとビューアで違う字になると「別の案件を見ている」と読まれる。
@@ -8715,236 +8523,36 @@ ${story_1.splitByNameLinks.toString()}
8715
8523
  return Array.from(String(SITE.title || "").trim())[0] || "?";
8716
8524
  }
8717
8525
 
8718
- /** 丸の中の頭文字(薄くするかどうかは CSS が丸の種類で決める) */
8526
+ /** 丸の中の頭文字 */
8719
8527
  function siteIconInitialEl() {
8720
8528
  return el("span", { class: "site-icon-initial", text: siteInitial() });
8721
8529
  }
8722
8530
 
8723
- /** メニューの開閉。描き直さずに hidden と aria-expanded だけ動かす */
8724
- function setSiteIconMenu(open) {
8725
- siteIconMenuOpen = !!open;
8726
- var slot = document.getElementById("site-icon-slot");
8727
- if (!slot) return;
8728
- var menu = slot.querySelector(".site-icon-menu");
8729
- if (menu) {
8730
- if (siteIconMenuOpen) menu.removeAttribute("hidden");
8731
- else menu.setAttribute("hidden", "");
8732
- }
8733
- var circle = slot.querySelector("button.site-icon");
8734
- if (circle) circle.setAttribute("aria-expanded", siteIconMenuOpen ? "true" : "false");
8735
- }
8736
-
8737
8531
  /**
8738
8532
  * ヘッダーの案件名の左の丸を描く(#site-icon-slot の中身を作り直す)。
8739
8533
  *
8740
8534
  * **丸は常に出す**(未設定なら頭文字)——ポータルの一覧と同じ顔をここでも見せる。
8741
- * **押せる形にするのは受け口(intake)がある案件だけ。** 無ければ span にして
8742
- * メニューもファイル選択も付けない——押して何も起きない口は「壊れている」と読まれる。
8743
- *
8535
+ * 🔴 **押せる形にしない**(受け口がある案件でも span・メニューもファイル選択も付けない)。仕様 common.screen-ops
8744
8536
  */
8745
8537
  function renderSiteIcon() {
8746
8538
  var slot = document.getElementById("site-icon-slot");
8747
8539
  if (!slot) return;
8748
- var intake = SITE.intake;
8749
- // **本物が追いついたら仮画像を捨てる。** 残したままだと、次に差し替えた人にも
8750
- // 古い仮画像が見え続ける(削除のトゥームストーンと同じ捨て方)
8751
- var pend = readIconPending();
8752
- if (pend && iconPendingStale(pend, pendingSiteTimeMs(SITE.generatedAt))) clearIconPending();
8753
- var cur = projectIconCurrent();
8540
+ var q = SITE.projectIcon;
8754
8541
  slot.textContent = "";
8755
-
8756
- var circle = el(intake ? "button" : "span", { class: "site-icon" });
8757
- if (intake) {
8758
- circle.setAttribute("type", "button");
8759
- circle.setAttribute("aria-haspopup", "menu");
8760
- circle.setAttribute("aria-expanded", siteIconMenuOpen ? "true" : "false");
8761
- // 未設定は「+」が見えているので、説明は「変更」ではなく置ける話にしない——
8762
- // 読み上げと tooltip は、いまの状態でできることをそのまま言う
8763
- var label = cur ? "プロジェクトアイコンを変更/外す" : "プロジェクトアイコンを設定";
8764
- circle.setAttribute("title", label);
8765
- circle.setAttribute("aria-label", label);
8766
- } else {
8767
- // すぐ隣に案件名が文字で出ているので、読み上げで名前を2回言わせない
8768
- circle.setAttribute("aria-hidden", "true");
8769
- }
8770
- if (cur) {
8771
- // **設定済みは画像だけ。** 頭文字も「+」も重ねない(選んだロゴが読めなくなる)
8772
- var img = el("img", { class: "site-icon-img", src: cur.src, alt: "" });
8773
- // **onerror 属性で書かない(CSP でインラインハンドラは動かない)。**
8774
- // 仮画像の data: が配信先の img-src で拒まれることがあるので、そのときは頭文字に倒す
8542
+ // すぐ隣に案件名が文字で出ているので、読み上げで名前を2回言わせない
8543
+ var circle = el("span", { class: "site-icon", "aria-hidden": "true" });
8544
+ if (q && q.file) {
8545
+ // **設定済みは画像だけ**(頭文字を重ねない)。ビルドが同梱した画像はサイト内の相対パスで、外部URLは載せない
8546
+ var img = el("img", { class: "site-icon-img", src: q.file, alt: "" });
8547
+ // **onerror 属性で書かない(CSP でインラインハンドラは動かない)。** 読めなければ頭文字に倒す
8775
8548
  img.addEventListener("error", function () {
8776
8549
  if (img.parentNode) img.parentNode.replaceChild(siteIconInitialEl(), img);
8777
8550
  });
8778
8551
  circle.appendChild(img);
8779
- // 押せる案件だけ、ホバーで「変更」と出す(見た目は CSS の opacity で切り替える)
8780
- if (intake) circle.appendChild(el("span", { class: "site-icon-hover", "aria-hidden": "true", text: "変更" }));
8781
8552
  } else {
8782
8553
  circle.appendChild(siteIconInitialEl());
8783
- // **未設定の押せる丸には、ホバー時だけ右下に小さく「+」を出す**(CSS の opacity)。
8784
- // 頭文字ははっきり出す——0.12.25 の「薄い頭文字+大きな+」は「+だけの丸」に見えた
8785
- if (intake) circle.appendChild(el("span", { class: "site-icon-plus", "aria-hidden": "true", text: "+" }));
8786
8554
  }
8787
8555
  slot.appendChild(circle);
8788
- if (!intake) return;
8789
- // 保存中に描き直されても、オーバーレイと押せない状態を引き継ぐ(正本は siteIconSaving)。
8790
- // いまの呼び出し経路(起動時1回・ui.busy(false) の後の redraw)では保存中に来ないので
8791
- // 防御。将来タブ切り替え等でヘッダーを描き直すようになっても、ぐるぐるが消えない
8792
- if (siteIconSaving) setSiteIconBusy(circle, true);
8793
-
8794
- circle.addEventListener("click", function () {
8795
- // 保存中は丸が disabled なので click 自体が飛ばない(ブラウザの既定)。これは防御
8796
- if (siteIconSaving) return;
8797
- setSiteIconMenu(!siteIconMenuOpen);
8798
- });
8799
-
8800
- var menu = el("div", { class: "site-icon-menu", role: "menu" });
8801
- if (!siteIconMenuOpen) menu.setAttribute("hidden", "");
8802
-
8803
- var ui = {
8804
- // 描き直すと押していたボタンが DOM から消えてフォーカスが body に落ちるので、
8805
- // 丸へ戻す(キーボードの人が位置を失わない。Escape は document 側なので効く)
8806
- redraw: function () {
8807
- renderSiteIcon();
8808
- ui.refocus();
8809
- },
8810
- // 丸へフォーカスを戻す。失敗のあとも呼ぶ——押していた項目は disabled にした瞬間に
8811
- // フォーカスを失って body に落ちるので、戻さないとキーボードの人がやり直す位置を失う
8812
- refocus: function () {
8813
- var s = document.getElementById("site-icon-slot");
8814
- var c = s && s.querySelector("button.site-icon");
8815
- if (c && typeof c.focus === "function") c.focus();
8816
- },
8817
- input: null,
8818
- buttons: [],
8819
- };
8820
- // 送信中は全部を止める(#378: 丸にオーバーレイ+スピナー・丸とメニューを押せなくする)。
8821
- // **元から押せない項目は押せないまま戻す**——
8822
- // 未設定の「画像を外す」が、失敗して戻ったときだけ押せるようになるのを防ぐ
8823
- ui.busy = function (flag) {
8824
- siteIconSaving = !!flag;
8825
- ui.buttons.forEach(function (b) { b.disabled = flag ? true : !!b.lockedOff; });
8826
- var s = document.getElementById("site-icon-slot");
8827
- setSiteIconBusy(s && s.querySelector("button.site-icon"), siteIconSaving);
8828
- };
8829
-
8830
- var input = el("input", { type: "file", accept: "image/png,image/jpeg,image/webp", hidden: "" });
8831
- ui.input = input;
8832
- input.addEventListener("change", function () {
8833
- var f = input.files && input.files[0];
8834
- // **読んだらすぐ value を戻す**(成功時だけ戻す形だと、形式で断られた直後に
8835
- // 同じファイルを選び直しても change が飛ばず、直す経路がここで死ぬ)
8836
- try { input.value = ""; } catch (e) {}
8837
- if (f) setProjectIcon(f, ui);
8838
- });
8839
- // **項目は常にこの2つ・この順。** 状態で数が変わると、押す場所が毎回動く
8840
- // (未設定のときは「外す」を押せなくするだけで、消しはしない)
8841
- var choose = el("button", { class: "site-icon-choose", type: "button", role: "menuitem", text: cur ? "画像を変える" : "画像を選ぶ" });
8842
- choose.addEventListener("click", function () { input.click(); });
8843
- ui.buttons.push(choose);
8844
- menu.appendChild(choose);
8845
- var remove = el("button", { class: "site-icon-remove", type: "button", role: "menuitem", text: "画像を外す" });
8846
- if (!cur) {
8847
- remove.disabled = true;
8848
- remove.lockedOff = true;
8849
- }
8850
- remove.addEventListener("click", function () { removeProjectIcon(ui); });
8851
- ui.buttons.push(remove);
8852
- menu.appendChild(remove);
8853
- menu.appendChild(input);
8854
- slot.appendChild(menu);
8855
-
8856
- if (!siteIconGlobalBound) {
8857
- siteIconGlobalBound = true;
8858
- // 外を押したら閉じる。**スロットの中では閉じない**——「画像を選ぶ」を押した
8859
- // 瞬間に畳まれると、選び終えたときに結果を書く場所が無い
8860
- document.addEventListener("click", function (ev) {
8861
- if (!siteIconMenuOpen) return;
8862
- var s = document.getElementById("site-icon-slot");
8863
- if (s && s.contains(ev.target)) return;
8864
- setSiteIconMenu(false);
8865
- });
8866
- document.addEventListener("keydown", function (ev) {
8867
- if (siteIconMenuOpen && (ev.key === "Escape" || ev.key === "Esc")) setSiteIconMenu(false);
8868
- });
8869
- }
8870
- }
8871
-
8872
- /**
8873
- * 画像を1枚選んで保存する。
8874
- *
8875
- * **画面側でも門を張る。** サーバ側(planProjectIconSet)が正本だが、ここで断れる
8876
- * ものを送ると、1MB を上げきってから「大きすぎます」と言うことになる
8877
- */
8878
- async function setProjectIcon(file, ui) {
8879
- // 走っている間の2回目は捨てる(丸もメニューも止めてあるが、正本はこの変数)
8880
- if (siteIconSaving) return;
8881
- function fail(msg) { showToast("✗ " + msg, true); ui.busy(false); ui.refocus(); }
8882
- // 画面側の門。**サーバ側と同じ文言**で、送る前に断る(fail で「✗ 」を揃える)
8883
- if (!projectIconExtOfName(file.name)) return fail("PNG・JPEG・WebP の画像を選んでください");
8884
- if (file.size > PROJECT_ICON_MAX_BYTES) return fail("1MB までの画像を選んでください");
8885
- if (file.size === 0) return fail("画像が空です(0 バイト)。別の画像を選んでください");
8886
-
8887
- var intake = SITE.intake;
8888
- var ctype = file.type || "application/octet-stream";
8889
- // 押した瞬間からぐるぐる(#378)。成功なら redraw で新しい丸に、失敗なら fail で元の丸に戻る
8890
- ui.busy(true);
8891
-
8892
- var pres;
8893
- try {
8894
- pres = await intakeJson(callIntake("presign", {
8895
- projectKey: intake.projectKey, filename: file.name, contentType: ctype, purpose: "project-icon",
8896
- }));
8897
- } catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
8898
- if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました");
8899
-
8900
- // 署名付きURLへの PUT だけは intake ではないので素の fetch(submitIntake と同じ)
8901
- var put;
8902
- try {
8903
- put = await fetch(pres.j.url, { method: "PUT", headers: { "content-type": ctype }, body: file });
8904
- } catch (net2) { return fail("アップロードに失敗しました(もう一度お試しください)"); }
8905
- if (!put.ok) return fail("アップロードに失敗しました(" + put.status + ")");
8906
-
8907
- var saved;
8908
- try {
8909
- saved = await intakeJson(callIntake("project-icon-set", {
8910
- projectKey: intake.projectKey, key: pres.j.key, filename: file.name,
8911
- }));
8912
- } catch (net3) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
8913
- if (!saved.ok) return fail((saved.j && saved.j.message) || "保存できませんでした");
8914
-
8915
- // **選んだ画像そのものを反映待ちに置く。** blob: にしないのは、
8916
- // ポータルの一覧カード(別のページ)が同じ画像を読むため——blob: は
8917
- // 作ったページの中でしか開けない
8918
- var dataUrl = await fileToDataUrl(file);
8919
- // **読めなかったら反映待ちを書かない**(サーバ側の保存は済んでいるので、仮画像だけ
8920
- // 諦めて次のビルドを待つ)。空の set を書くと、直前の有効な反映待ちを上書きして消す
8921
- if (dataUrl) rememberIconPending("set", dataUrl);
8922
- // **同じファイルをもう一度選べるようにする。** value を戻さないと、選び直しても
8923
- // change が飛ばない(形式で断られた直後に選び直す経路がここで死ぬ)
8924
- if (ui.input) ui.input.value = "";
8925
- ui.busy(false);
8926
- // 丸がその場で変わるので、メニューは畳んでよい(結果はトーストが伝える)
8927
- setSiteIconMenu(false);
8928
- ui.redraw();
8929
- showToast("保存しました", false);
8930
- }
8931
-
8932
- /** 画像を外す。**確認は出さない**——押し間違えても、もう一度選び直せば戻る操作 */
8933
- async function removeProjectIcon(ui) {
8934
- // 保存中はメニューの項目が disabled なので普通は来ない(setProjectIcon と同じ防御)
8935
- if (siteIconSaving) return;
8936
- function fail(msg) { showToast("✗ " + msg, true); ui.busy(false); ui.refocus(); }
8937
- ui.busy(true);
8938
- var res;
8939
- try {
8940
- res = await intakeJson(callIntake("project-icon-remove", { projectKey: SITE.intake.projectKey }));
8941
- } catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
8942
- if (!res.ok) return fail((res.j && res.j.message) || "外せませんでした");
8943
- rememberIconPending("remove", "");
8944
- ui.busy(false);
8945
- setSiteIconMenu(false);
8946
- ui.redraw();
8947
- showToast("外しました", false);
8948
8556
  }
8949
8557
 
8950
8558
  // ---------- ホーム(着地画面: Markdownをそのまま表示) ----------
@@ -20512,9 +20120,11 @@ ${story_1.splitByNameLinks.toString()}
20512
20120
  var allEdges = graph.edges.filter(function (e) {
20513
20121
  return graphState.showWeak || !WEAK_RELS[e.rel];
20514
20122
  });
20515
- // 弱い辺を消したとき、レコード以外の孤立ノードは隠す
20123
+ // レコード以外の点は、**ゆるいつながりを外す前**の線(消した記録の線は graphWithoutDeleted が落としてある)が 1 本でもあれば出す。
20124
+ // 🔴 **表示を絞る切り替えで根拠の点を隠さない**(仕様 graph.evidence-always)——切り替えで変わるのは線だけ。
20125
+ // group では見分けない(元のツールが無い根拠も group phantom で作られる)。消した記録だけが指していた点は線が無いので出ない
20516
20126
  var used = {};
20517
- allEdges.forEach(function (e) { used[e.from] = true; used[e.to] = true; });
20127
+ graph.edges.forEach(function (e) { used[e.from] = true; used[e.to] = true; });
20518
20128
  // ビルド時に算出済みの座標(n.x/n.y)があればそれを使う=ウォームアップ不要で開いた瞬間に整列済み。
20519
20129
  var preLaidOut = graph.nodes.some(function (n) { return typeof n.x === "number"; });
20520
20130
  var nodes = graph.nodes
@@ -20525,6 +20135,8 @@ ${story_1.splitByNameLinks.toString()}
20525
20135
  x: typeof n.x === "number" ? n.x : 0, y: typeof n.y === "number" ? n.y : 0,
20526
20136
  vx: 0, vy: 0, phase: i * 2.399 };
20527
20137
  });
20138
+ // 見えている点の数(開発者ツールとテストが見る。件数の表示はヘッダーから外した)
20139
+ canvas.setAttribute("data-points", String(nodes.length));
20528
20140
  // Gold層を中心に据え、グレーのコンテキストはその外周に配置する(ハブ&スポーク)
20529
20141
  var RING = Math.max(200, 26 * Math.sqrt(nodes.length));
20530
20142
  var byNode = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takagaki/cortex-decisions-viewer",
3
- "version": "0.12.160",
3
+ "version": "0.12.162",
4
4
  "description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,