@takagaki/cortex-decisions-viewer 0.12.22 → 0.12.24
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 +58 -0
- package/dist/render.js +461 -139
- package/package.json +1 -1
package/dist/build.js
CHANGED
|
@@ -36,7 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.PROJECT_ICON_EXTS = void 0;
|
|
39
40
|
exports.sanitizeBody = sanitizeBody;
|
|
41
|
+
exports.findProjectIcon = findProjectIcon;
|
|
40
42
|
exports.readFleetSources = readFleetSources;
|
|
41
43
|
exports.readHarnessSchedules = readHarnessSchedules;
|
|
42
44
|
exports.readHarnessShelf = readHarnessShelf;
|
|
@@ -247,6 +249,48 @@ function resolveKnowledgeRoot(decisionsDirAbs) {
|
|
|
247
249
|
}
|
|
248
250
|
return null;
|
|
249
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* 案件アイコン(#348)で受け付ける拡張子。**探索の順でもある**(2枚あれば先頭を使う)。
|
|
254
|
+
*
|
|
255
|
+
* **Lambda 側(infra/lambda/intake/logic.mjs の PROJECT_ICON_EXTS)と同じ並びにすること。**
|
|
256
|
+
* 片方だけ増やす・並べ替えると、intake が書いた名前を build が拾わない(=設定したのに
|
|
257
|
+
* 画面に出ない)か、2枚あるときに intake と build が別の1枚を選ぶ。
|
|
258
|
+
* 突き合わせは viewer/test/project-icon-parity.test.mjs が機械的に見張る。
|
|
259
|
+
*
|
|
260
|
+
* `.jpeg` は入れない——intake は `.jpeg` を `jpg` へ正規化して書くので、
|
|
261
|
+
* リポジトリにその名前は生まれない(拾うと「どちらが正か」が2つになる)。
|
|
262
|
+
*/
|
|
263
|
+
exports.PROJECT_ICON_EXTS = ["png", "jpg", "webp"];
|
|
264
|
+
/**
|
|
265
|
+
* `Cortex/icon.<ext>` を探す。無ければ undefined(キーごと出さないため)。
|
|
266
|
+
*
|
|
267
|
+
* **2枚以上あっても落とさない。** 人が GitHub 側で直接置くと起こりうる形なので、
|
|
268
|
+
* 固定順の先頭1枚に倒し、どれを使ったかをビルドログに残す(黙って選ぶと
|
|
269
|
+
* 「差し替えたのに変わらない」の原因が画面からもログからも分からなくなる)。
|
|
270
|
+
*/
|
|
271
|
+
async function findProjectIcon(knowledgeRoot) {
|
|
272
|
+
if (!knowledgeRoot)
|
|
273
|
+
return undefined;
|
|
274
|
+
const found = [];
|
|
275
|
+
for (const ext of exports.PROJECT_ICON_EXTS) {
|
|
276
|
+
const file = `icon.${ext}`;
|
|
277
|
+
const srcAbs = path.join(knowledgeRoot, file);
|
|
278
|
+
try {
|
|
279
|
+
const st = await node_fs_1.promises.stat(srcAbs);
|
|
280
|
+
if (st.isFile())
|
|
281
|
+
found.push({ file, repoPath: `${path.basename(knowledgeRoot)}/${file}`, srcAbs });
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// 無いだけ(探索なので握りつぶす)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (!found.length)
|
|
288
|
+
return undefined;
|
|
289
|
+
if (found.length > 1) {
|
|
290
|
+
console.warn(`案件アイコンが複数あります(${found.map((f) => f.file).join(", ")})。${found[0].file} だけを使います`);
|
|
291
|
+
}
|
|
292
|
+
return found[0];
|
|
293
|
+
}
|
|
250
294
|
/**
|
|
251
295
|
* スペースごとの Backlog キーの登録状況で、画面へ運んでよい値。
|
|
252
296
|
*
|
|
@@ -1812,6 +1856,8 @@ async function build(opts) {
|
|
|
1812
1856
|
// 走るのは latestGeneratedPaths と同じ **リポジトリルート**(process.cwd())——
|
|
1813
1857
|
// Decisions ディレクトリで実行すると pathspec に一致せず常に空になる
|
|
1814
1858
|
const aiHistory = (0, repo_1.collectAiChangeHistory)(process.cwd());
|
|
1859
|
+
// 案件アイコン(#348)。Cortex構成でなければ undefined(旧レイアウトには置き場が無い)
|
|
1860
|
+
const projectIcon = await findProjectIcon(knowledgeRoot);
|
|
1815
1861
|
const site = {
|
|
1816
1862
|
title: opts.title,
|
|
1817
1863
|
generatedAt: opts.generatedAt,
|
|
@@ -1862,6 +1908,9 @@ async function build(opts) {
|
|
|
1862
1908
|
// 同上(短縮記法にしない)。**undefined のまま渡すのが仕様**——「棚が無い」と
|
|
1863
1909
|
// 「棚が空」を画面が言い分けるための材料で、ここで [] へ倒すと区別が消える
|
|
1864
1910
|
harnessShelf: harnessShelf,
|
|
1911
|
+
// 同上(短縮記法にしない)。**undefined ならキーごと出さない**——画面は
|
|
1912
|
+
// 「アイコンがある/無い」をキーの有無で見る(空の器を通さない)
|
|
1913
|
+
projectIcon: projectIcon ? { file: projectIcon.file, repoPath: projectIcon.repoPath } : undefined,
|
|
1865
1914
|
// null(無い・壊れ・version違い)は undefined へ落とす——SiteData の
|
|
1866
1915
|
// 「知らないことはキーが無いことで表す」に合わせる(空の器を通さない)
|
|
1867
1916
|
// 空の器は通さない(0件なら undefined = キーごと出さない)
|
|
@@ -1869,6 +1918,15 @@ async function build(opts) {
|
|
|
1869
1918
|
};
|
|
1870
1919
|
// サムネイル(デザイン画面等)をサイトに同梱し、ノードのパスをサイト内相対に書き換える
|
|
1871
1920
|
await node_fs_1.promises.mkdir(outAbs, { recursive: true });
|
|
1921
|
+
// 案件アイコン(#348)を site/ 直下へ同梱する。
|
|
1922
|
+
// **先に3候補を消す。** 出力ディレクトリは丸ごと作り直されないので、
|
|
1923
|
+
// 「置いてビルド → 外してビルド」をすると古い画像がファイルとして残り、
|
|
1924
|
+
// site-data には出ていないのに URL 直打ちで前のアイコンが見えてしまう
|
|
1925
|
+
for (const ext of exports.PROJECT_ICON_EXTS) {
|
|
1926
|
+
await node_fs_1.promises.rm(path.join(outAbs, `icon.${ext}`), { force: true });
|
|
1927
|
+
}
|
|
1928
|
+
if (projectIcon)
|
|
1929
|
+
await node_fs_1.promises.copyFile(projectIcon.srcAbs, path.join(outAbs, projectIcon.file));
|
|
1872
1930
|
let thumbIndex = 0;
|
|
1873
1931
|
for (const node of graph.nodes) {
|
|
1874
1932
|
if (!node.thumb)
|
package/dist/render.js
CHANGED
|
@@ -756,7 +756,7 @@ function sanitizeHarnessShelf(v) {
|
|
|
756
756
|
* 写し忘れても赤くならなかったので、突き合わせは viewer/test/state-vocab-parity.test.mjs。
|
|
757
757
|
*/
|
|
758
758
|
const STATE_STATUSES = {
|
|
759
|
-
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown", "not_in_space", "no_credentials"],
|
|
759
|
+
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown", "not_in_space", "no_credentials", "no_consent", "transcripts_api_disabled", "no_access_policy", "speaker_attribution_off", "folder_not_granted"],
|
|
760
760
|
ingested: ["ok", "failed", "skipped"],
|
|
761
761
|
};
|
|
762
762
|
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "detail", "lastOkAt", "lastFailAt"];
|
|
@@ -943,6 +943,13 @@ function gchatRowsToAdd(externals, rows) {
|
|
|
943
943
|
*
|
|
944
944
|
* **種別(issues / discussions)は登録ファイルの宣言をそのまま使う。** 無いときだけ
|
|
945
945
|
* Issues に倒す——ここで決め打ちすると、Discussions だけを登録した案件のラベルが嘘になる。
|
|
946
|
+
*
|
|
947
|
+
* 🔴 **`origin: "explicit"` を付ける(#354)。** 「登録から削除」は `origin === "explicit"` の
|
|
948
|
+
* 行にだけ出る(導出リポに出すと押しても翌日戻る)。ここで足す行は `dev-sources.json` の
|
|
949
|
+
* `sources`(人が登録簿に書いたもの)からしか来ないので、明示登録であることは事実。
|
|
950
|
+
* 付けないと、**登録した直後の行だけ「登録から削除」が無い**(夜間のまとめ表が追いつく
|
|
951
|
+
* 翌朝まで、読めていない主役の行を外す手段が画面に無い——cortex-test で実際に踏んだ)。
|
|
952
|
+
* Slack の同じ穴を #221 で `chatRowsToAdd` が塞いだのと同型。
|
|
946
953
|
*/
|
|
947
954
|
function devRowsToAdd(externals, rows) {
|
|
948
955
|
const have = {};
|
|
@@ -957,7 +964,7 @@ function devRowsToAdd(externals, rows) {
|
|
|
957
964
|
continue;
|
|
958
965
|
if (have[String(r.ref)])
|
|
959
966
|
continue;
|
|
960
|
-
out.push({ type: r.type || "github-issues", name: r.ref, ref: r.ref });
|
|
967
|
+
out.push({ type: r.type || "github-issues", name: r.ref, ref: r.ref, origin: "explicit" });
|
|
961
968
|
}
|
|
962
969
|
return out;
|
|
963
970
|
}
|
|
@@ -1627,7 +1634,7 @@ function keyMissingText(cap) {
|
|
|
1627
1634
|
*/
|
|
1628
1635
|
const BAD_STATE_STATUSES = {
|
|
1629
1636
|
ingested: ["failed", "skipped"],
|
|
1630
|
-
reachable: ["not_in_channel", "missing_scope", "no_token", "unreachable", "not_in_space", "no_credentials"],
|
|
1637
|
+
reachable: ["not_in_channel", "missing_scope", "no_token", "unreachable", "not_in_space", "no_credentials", "no_consent", "transcripts_api_disabled", "no_access_policy", "speaker_attribution_off", "folder_not_granted"],
|
|
1631
1638
|
};
|
|
1632
1639
|
/**
|
|
1633
1640
|
* タイル1行(と接続グラフの1ノード)の状態を、**正本から導出する**(0.12.0)。
|
|
@@ -2488,6 +2495,30 @@ a:hover { text-decoration: underline; }
|
|
|
2488
2495
|
padding-bottom: 6px; border-bottom: 1px solid var(--border);
|
|
2489
2496
|
}
|
|
2490
2497
|
.home-grounding > .home-grounding-title:first-child { margin-top: 0; }
|
|
2498
|
+
/* ---- 案件アイコン(#348)----
|
|
2499
|
+
Home のタイトルの左に丸い枠を置く。ポータルの一覧カード(ProjectIcon.tsx)と
|
|
2500
|
+
同じ「丸・cover」にする——同じ画像が2つの画面で別の切れ方をすると、
|
|
2501
|
+
「設定したのと違う絵が出ている」と読まれる */
|
|
2502
|
+
.home-head { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
|
|
2503
|
+
.home-title-wrap { flex: 1 1 320px; min-width: 0; }
|
|
2504
|
+
.project-icon { flex: 0 0 auto; }
|
|
2505
|
+
/* 未設定は破線(置き場が空いていることを見せる)・設定済みは実線 */
|
|
2506
|
+
.project-icon-frame {
|
|
2507
|
+
width: 72px; height: 72px; border-radius: 50%; overflow: hidden;
|
|
2508
|
+
border: 2px dashed var(--border); background: #f6f7f9;
|
|
2509
|
+
display: flex; align-items: center; justify-content: center;
|
|
2510
|
+
padding: 0; cursor: pointer;
|
|
2511
|
+
}
|
|
2512
|
+
.project-icon-frame.project-icon-filled { border-style: solid; }
|
|
2513
|
+
/* 受け口が無い案件では押せないので、押せる見た目にしない */
|
|
2514
|
+
.project-icon-frame:not(button) { cursor: default; }
|
|
2515
|
+
.project-icon-img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
2516
|
+
.project-icon-empty { font-size: 11px; color: var(--muted); text-align: center; line-height: 1.3; }
|
|
2517
|
+
.project-icon-actions { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
|
|
2518
|
+
/* 結果の行はタイトルの下へ回す(枠の横に入れると72pxの高さに押し込まれる) */
|
|
2519
|
+
.project-icon-status { flex-basis: 100%; font-size: 13px; }
|
|
2520
|
+
.project-icon-status:empty { display: none; }
|
|
2521
|
+
.project-icon-status-error { color: #b91c1c; }
|
|
2491
2522
|
/* ---- レコード詳細の「AIによる最近の変更」(リポジトリの git 履歴 由来)----
|
|
2492
2523
|
カードは詳細ページの提案(.proposals)と同系色にする——どちらも
|
|
2493
2524
|
「AIがやったこと・言っていることを読む」もので種類が同じ。
|
|
@@ -3326,6 +3357,56 @@ const CLIENT_JS = `
|
|
|
3326
3357
|
var SITE = JSON.parse(document.getElementById("site-data").textContent);
|
|
3327
3358
|
var ADAPTER_ICONS = ${JSON.stringify(ADAPTER_ICONS)};
|
|
3328
3359
|
|
|
3360
|
+
// ---- intake(操作の受け口)への共通の呼び出し(#349)----
|
|
3361
|
+
//
|
|
3362
|
+
// **全 op はここを通す。** 名乗り券(ポータルが出す Cookie の cortex-actor)を body に添えるのは
|
|
3363
|
+
// この1箇所だけ。fetch を直書きすると、その op だけ操作者が記録されない。
|
|
3364
|
+
// 券は document.cookie から**1回だけ**読んでメモリに持つ。site-data にも localStorage にも書かない
|
|
3365
|
+
// (ビルド時の値ではなく、開いた人ごとに違う値なので)。
|
|
3366
|
+
//
|
|
3367
|
+
// **1回だけ読むのは仕様。** ポータルからログアウトしても、開いたままのタブは券の期限まで
|
|
3368
|
+
// 名乗り続ける——署名付き Cookie と同じ性質で、券の寿命がそのまま名乗りの寿命になる。
|
|
3369
|
+
var ACTOR_COOKIE = "cortex-actor";
|
|
3370
|
+
var actorTicketCache;
|
|
3371
|
+
function actorTicket() {
|
|
3372
|
+
if (actorTicketCache !== undefined) return actorTicketCache;
|
|
3373
|
+
actorTicketCache = null;
|
|
3374
|
+
try {
|
|
3375
|
+
var parts = String(document.cookie || "").split(";");
|
|
3376
|
+
for (var i = 0; i < parts.length; i++) {
|
|
3377
|
+
var kv = parts[i].trim();
|
|
3378
|
+
if (kv.indexOf(ACTOR_COOKIE + "=") === 0) { actorTicketCache = kv.slice(ACTOR_COOKIE.length + 1) || null; break; }
|
|
3379
|
+
}
|
|
3380
|
+
} catch (e) { actorTicketCache = null; }
|
|
3381
|
+
return actorTicketCache;
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* intake の op を1回呼ぶ。POST は JSON の body に名乗り券(あれば)を actor として足す。
|
|
3385
|
+
* GET(opts.method === "GET" のとき)はカスタムヘッダも body も付けない(preflight を増やさないため・#198)。
|
|
3386
|
+
* 戻り値は fetch の Promise そのもの(呼び出し側の .then / await / res.json() はそのまま使える)。
|
|
3387
|
+
*/
|
|
3388
|
+
function callIntake(op, body, opts) {
|
|
3389
|
+
var intake = SITE.intake;
|
|
3390
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
3391
|
+
var url = pfx + "op=" + encodeURIComponent(op) + "&t=" + encodeURIComponent(intake.token);
|
|
3392
|
+
var o = opts || {};
|
|
3393
|
+
if (o.method === "GET") return fetch(url, { signal: o.signal });
|
|
3394
|
+
var payload = body && typeof body === "object" ? body : {};
|
|
3395
|
+
var ticket = actorTicket();
|
|
3396
|
+
if (ticket) {
|
|
3397
|
+
var withActor = {};
|
|
3398
|
+
for (var k in payload) if (Object.prototype.hasOwnProperty.call(payload, k)) withActor[k] = payload[k];
|
|
3399
|
+
withActor.actor = ticket;
|
|
3400
|
+
payload = withActor;
|
|
3401
|
+
}
|
|
3402
|
+
return fetch(url, {
|
|
3403
|
+
method: "POST",
|
|
3404
|
+
headers: { "content-type": "application/json" },
|
|
3405
|
+
body: JSON.stringify(payload),
|
|
3406
|
+
signal: o.signal,
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3329
3410
|
// ---- ビルド時TSと共有する判定(唯一の正本は上の関数定義) ----
|
|
3330
3411
|
//
|
|
3331
3412
|
// **写経しない。** 接続マップ(ビルド時にSVGを描く)と画面(ブラウザで一覧を描く)が
|
|
@@ -3688,14 +3769,9 @@ ${deriveCapTile.toString()}
|
|
|
3688
3769
|
var endBusy = beginBusy(btn, "確認中…");
|
|
3689
3770
|
setStatus("", false);
|
|
3690
3771
|
var who = String(name || "").trim();
|
|
3691
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
3692
3772
|
var res, j;
|
|
3693
3773
|
try {
|
|
3694
|
-
res = await
|
|
3695
|
-
method: "POST",
|
|
3696
|
-
headers: { "content-type": "application/json" },
|
|
3697
|
-
body: JSON.stringify({ projectKey: intake.projectKey, path: path, submitter: who }),
|
|
3698
|
-
});
|
|
3774
|
+
res = await callIntake("promote", { projectKey: intake.projectKey, path: path, submitter: who });
|
|
3699
3775
|
j = await res.json().catch(function () { return {}; });
|
|
3700
3776
|
} catch (e) {
|
|
3701
3777
|
endBusy();
|
|
@@ -4250,6 +4326,215 @@ ${deriveCapTile.toString()}
|
|
|
4250
4326
|
app.appendChild(sec);
|
|
4251
4327
|
}
|
|
4252
4328
|
|
|
4329
|
+
// ---------- 案件アイコン(#348) ----------
|
|
4330
|
+
//
|
|
4331
|
+
// ポータルの案件一覧はカードにアイコンを出す。置き場はこの案件のリポジトリの
|
|
4332
|
+
// Cortex/icon.<ext> で、**設定する口はここ(Home)にしかない**——ポータル側に
|
|
4333
|
+
// 置くと「誰の案件か」の判定を先に作らねばならず、案件を知っている人が
|
|
4334
|
+
// その場で直せる形にならない。
|
|
4335
|
+
//
|
|
4336
|
+
// 保存はビルドを待たずに画面へ反映する(mirror)。恒久化は次のビルドで
|
|
4337
|
+
// site-data ごと焼き直されるので、mirror はこのタブが開いているあいだだけ。
|
|
4338
|
+
// 上限はサーバ側(logic.mjs の PROJECT_ICON_MAX_BYTES)と同じ値。画面の門は
|
|
4339
|
+
// 「送る前に断る」ためのもので、正本はサーバ側
|
|
4340
|
+
var PROJECT_ICON_MAX_BYTES = 1024 * 1024;
|
|
4341
|
+
// 保存直後の反映。null=ビルド時の値(SITE.projectIcon)をそのまま使う。
|
|
4342
|
+
// { file, src } なら設定済み、{ removed: true } なら「今この画面では外れている」
|
|
4343
|
+
var projectIconMirror = null;
|
|
4344
|
+
// いま img に載せている blob:。差し替え・解除のたびに revoke する(掴んだままだと
|
|
4345
|
+
// 選び直すたびにファイルの実体がメモリに積み上がる)
|
|
4346
|
+
var projectIconBlobUrl = null;
|
|
4347
|
+
|
|
4348
|
+
/** いま画面に出すべきアイコン(無ければ null) */
|
|
4349
|
+
function projectIconCurrent() {
|
|
4350
|
+
if (projectIconMirror) {
|
|
4351
|
+
return projectIconMirror.removed ? null : { file: projectIconMirror.file, src: projectIconMirror.src };
|
|
4352
|
+
}
|
|
4353
|
+
var p = SITE.projectIcon;
|
|
4354
|
+
// ビルドが同梱した画像はサイト内の相対パス。**外部URLは載せない**
|
|
4355
|
+
return p ? { file: p.file, src: p.file } : null;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
/**
|
|
4359
|
+
* 名前から拡張子を引く(jpeg は jpg へ寄せる)。許可外・拡張子なしは null。
|
|
4360
|
+
* **サーバ側(logic.mjs の projectIconExtOf)と同じ判断**を画面でも先にやるのは、
|
|
4361
|
+
* 通らないと分かっているファイルを1MB 送ってから断らないため
|
|
4362
|
+
*/
|
|
4363
|
+
function projectIconExtOfName(name) {
|
|
4364
|
+
var m = /\\.([A-Za-z0-9]+)$/.exec(String(name || ""));
|
|
4365
|
+
if (!m) return null;
|
|
4366
|
+
var e = m[1].toLowerCase();
|
|
4367
|
+
if (e === "jpeg") e = "jpg";
|
|
4368
|
+
return e === "png" || e === "jpg" || e === "webp" ? e : null;
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
/** callIntake の戻り(fetch の Promise)を { ok, j } に畳む(submitIntake と同じ形) */
|
|
4372
|
+
function intakeJson(req) {
|
|
4373
|
+
return req.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
/**
|
|
4377
|
+
* アイコンの枠(出さないときは null)。
|
|
4378
|
+
*
|
|
4379
|
+
* **押せない枠は出さない。** 受け口(intake)が無く画像も無い案件では、枠ごと出さない
|
|
4380
|
+
*(「+ 画像を選ぶ」を出して押せないのが一番悪い)。画像だけある案件では画像を出し、
|
|
4381
|
+
* 押せる導線は付けない。
|
|
4382
|
+
*
|
|
4383
|
+
* @param status 結果を書く行(枠を描き直しても残る・呼び出し側が持つ)
|
|
4384
|
+
*/
|
|
4385
|
+
function renderProjectIcon(status) {
|
|
4386
|
+
var intake = SITE.intake;
|
|
4387
|
+
var cur = projectIconCurrent();
|
|
4388
|
+
if (!intake && !cur) return null;
|
|
4389
|
+
|
|
4390
|
+
var box = el("div", { class: "project-icon", id: "project-icon" });
|
|
4391
|
+
function setStatus(msg, isError) {
|
|
4392
|
+
status.className = "project-icon-status" + (isError ? " project-icon-status-error" : "");
|
|
4393
|
+
status.textContent = msg || "";
|
|
4394
|
+
}
|
|
4395
|
+
// 枠だけを差し替える。status は外にあるので、書いた結果は描き直しでも消えない
|
|
4396
|
+
function redraw() {
|
|
4397
|
+
var next = renderProjectIcon(status);
|
|
4398
|
+
if (next && box.parentNode) box.parentNode.replaceChild(next, box);
|
|
4399
|
+
return next;
|
|
4400
|
+
}
|
|
4401
|
+
var ui = { status: status, setStatus: setStatus, redraw: redraw, input: null, buttons: [] };
|
|
4402
|
+
ui.disable = function (flag) {
|
|
4403
|
+
ui.buttons.forEach(function (b) { b.disabled = !!flag; });
|
|
4404
|
+
};
|
|
4405
|
+
|
|
4406
|
+
// **受け口が無ければ button にしない。** 押せる形(フォーカスが当たる・指差しの
|
|
4407
|
+
// カーソルが出る)で何も起きないのが、画面の嘘のうちで一番たちが悪い
|
|
4408
|
+
var frame = el(intake ? "button" : "div", {
|
|
4409
|
+
class: "project-icon-frame" + (cur ? " project-icon-filled" : ""),
|
|
4410
|
+
"aria-label": "案件アイコン",
|
|
4411
|
+
});
|
|
4412
|
+
if (intake) frame.setAttribute("type", "button");
|
|
4413
|
+
if (cur) {
|
|
4414
|
+
var img = el("img", { class: "project-icon-img", src: cur.src, alt: "案件アイコン" });
|
|
4415
|
+
// **onerror 属性で書かない(CSP でインラインハンドラは動かない)。**
|
|
4416
|
+
// blob: が配信先の img-src で拒まれることがあるので、そのときは文字に倒す
|
|
4417
|
+
img.addEventListener("error", function () {
|
|
4418
|
+
if (!img.parentNode) return;
|
|
4419
|
+
img.parentNode.replaceChild(el("span", { class: "project-icon-empty", text: "保存済み" }), img);
|
|
4420
|
+
});
|
|
4421
|
+
frame.appendChild(img);
|
|
4422
|
+
} else {
|
|
4423
|
+
frame.appendChild(el("span", { class: "project-icon-empty", text: intake ? "+ 画像を選ぶ" : "未設定" }));
|
|
4424
|
+
}
|
|
4425
|
+
box.appendChild(frame);
|
|
4426
|
+
|
|
4427
|
+
if (intake) {
|
|
4428
|
+
var input = el("input", { type: "file", accept: "image/png,image/jpeg,image/webp", hidden: "" });
|
|
4429
|
+
ui.input = input;
|
|
4430
|
+
input.addEventListener("change", function () {
|
|
4431
|
+
var f = input.files && input.files[0];
|
|
4432
|
+
// **読んだらすぐ value を戻す**(成功時だけ戻す形だと、形式で断られた直後に
|
|
4433
|
+
// 同じファイルを選び直しても change が飛ばず、直す経路がここで死ぬ)
|
|
4434
|
+
try { input.value = ""; } catch (e) {}
|
|
4435
|
+
if (f) setProjectIcon(f, ui);
|
|
4436
|
+
});
|
|
4437
|
+
var choose = el("button", { class: "project-icon-choose", type: "button", text: cur ? "画像を変える" : "画像を選ぶ" });
|
|
4438
|
+
choose.addEventListener("click", function () { input.click(); });
|
|
4439
|
+
// 枠を押すのも「選ぶ」と同じ(丸を押したくなるので、押せる場所を分けない)
|
|
4440
|
+
frame.addEventListener("click", function () { input.click(); });
|
|
4441
|
+
ui.buttons.push(choose, frame);
|
|
4442
|
+
var actions = el("div", { class: "project-icon-actions" }, [choose]);
|
|
4443
|
+
if (cur) {
|
|
4444
|
+
var remove = el("button", { class: "project-icon-remove", type: "button", text: "画像を外す" });
|
|
4445
|
+
remove.addEventListener("click", function () { removeProjectIcon(ui); });
|
|
4446
|
+
ui.buttons.push(remove);
|
|
4447
|
+
actions.appendChild(remove);
|
|
4448
|
+
}
|
|
4449
|
+
actions.appendChild(input);
|
|
4450
|
+
box.appendChild(actions);
|
|
4451
|
+
}
|
|
4452
|
+
return box;
|
|
4453
|
+
}
|
|
4454
|
+
|
|
4455
|
+
/** 保存先(GitHub)の1行。submitIntake の modal-paths と同じ組み立て */
|
|
4456
|
+
function projectIconPathBox(repo, p) {
|
|
4457
|
+
var br = SITE.branch || "main";
|
|
4458
|
+
var href = "https://github.com/" + repo + "/blob/" + encodeURIComponent(br) + "/" + p.split("/").map(encodeURIComponent).join("/");
|
|
4459
|
+
return el("div", { class: "modal-paths" }, [
|
|
4460
|
+
el("div", { class: "modal-paths-label", text: "保存先(GitHub):" }),
|
|
4461
|
+
el("a", { class: "modal-path", href: href, target: "_blank", rel: "noopener", text: p }),
|
|
4462
|
+
]);
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
/**
|
|
4466
|
+
* 画像を1枚選んで保存する。
|
|
4467
|
+
*
|
|
4468
|
+
* **画面側でも門を張る。** サーバ側(planProjectIconSet)が正本だが、ここで断れる
|
|
4469
|
+
* ものを送ると、1MB を上げきってから「大きすぎます」と言うことになる
|
|
4470
|
+
*/
|
|
4471
|
+
async function setProjectIcon(file, ui) {
|
|
4472
|
+
function fail(msg) { ui.setStatus("✗ " + msg, true); ui.disable(false); }
|
|
4473
|
+
// 画面側の門。**サーバ側と同じ文言**で、送る前に断る(fail で「✗ 」を揃える)
|
|
4474
|
+
if (!projectIconExtOfName(file.name)) return fail("PNG・JPEG・WebP の画像を選んでください");
|
|
4475
|
+
if (file.size > PROJECT_ICON_MAX_BYTES) return fail("1MB までの画像を選んでください");
|
|
4476
|
+
if (file.size === 0) return fail("画像が空です(0 バイト)。別の画像を選んでください");
|
|
4477
|
+
|
|
4478
|
+
var intake = SITE.intake;
|
|
4479
|
+
var ctype = file.type || "application/octet-stream";
|
|
4480
|
+
ui.disable(true);
|
|
4481
|
+
ui.setStatus("アップロード中…", false);
|
|
4482
|
+
|
|
4483
|
+
var pres;
|
|
4484
|
+
try {
|
|
4485
|
+
pres = await intakeJson(callIntake("presign", {
|
|
4486
|
+
projectKey: intake.projectKey, filename: file.name, contentType: ctype, purpose: "project-icon",
|
|
4487
|
+
}));
|
|
4488
|
+
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
4489
|
+
if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました");
|
|
4490
|
+
|
|
4491
|
+
// 署名付きURLへの PUT だけは intake ではないので素の fetch(submitIntake と同じ)
|
|
4492
|
+
var put;
|
|
4493
|
+
try {
|
|
4494
|
+
put = await fetch(pres.j.url, { method: "PUT", headers: { "content-type": ctype }, body: file });
|
|
4495
|
+
} catch (net2) { return fail("アップロードに失敗しました(もう一度お試しください)"); }
|
|
4496
|
+
if (!put.ok) return fail("アップロードに失敗しました(" + put.status + ")");
|
|
4497
|
+
|
|
4498
|
+
var saved;
|
|
4499
|
+
try {
|
|
4500
|
+
saved = await intakeJson(callIntake("project-icon-set", {
|
|
4501
|
+
projectKey: intake.projectKey, key: pres.j.key, filename: file.name,
|
|
4502
|
+
}));
|
|
4503
|
+
} catch (net3) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
4504
|
+
if (!saved.ok) return fail((saved.j && saved.j.message) || "保存できませんでした");
|
|
4505
|
+
|
|
4506
|
+
var repoPath = (saved.j && saved.j.path) || "";
|
|
4507
|
+
if (projectIconBlobUrl) { try { URL.revokeObjectURL(projectIconBlobUrl); } catch (e) {} }
|
|
4508
|
+
projectIconBlobUrl = URL.createObjectURL(file);
|
|
4509
|
+
projectIconMirror = { file: repoPath.split("/").pop() || "icon", src: projectIconBlobUrl };
|
|
4510
|
+
// **同じファイルをもう一度選べるようにする。** value を戻さないと、選び直しても
|
|
4511
|
+
// change が飛ばない(形式で断られた直後に選び直す経路がここで死ぬ)
|
|
4512
|
+
if (ui.input) ui.input.value = "";
|
|
4513
|
+
ui.redraw();
|
|
4514
|
+
ui.setStatus("", false);
|
|
4515
|
+
ui.status.appendChild(el("div", { text: "✓ 保存しました。数分後にポータルの一覧に反映されます。" }));
|
|
4516
|
+
if (repoPath && saved.j.repo) ui.status.appendChild(projectIconPathBox(saved.j.repo, repoPath));
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
/** 画像を外す。**確認は出さない**——押し間違えても、もう一度選び直せば戻る操作 */
|
|
4520
|
+
async function removeProjectIcon(ui) {
|
|
4521
|
+
function fail(msg) { ui.setStatus("✗ " + msg, true); ui.disable(false); }
|
|
4522
|
+
ui.disable(true);
|
|
4523
|
+
ui.setStatus("外しています…", false);
|
|
4524
|
+
var res;
|
|
4525
|
+
try {
|
|
4526
|
+
res = await intakeJson(callIntake("project-icon-remove", { projectKey: SITE.intake.projectKey }));
|
|
4527
|
+
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
4528
|
+
if (!res.ok) return fail((res.j && res.j.message) || "外せませんでした");
|
|
4529
|
+
if (projectIconBlobUrl) {
|
|
4530
|
+
try { URL.revokeObjectURL(projectIconBlobUrl); } catch (e) {}
|
|
4531
|
+
projectIconBlobUrl = null;
|
|
4532
|
+
}
|
|
4533
|
+
projectIconMirror = { removed: true };
|
|
4534
|
+
ui.redraw();
|
|
4535
|
+
ui.setStatus("✓ 外しました。数分後にポータルの一覧に反映されます(案件名の頭文字に戻ります)。", false);
|
|
4536
|
+
}
|
|
4537
|
+
|
|
4253
4538
|
// ---------- ホーム(着地画面: Markdownをそのまま表示) ----------
|
|
4254
4539
|
function renderHome() {
|
|
4255
4540
|
var h = SITE.home;
|
|
@@ -4267,10 +4552,18 @@ ${deriveCapTile.toString()}
|
|
|
4267
4552
|
text: "最終更新: " + SITE.homeUpdatedAt,
|
|
4268
4553
|
}));
|
|
4269
4554
|
}
|
|
4270
|
-
var titleWrap = el("div", {}, [ titleEl ]);
|
|
4555
|
+
var titleWrap = el("div", { class: "home-title-wrap" }, [ titleEl ]);
|
|
4271
4556
|
var ai = h.fm ? adoptionInfo(h.fm.adoption) : null;
|
|
4272
4557
|
if (ai) titleWrap.appendChild(el("span", { class: "chip adoption-" + String(h.fm.adoption), text: ai.label }));
|
|
4273
|
-
|
|
4558
|
+
// 案件アイコン(#348)はタイトルの左。**枠と結果の行は同じ flex 行に置く**
|
|
4559
|
+
// ——結果を枠の横に入れると 72px の高さに押し込まれて読めない
|
|
4560
|
+
var homeHead = el("div", { class: "home-head" });
|
|
4561
|
+
var iconStatus = el("div", { class: "project-icon-status", "aria-live": "polite" });
|
|
4562
|
+
var iconBox = renderProjectIcon(iconStatus);
|
|
4563
|
+
if (iconBox) homeHead.appendChild(iconBox);
|
|
4564
|
+
homeHead.appendChild(titleWrap);
|
|
4565
|
+
if (iconBox) homeHead.appendChild(iconStatus);
|
|
4566
|
+
head.appendChild(homeHead);
|
|
4274
4567
|
var homeEditRow = el("div", { class: "detail-actions-row" }, [
|
|
4275
4568
|
el("a", { class: "ai-edit-btn", href: codeUrl(HOME_EDIT_PROMPT), target: "_blank", rel: "noopener", text: "AIで編集" }),
|
|
4276
4569
|
]);
|
|
@@ -4406,6 +4699,44 @@ ${deriveCapTile.toString()}
|
|
|
4406
4699
|
cls: "ds-gate-muted",
|
|
4407
4700
|
help: "管理者の設定が済むと読み取りが始まります。",
|
|
4408
4701
|
},
|
|
4702
|
+
// --- ここから Microsoft 365(#335)。**値の正本は infra 側の語彙表**
|
|
4703
|
+
// (slack-gate.mjs の SLACK_GATE_VALUES と connection-state-keys.mjs の
|
|
4704
|
+
// REACHABLE_STATUSES)で、1対1の一致を infra のテストと
|
|
4705
|
+
// viewer/test/state-vocab-parity.test.mjs が見張る。
|
|
4706
|
+
// **判定を返すのは engine の夜間実測(1-E 以降)**——画面は語彙と文面だけを先に持つ
|
|
4707
|
+
// (判定器そのものは infra/lambda/shared の teams-gate.mjs / sharepoint-gate.mjs /
|
|
4708
|
+
// teams-chat-gate.mjs に既にある)。
|
|
4709
|
+
//
|
|
4710
|
+
// **バッジは短語・次の一手は help に分ける**(DS_GATE_NO_TOKEN と同じ作り。
|
|
4711
|
+
// バッジに長文を入れると行が折り返す)。**内部語を出さない**——証明書・トークン・
|
|
4712
|
+
// テナント・サービスプリンシパルは、読む人が自分の画面で探せない言葉
|
|
4713
|
+
"no_consent": {
|
|
4714
|
+
text: "管理者の承認が必要です",
|
|
4715
|
+
cls: "ds-gate-warn",
|
|
4716
|
+
help: "貴社の Microsoft 365 管理者が承認リンクを開いて許可すると読み取りが始まります。承認を取り消した場合もこの表示になります。",
|
|
4717
|
+
},
|
|
4718
|
+
"transcripts_api_disabled": {
|
|
4719
|
+
text: "文字起こしの外部取得がオフです",
|
|
4720
|
+
cls: "ds-gate-warn",
|
|
4721
|
+
help: "Teams 管理センターの会議設定で「文字起こしへの API アクセス」をオンにしてください。",
|
|
4722
|
+
},
|
|
4723
|
+
"no_access_policy": {
|
|
4724
|
+
text: "会議へのアクセス許可が未設定です",
|
|
4725
|
+
cls: "ds-gate-warn",
|
|
4726
|
+
help: "管理者がこの主催者にアクセス ポリシーを割り当てると読み取りが始まります(反映まで最大30分)。",
|
|
4727
|
+
},
|
|
4728
|
+
// **承認済み・API アクセスもオンなのに、話者名のトグルだけがオフ**という状態が実在する。
|
|
4729
|
+
// transcripts_api_disabled に寄せると、既にオンにしてある設定をもう一度案内することになる
|
|
4730
|
+
"speaker_attribution_off": {
|
|
4731
|
+
text: "話者名付きの文字起こしがオフです",
|
|
4732
|
+
cls: "ds-gate-warn",
|
|
4733
|
+
help: "Teams 管理センターの会議設定で話者名付き文字起こしをオンにしてください。オフのままだと取り込みません。",
|
|
4734
|
+
},
|
|
4735
|
+
"folder_not_granted": {
|
|
4736
|
+
text: "フォルダへのアクセス許可がありません",
|
|
4737
|
+
cls: "ds-gate-warn",
|
|
4738
|
+
help: "SharePoint 管理者がこのフォルダに読み取り許可を付与すると取り込みが始まります。",
|
|
4739
|
+
},
|
|
4409
4740
|
"unknown": { text: "不明", cls: "ds-gate-muted" },
|
|
4410
4741
|
};
|
|
4411
4742
|
/* ---------------------------------------------------------------------------
|
|
@@ -4479,16 +4810,26 @@ ${deriveCapTile.toString()}
|
|
|
4479
4810
|
},
|
|
4480
4811
|
};
|
|
4481
4812
|
/**
|
|
4482
|
-
* 休止状態の行に、状態の語に添えて**押す前から**出す定型の補足(#346
|
|
4483
|
-
*
|
|
4484
|
-
* {
|
|
4813
|
+
* 休止状態の行に、状態の語に添えて**押す前から**出す定型の補足(#346)と、
|
|
4814
|
+
* 「接続を確認」を押した結果に添える定型の1文(#356)。
|
|
4815
|
+
* 鍵は「アダプタ種別(helpTypeOf)× 状態」。値は { resting, probed } で、どちらも文の
|
|
4816
|
+
* パーツの配列。文字列はテキスト、{ link: <リンクの文字列> } は GitHub App の
|
|
4817
|
+
* インストール設定へのリンク(GITHUB_APP_INSTALL_URL)。
|
|
4818
|
+
*
|
|
4819
|
+
* ## 文の長さ(#356・オーナー却下を受けた原則)
|
|
4820
|
+
* **帯は症状だけ・行は具体的な一手だけ・説明を繰り返さない。** 0.12.22 では帯と行と
|
|
4821
|
+
* 押した結果に同じ admin の4文が3回出ており、オーナーが「長すぎ・冗長」と却下した。
|
|
4822
|
+
* - resting … **1文+リンクだけ**(誰が・どこで。「何が起きているか」は帯が言う)
|
|
4823
|
+
* - probed … **状態に応じた1文だけ**(1行目のサーバの needed が言わないことだけ)。
|
|
4824
|
+
* 押した結果に resting を繰り返さない——resting は押した瞬間に隠れ、needed が
|
|
4825
|
+
* 同じ操作(Configure から追加)を宛先つきで言う
|
|
4826
|
+
* 「admin」の語は画面に1回だけ(github-state-reason.test.mjs が押す前・押した後で数える)。
|
|
4485
4827
|
*
|
|
4486
4828
|
* ## なぜ画面の表で持つのか
|
|
4487
4829
|
* サーバの needed / detail は「何が起きているか」と「どこを開くか」までを持つが、
|
|
4488
|
-
* **誰がその操作をできるか**(リポジトリの admin
|
|
4489
|
-
*
|
|
4490
|
-
*
|
|
4491
|
-
* ここ、という役割分担にする。
|
|
4830
|
+
* **誰がその操作をできるか**(リポジトリの admin)と **request の意味**(組織オーナーの
|
|
4831
|
+
* 承認待ち)は、夜間(engine)とボタン(Lambda)の2つの書き手が別々に文を作るので、
|
|
4832
|
+
* サーバ側で足すと片方だけ古くなる。判定と URL はサーバ、定型の補足はここ、という役割分担。
|
|
4492
4833
|
*
|
|
4493
4834
|
* 🔴 **DS_GATE には入れない**(実測が返す語彙の表で、突き合わせテストの対象)。
|
|
4494
4835
|
* DS_GATE_NO_TOKEN と同じく、仕組みはアダプタ非依存・データだけがアダプタ固有。
|
|
@@ -4500,21 +4841,29 @@ ${deriveCapTile.toString()}
|
|
|
4500
4841
|
* 片方だけ古くなる)。
|
|
4501
4842
|
*
|
|
4502
4843
|
* エントリがある状態では**この文だけ**を出し、サーバの needed / detail は出さない
|
|
4503
|
-
*
|
|
4504
|
-
*
|
|
4844
|
+
* ——理由文と補足が issues / discussions の2行 × N リポに並ぶと、一覧が案内で埋まる
|
|
4845
|
+
* (DS_GATE_NO_TOKEN.github の項と同じ判断)。needed は押した結果が持つ。
|
|
4846
|
+
*
|
|
4847
|
+
* ⚠ **「移動・改名・削除の可能性 → 登録から外してください」は出さない。** App のトークン
|
|
4848
|
+
* では「未選択」と「消えた・改名」がどちらも 404 で**区別できない**(github-gate.mjs)。
|
|
4849
|
+
* 状態で出し分ける材料が無いので書かない。消えたリポジトリの逃げ道は、行に常に出る
|
|
4850
|
+
* 「登録から削除」(#354)が担う。区別する信号ができたら probed に足す
|
|
4505
4851
|
*/
|
|
4506
|
-
var GITHUB_ADMIN_HELP = "このリポジトリの admin(管理者権限を持つ人)であれば、Configure から自分で追加できます(承認は不要です)。"
|
|
4507
|
-
+ "admin でない場合は、そのリポジトリの admin の人に「Cortex の GitHub App の対象にこのリポジトリを追加してください」と依頼してください。"
|
|
4508
|
-
+ "GitHub 側で request と表示された場合は、組織オーナーの承認待ちです。"
|
|
4509
|
-
+ "追加したあと「接続を確認」を押して、読めるようになったことを確かめてください。";
|
|
4510
4852
|
var DS_GATE_RESTING_HELP = {
|
|
4511
4853
|
github: {
|
|
4512
|
-
unreachable:
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4854
|
+
unreachable: {
|
|
4855
|
+
// 誰が(admin)・どこで(Configure の Repository access)だけ。理由は帯、宛先はリンクが持つ。
|
|
4856
|
+
// 着地点(Repository access)を名指しするのは、押す前の画面からこの語が消えないため
|
|
4857
|
+
//(設計レビュー②)。「admin が…してください」はオーナーの原文の形
|
|
4858
|
+
resting: [
|
|
4859
|
+
"このリポジトリの admin(管理者権限を持つ人)が、Configure の Repository access に追加してください。",
|
|
4860
|
+
{ link: "インストール設定を開く" },
|
|
4861
|
+
],
|
|
4862
|
+
// 2026-09-02 の実測: admin でないリポジトリを Configure で選ぶと「request」タグが付き、
|
|
4863
|
+
// 組織オーナーの承認待ちになる("Okay, updated" と出るのに対象に入らない)。
|
|
4864
|
+
// 1行目(サーバの needed)はこれを言わないので、ここだけが持つ
|
|
4865
|
+
probed: ["GitHub 側で request と表示された場合は、組織オーナーの承認待ちです。"],
|
|
4866
|
+
},
|
|
4518
4867
|
},
|
|
4519
4868
|
};
|
|
4520
4869
|
/**
|
|
@@ -4526,11 +4875,20 @@ ${deriveCapTile.toString()}
|
|
|
4526
4875
|
var t = String(type || "");
|
|
4527
4876
|
return t.indexOf("github") === 0 ? "github" : t;
|
|
4528
4877
|
}
|
|
4529
|
-
|
|
4530
|
-
function restingHelpPartsOf(type, gate) {
|
|
4878
|
+
function restingHelpEntryOf(type, gate) {
|
|
4531
4879
|
var byType = DS_GATE_RESTING_HELP[helpTypeOf(type)];
|
|
4532
4880
|
return (byType && byType[gate]) || null;
|
|
4533
4881
|
}
|
|
4882
|
+
/** 休止状態の行に出すパーツ(無ければ null) */
|
|
4883
|
+
function restingHelpPartsOf(type, gate) {
|
|
4884
|
+
var e = restingHelpEntryOf(type, gate);
|
|
4885
|
+
return (e && e.resting) || null;
|
|
4886
|
+
}
|
|
4887
|
+
/** 押した結果に添えるパーツ(無ければ null)。行と同じ表から引く(写経しない) */
|
|
4888
|
+
function probedHelpPartsOf(type, gate) {
|
|
4889
|
+
var e = restingHelpEntryOf(type, gate);
|
|
4890
|
+
return (e && e.probed) || null;
|
|
4891
|
+
}
|
|
4534
4892
|
/** パーツを1段落にする。包む要素の class は置き場所で違う(行は ds-gate-help・押した結果は ds-probe-help) */
|
|
4535
4893
|
function restingHelpNode(parts, cls) {
|
|
4536
4894
|
var p = el("p", { class: cls });
|
|
@@ -4895,12 +5253,7 @@ ${deriveCapTile.toString()}
|
|
|
4895
5253
|
msg.className = "ds-toggle-msg";
|
|
4896
5254
|
msg.textContent = "承認画面を準備しています…";
|
|
4897
5255
|
try {
|
|
4898
|
-
var
|
|
4899
|
-
var res = await fetch(pfx + "op=slack-oauth-start&t=" + encodeURIComponent(intake.token), {
|
|
4900
|
-
method: "POST",
|
|
4901
|
-
headers: { "content-type": "application/json" },
|
|
4902
|
-
body: "{}",
|
|
4903
|
-
});
|
|
5256
|
+
var res = await callIntake("slack-oauth-start", {});
|
|
4904
5257
|
var j = await res.json().catch(function () { return {}; });
|
|
4905
5258
|
if (!res.ok || !j.url) {
|
|
4906
5259
|
// サーバの文言をそのまま出す(未設定のときは「準備中です」が返る)
|
|
@@ -4959,7 +5312,6 @@ ${deriveCapTile.toString()}
|
|
|
4959
5312
|
orgResolveDone = true;
|
|
4960
5313
|
var intake = SITE.intake;
|
|
4961
5314
|
if (!intake) return;
|
|
4962
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
4963
5315
|
// **描画の途中で投げない。** ここは行を組み立てている最中に呼ばれるので、
|
|
4964
5316
|
// 例外が出ると**画面そのものが描かれない**(fetch が無い環境で実際にそうなった)。
|
|
4965
5317
|
// 判定し直しは「できたら直る」性質のもので、失敗しても行は
|
|
@@ -4971,11 +5323,7 @@ ${deriveCapTile.toString()}
|
|
|
4971
5323
|
// fetch が存在しない(ReferenceError)と、呼び出しが投げる
|
|
4972
5324
|
if (typeof fetch !== "function") return;
|
|
4973
5325
|
try {
|
|
4974
|
-
|
|
4975
|
-
method: "POST",
|
|
4976
|
-
headers: { "content-type": "application/json" },
|
|
4977
|
-
body: "{}",
|
|
4978
|
-
})
|
|
5326
|
+
callIntake("slack-resolve", {})
|
|
4979
5327
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
4980
5328
|
.then(function (j) {
|
|
4981
5329
|
if (!j || !j.resolved) return;
|
|
@@ -5192,35 +5540,46 @@ ${deriveCapTile.toString()}
|
|
|
5192
5540
|
// App 導入済みの組織にも「まだ接続されていません」と出て、**同じ帯の本文と矛盾**した
|
|
5193
5541
|
// (実機でオーナーが踏み、利用者が「組織の接続からやり直し」と誤解した)
|
|
5194
5542
|
if (installed === true) {
|
|
5195
|
-
//
|
|
5196
|
-
//
|
|
5543
|
+
// 🔴 **帯は症状だけ(#356・オーナー却下を受けた原則)。** 0.12.22 ではここに9文
|
|
5544
|
+
// (App の説明・Configure の手順・admin の4文・移動改名削除・お客様の組織)を入れ、
|
|
5545
|
+
// 同じ説明が行と押した結果にも出て3回繰り返された。見出しは件数だけ・本文は2文だけ。
|
|
5546
|
+
// **組織名は見出しに入れない**——各行のバッジ「{組織}(GitHub App)」が持っている。
|
|
5547
|
+
// 組織が複数ある画面でも、組織ごとの帯のまま・見出しは同じ形。
|
|
5548
|
+
//
|
|
5549
|
+
// **見出しでは「読み取り対象に選ばれていません」と言い切らない。** 「appInstalled」 が
|
|
5550
|
+
// 分けられるのは「①未インストール」と「②未選択/③消えた・改名」までで、**②と③は
|
|
5551
|
+
// 分かれない**(App のトークンではどちらも 404)。見出しは事実だけを言う
|
|
5197
5552
|
out.push(el("div", {
|
|
5198
5553
|
class: "ds-orgnote-title",
|
|
5199
|
-
text: "⚠️ " +
|
|
5554
|
+
text: "⚠️ " + unread + "件のリポジトリを読み取れていません",
|
|
5200
5555
|
}));
|
|
5201
5556
|
// App は入っている。**インストールをもう一度促さないこと**——済んでいる操作を促すのは
|
|
5202
|
-
//
|
|
5557
|
+
// 「設定する必要が無いものに設定を促す」偽の警告と同じで、しかも直らない。
|
|
5558
|
+
//
|
|
5559
|
+
// 「誰が・どこで」は各行の1文(DS_GATE_RESTING_HELP)が持つ。ここで繰り返さない。
|
|
5560
|
+
// 「移動・改名・削除の可能性 → 登録から外して」も言わない——②と③を区別する材料が
|
|
5561
|
+
// 無く、外す手段は行の「登録から削除」(#354)が常に持つ。
|
|
5562
|
+
// 「お客様の組織では、組織のオーナーに承認を依頼」も外した——request(承認待ち)の
|
|
5563
|
+
// 1文は押した結果が持つ(同じ表の probed)。
|
|
5203
5564
|
//
|
|
5204
|
-
//
|
|
5205
|
-
//
|
|
5206
|
-
//
|
|
5207
|
-
//
|
|
5565
|
+
// ⚠ **件数の文 「head」(「{N}リポジトリ中 {M}件が読めていません。」)もこの枝では
|
|
5566
|
+
// 出さない(#356)。** #101 で3度直した「読める件数を出す」を意図して覆した——見出しが
|
|
5567
|
+
// 件数を持つので、本文で二重に言うと帯が3文になる。分母(登録数)は画面から消えるが、
|
|
5568
|
+
// 箱の中に行が全部並んでいるので数えられる。false / null の枝は従来どおり head を出す。
|
|
5208
5569
|
//
|
|
5209
|
-
//
|
|
5210
|
-
//
|
|
5211
|
-
//
|
|
5212
|
-
// いまの宛先と、そこに着ける人の条件は 「appSettingsLink」 の項にある。
|
|
5570
|
+
// **「各行の案内」ではなく「アクセス不可の行の案内」と言う。** 帯の箱には読めている行も
|
|
5571
|
+
// 一緒に入る(unread が1件でもあれば組織ごと warn)。案内を持つのは「アクセス不可」の
|
|
5572
|
+
// 行だけなので、行に出ている状態の語そのもので指す(設計レビュー②の指摘)。
|
|
5213
5573
|
//
|
|
5214
|
-
//
|
|
5215
|
-
//
|
|
5216
|
-
//
|
|
5574
|
+
// **「読み取り対象に入っていません」はオーナーの文(#356)。** 2026-08-21 の
|
|
5575
|
+
// 「②未選択と③消えた・改名を区別できないので断定しない」は、帯の本文については
|
|
5576
|
+
// この文で覆した(見出しは引き続き断定しない)。③の逃げ道は「登録から削除」。
|
|
5577
|
+
//
|
|
5578
|
+
// **設定画面の宛先は App 個別ページ(#346)。** いまの宛先と、そこに着ける人の条件は
|
|
5579
|
+
// 「appSettingsLink」 の項にある
|
|
5217
5580
|
out.push(el("p", {
|
|
5218
5581
|
class: "ds-group-note",
|
|
5219
|
-
text:
|
|
5220
|
-
+ "「インストール設定を開く」から " + g.title + " の Configure を開き、Repository access に対象のリポジトリを追加してください。"
|
|
5221
|
-
+ GITHUB_ADMIN_HELP
|
|
5222
|
-
+ "リポジトリ自体が一覧に見当たらない場合は、移動・改名・削除されている可能性があるので、登録から外してください。"
|
|
5223
|
-
+ "お客様の組織では、組織のオーナーに承認を依頼してください。",
|
|
5582
|
+
text: "GitHub App の読み取り対象に入っていません。アクセス不可の行の案内に従って追加してください。",
|
|
5224
5583
|
}));
|
|
5225
5584
|
out.push(appSettingsLink("インストール設定を開く →"));
|
|
5226
5585
|
return out;
|
|
@@ -5414,14 +5773,8 @@ ${deriveCapTile.toString()}
|
|
|
5414
5773
|
btn.disabled = true;
|
|
5415
5774
|
msg.className = "ds-toggle-msg";
|
|
5416
5775
|
msg.textContent = "実行中…";
|
|
5417
|
-
var intake = SITE.intake;
|
|
5418
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
5419
5776
|
try {
|
|
5420
|
-
var res = await
|
|
5421
|
-
method: "POST",
|
|
5422
|
-
headers: { "content-type": "application/json" },
|
|
5423
|
-
body: JSON.stringify({ kind: kind }),
|
|
5424
|
-
});
|
|
5777
|
+
var res = await callIntake("sync", { kind: kind });
|
|
5425
5778
|
var j = await res.json().catch(function () { return {}; });
|
|
5426
5779
|
// **起こせなかったときも「失敗」と言わない。** 定期実行が拾うので、
|
|
5427
5780
|
// 押した人にできることは無い。何が起きるかだけを伝える
|
|
@@ -7106,11 +7459,9 @@ ${deriveCapTile.toString()}
|
|
|
7106
7459
|
var giveUp = ctrl ? setTimeout(function () { ctrl.abort(); }, LIVE_CONFIRM_TIMEOUT_MS) : null;
|
|
7107
7460
|
// **成功・失敗の両方で外す。** 残すと、window を閉じないテストで node --test が終わらない
|
|
7108
7461
|
var clearGiveUp = function () { if (giveUp != null) { clearTimeout(giveUp); giveUp = null; } };
|
|
7109
|
-
|
|
7110
|
-
// GET
|
|
7111
|
-
|
|
7112
|
-
signal: ctrl ? ctrl.signal : undefined,
|
|
7113
|
-
})
|
|
7462
|
+
// GET で取る。**カスタムヘッダは付けない**(preflight を1往復増やさないため)。
|
|
7463
|
+
// 名乗り券は body に載せる仕組みなので、GET のこの op には付かない(#349)
|
|
7464
|
+
callIntake("harness-catalog", null, { method: "GET", signal: ctrl ? ctrl.signal : undefined })
|
|
7114
7465
|
.then(function (res) {
|
|
7115
7466
|
clearGiveUp();
|
|
7116
7467
|
if (!res.ok) throw new Error("http " + res.status);
|
|
@@ -8368,18 +8719,21 @@ ${deriveCapTile.toString()}
|
|
|
8368
8719
|
* 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
|
|
8369
8720
|
* 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
|
|
8370
8721
|
* (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
|
|
8371
|
-
*
|
|
8372
|
-
*
|
|
8722
|
+
* **ただし状態に応じた定型の1文だけは画面の表(DS_GATE_RESTING_HELP の probed)から引く**
|
|
8723
|
+
* (#346 → #356)——「request は組織オーナーの承認待ち」のような GitHub 側の意味は、
|
|
8724
|
+
* サーバの2つの書き手が別々に文を作るので、片方だけ古くなる。
|
|
8725
|
+
*
|
|
8726
|
+
* 🔴 **行の常時表示(resting)を押した結果に繰り返さない(#356)。** 0.12.22 では admin の
|
|
8727
|
+
* 4文が行と押した結果の両方に出ていた。行の1文は押した瞬間に隠れ、needed が同じ操作
|
|
8728
|
+
* (Configure から追加)を宛先つきで言うので、ここは needed が言わない1文だけ
|
|
8373
8729
|
*/
|
|
8374
8730
|
function githubProbeHelpNodes(r) {
|
|
8375
8731
|
if (r.gate === "ok") return [];
|
|
8376
8732
|
if (r.needed) {
|
|
8377
8733
|
var nodes = [neededNode(r.needed)];
|
|
8378
|
-
//
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
// 表のパーツ全部ではなく admin の4文(GITHUB_ADMIN_HELP)だけを添える
|
|
8382
|
-
if (restingHelpPartsOf("github", r.gate)) nodes.push(restingHelpNode([GITHUB_ADMIN_HELP], "ds-probe-help"));
|
|
8734
|
+
// 状態に応じた1文は**行と同じ表から引く**(ここに文を直書きすると表と割れる)
|
|
8735
|
+
var probed = probedHelpPartsOf("github", r.gate);
|
|
8736
|
+
if (probed) nodes.push(restingHelpNode(probed, "ds-probe-help"));
|
|
8383
8737
|
return nodes;
|
|
8384
8738
|
}
|
|
8385
8739
|
// needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
|
|
@@ -8525,12 +8879,7 @@ ${deriveCapTile.toString()}
|
|
|
8525
8879
|
if (!intake) return;
|
|
8526
8880
|
var payload = { op: "refresh-channel-name", ref: s.ref };
|
|
8527
8881
|
if (s.workspace) payload.workspace = s.workspace;
|
|
8528
|
-
|
|
8529
|
-
fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
|
|
8530
|
-
method: "POST",
|
|
8531
|
-
headers: { "content-type": "application/json" },
|
|
8532
|
-
body: JSON.stringify(payload),
|
|
8533
|
-
}).then(function (res) {
|
|
8882
|
+
callIntake("config", payload).then(function (res) {
|
|
8534
8883
|
if (!res.ok) return;
|
|
8535
8884
|
// 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
|
|
8536
8885
|
var nameEl = card.querySelector(".ds-name");
|
|
@@ -8591,13 +8940,8 @@ ${deriveCapTile.toString()}
|
|
|
8591
8940
|
// ワークスペースは Slack にしか無い概念なので、他アダプタでは載せない
|
|
8592
8941
|
var payload = { type: probeTypeOf(s.type), ref: s.ref };
|
|
8593
8942
|
if (s.workspace) payload.workspace = s.workspace;
|
|
8594
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
8595
8943
|
try {
|
|
8596
|
-
var res = await
|
|
8597
|
-
method: "POST",
|
|
8598
|
-
headers: { "content-type": "application/json" },
|
|
8599
|
-
body: JSON.stringify(payload),
|
|
8600
|
-
});
|
|
8944
|
+
var res = await callIntake("probe", payload);
|
|
8601
8945
|
var j = await res.json().catch(function () { return {}; });
|
|
8602
8946
|
if (!res.ok) {
|
|
8603
8947
|
// サーバの文言をそのまま出す(回数制限の案内・未対応のアダプタの説明を含んでいる)
|
|
@@ -9932,17 +10276,12 @@ ${deriveCapTile.toString()}
|
|
|
9932
10276
|
async function sendConfig(payload, msg, btn, onDone, hooks) {
|
|
9933
10277
|
var intake = SITE.intake;
|
|
9934
10278
|
if (!intake) return;
|
|
9935
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
9936
10279
|
// 押している間はボタンごと「処理中…」にする(#222・beginBusy の項)
|
|
9937
10280
|
var endBusy = beginBusy(btn);
|
|
9938
10281
|
msg.className = "ds-toggle-msg";
|
|
9939
10282
|
msg.textContent = "保存中…";
|
|
9940
10283
|
try {
|
|
9941
|
-
var res = await
|
|
9942
|
-
method: "POST",
|
|
9943
|
-
headers: { "content-type": "application/json" },
|
|
9944
|
-
body: JSON.stringify(payload),
|
|
9945
|
-
});
|
|
10284
|
+
var res = await callIntake("config", payload);
|
|
9946
10285
|
var j = await res.json().catch(function () { return {}; });
|
|
9947
10286
|
// 一覧が添えられていれば、断られていても先に画面を追いつかせる(#127)
|
|
9948
10287
|
var raced = hooks && hooks.onTargets && Array.isArray(j.targets) ? hooks.onTargets(j.targets) : false;
|
|
@@ -10962,17 +11301,15 @@ ${deriveCapTile.toString()}
|
|
|
10962
11301
|
var giveUp = ctrl ? setTimeout(function () { ctrl.abort(); }, LIVE_CONFIRM_TIMEOUT_MS) : null;
|
|
10963
11302
|
/** 見切りのタイマを外す。**応答が来たら必ず呼ぶ**(残すと次の取得を巻き込んで中断する) */
|
|
10964
11303
|
var clearGiveUp = function () { if (giveUp != null) { clearTimeout(giveUp); giveUp = null; } };
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
body: JSON.stringify(useFresh ? { caps: ALL_STATE_CAPS, fresh: true } : { caps: ALL_STATE_CAPS }),
|
|
10975
|
-
})
|
|
11304
|
+
// **常に全能力を要求する(ALL_STATE_CAPS の項)。** 知らない値は Lambda 側が黙って
|
|
11305
|
+
// 捨てる(断らない)ので、画面と Lambda の版が食い違っても接続状況の表示は消えない
|
|
11306
|
+
// **保存直後だけ据え置きを迂回させる。** これが無いと、サーバが保存前の答えを返し、
|
|
11307
|
+
// 足りない行が0件になって取り直しも張られない=手で再読み込みするまで追いつかない
|
|
11308
|
+
callIntake(
|
|
11309
|
+
"state",
|
|
11310
|
+
useFresh ? { caps: ALL_STATE_CAPS, fresh: true } : { caps: ALL_STATE_CAPS },
|
|
11311
|
+
{ signal: ctrl ? ctrl.signal : undefined },
|
|
11312
|
+
)
|
|
10976
11313
|
// **非2xxも失敗として扱う。** ビューアとLambdaの版は独立に進むので、
|
|
10977
11314
|
// op=state 未対応のLambdaは 400 を返す。通信エラーだけを見ていると空表示になる。
|
|
10978
11315
|
.then(function (res) {
|
|
@@ -11479,18 +11816,13 @@ ${deriveCapTile.toString()}
|
|
|
11479
11816
|
msg.className = "ds-toggle-msg";
|
|
11480
11817
|
msg.textContent = "保存中…";
|
|
11481
11818
|
var intake = SITE.intake;
|
|
11482
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
11483
11819
|
try {
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
// **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
|
|
11491
|
-
// 貼った鍵が「スペースを特定できません」で断られる
|
|
11492
|
-
body: JSON.stringify(secretPayload(intake, kind, value, cardSpace)),
|
|
11493
|
-
});
|
|
11820
|
+
// **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
|
|
11821
|
+
// **Backlog はスペースだけ名乗る(#138)。** 名前を組み立てるのはサーバで、
|
|
11822
|
+
// こちらが渡すのは「どのスペースの鍵か」という材料だけ。
|
|
11823
|
+
// **押した時点で読む。** 組み立て時に固めると、接続先を登録した直後に
|
|
11824
|
+
// 貼った鍵が「スペースを特定できません」で断られる
|
|
11825
|
+
var res = await callIntake("secret", secretPayload(intake, kind, value, cardSpace));
|
|
11494
11826
|
var j = await res.json().catch(function () { return {}; });
|
|
11495
11827
|
if (!res.ok) {
|
|
11496
11828
|
msg.className = "ds-toggle-msg is-error";
|
|
@@ -12239,7 +12571,6 @@ ${deriveCapTile.toString()}
|
|
|
12239
12571
|
if (!entries || !entries.length) { setStatus("⚠ ファイルを選択してください。", true); return; }
|
|
12240
12572
|
if (isT && !meeting) { setStatus("⚠ 会議名を選んでください。", true); return; }
|
|
12241
12573
|
submitBtn.disabled = true;
|
|
12242
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
12243
12574
|
function fail(msg) { setStatus("✗ " + msg, true); submitBtn.disabled = false; }
|
|
12244
12575
|
try {
|
|
12245
12576
|
var uploaded = [];
|
|
@@ -12248,10 +12579,8 @@ ${deriveCapTile.toString()}
|
|
|
12248
12579
|
setStatus("アップロード中… (" + (i + 1) + "/" + entries.length + ") " + f.name, false);
|
|
12249
12580
|
var pres;
|
|
12250
12581
|
try {
|
|
12251
|
-
pres = await
|
|
12252
|
-
|
|
12253
|
-
body: JSON.stringify({ projectKey: intake.projectKey, filename: f.name, contentType: f.type || "application/octet-stream" }),
|
|
12254
|
-
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12582
|
+
pres = await callIntake("presign", { projectKey: intake.projectKey, filename: f.name, contentType: f.type || "application/octet-stream" })
|
|
12583
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12255
12584
|
} catch (net) { return fail("サーバーに接続できませんでした。通信環境を確認して、もう一度お試しください。"); }
|
|
12256
12585
|
if (!pres.ok || !pres.j.url) return fail((pres.j && pres.j.message) || "アップロードURLの取得に失敗しました。対応していないファイル形式かもしれません。");
|
|
12257
12586
|
var put;
|
|
@@ -12269,10 +12598,8 @@ ${deriveCapTile.toString()}
|
|
|
12269
12598
|
try {
|
|
12270
12599
|
var payload = { projectKey: intake.projectKey, kind: kind, files: uploaded, submitter: submitter.trim() };
|
|
12271
12600
|
if (isT) payload.meeting = meeting;
|
|
12272
|
-
sub = await
|
|
12273
|
-
|
|
12274
|
-
body: JSON.stringify(payload),
|
|
12275
|
-
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12601
|
+
sub = await callIntake("submit", payload)
|
|
12602
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); });
|
|
12276
12603
|
} catch (net3) { return fail("サーバーに接続できませんでした。もう一度お試しください。"); }
|
|
12277
12604
|
if (!sub.ok) return fail((sub.j && sub.j.message) || "取り込みに失敗しました。時間をおいて再度お試しください。");
|
|
12278
12605
|
status.className = "modal-status";
|
|
@@ -12630,15 +12957,10 @@ ${deriveCapTile.toString()}
|
|
|
12630
12957
|
var endBusy = beginBusy(btn, "削除中…");
|
|
12631
12958
|
msg.className = "record-delete-msg";
|
|
12632
12959
|
msg.textContent = "";
|
|
12633
|
-
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
12634
12960
|
var res, j;
|
|
12635
12961
|
try {
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
headers: { "content-type": "application/json" },
|
|
12639
|
-
// 名乗りは確認済み昇格で覚えたものを流用する(削除のためだけに名前を聞かない)
|
|
12640
|
-
body: JSON.stringify({ projectKey: intake.projectKey, path: path, submitter: rememberedVerifier() }),
|
|
12641
|
-
});
|
|
12962
|
+
// 名乗りは確認済み昇格で覚えたものを流用する(削除のためだけに名前を聞かない)
|
|
12963
|
+
res = await callIntake("delete-record", { projectKey: intake.projectKey, path: path, submitter: rememberedVerifier() });
|
|
12642
12964
|
j = await res.json().catch(function () { return {}; });
|
|
12643
12965
|
} catch (e) {
|
|
12644
12966
|
endBusy();
|
package/package.json
CHANGED