@gavin-hu/my-pi-agent 0.17.0 → 0.19.0
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/README.md +61 -45
- package/README.zh-CN.md +54 -42
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +344 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
- package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
- package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
- package/docs/extensions/file-browse.md +554 -0
- package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
- package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
- package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
- package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
- package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
- package/docs/extensions/plan.md +976 -0
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +285 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
- package/docs/extensions/team.md +226 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
- package/extensions/board/commands.ts +87 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +72 -0
- package/extensions/board/index.ts +40 -0
- package/extensions/board/input.ts +54 -0
- package/extensions/board/join.ts +42 -0
- package/extensions/board/page.ts +312 -0
- package/extensions/board/result.ts +99 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +75 -0
- package/extensions/board/seat.ts +29 -0
- package/extensions/board/snapshot.ts +41 -0
- package/extensions/board/tools.ts +279 -0
- package/extensions/board/transport/router.ts +172 -0
- package/extensions/board/transport/server.ts +149 -0
- package/extensions/board/types.ts +54 -0
- package/extensions/board/view/actions.ts +306 -0
- package/extensions/board/view/client.ts +41 -0
- package/extensions/board/view/keys.ts +121 -0
- package/extensions/board/view/live.ts +397 -0
- package/extensions/board/view/page.ts +158 -0
- package/extensions/board/view/render.ts +496 -0
- package/extensions/board/view/styles.ts +211 -0
- package/extensions/board/view/team.ts +305 -0
- package/extensions/board/writes.ts +99 -0
- package/extensions/bytefly/notify/types.ts +1 -1
- package/extensions/bytefly/panel/commands.ts +40 -7
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +11 -21
- package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
- package/extensions/bytefly/picker/discovery.ts +0 -2
- package/extensions/bytefly/picker/types.ts +0 -2
- package/extensions/bytefly/update/check.ts +39 -0
- package/extensions/bytefly/update/format.ts +15 -0
- package/extensions/bytefly/update/register.ts +73 -34
- package/extensions/bytefly/update/state.ts +6 -3
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
- package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
- package/extensions/file-browse/index.ts +4 -4
- package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
- package/extensions/file-browse/live/rev.ts +42 -0
- package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/view/chrome.ts +83 -0
- package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
- package/extensions/file-browse/view/diff-html.ts +94 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
- package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
- package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
- package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
- package/extensions/file-read/schema.ts +2 -1
- package/extensions/goal/plan-route.ts +8 -2
- package/extensions/job/index.ts +15 -7
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +77 -21
- package/extensions/plan/feedback.ts +409 -63
- package/extensions/plan/index.ts +7 -5
- package/extensions/plan/plan-index.ts +44 -10
- package/extensions/plan/plan-text.ts +51 -24
- package/extensions/plan/plans.ts +162 -57
- package/extensions/plan/policy.ts +20 -12
- package/extensions/plan/review-prompt.ts +93 -0
- package/extensions/plan/review.ts +51 -95
- package/extensions/plan/runtime.ts +22 -10
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +63 -95
- package/extensions/plan/tools/enter.ts +8 -3
- package/extensions/plan/tools/exit.ts +13 -19
- package/extensions/plan/tools/write.ts +8 -3
- package/extensions/plan/tools.ts +1 -1
- package/extensions/plan/tui.ts +29 -15
- package/extensions/plan/types.ts +29 -3
- package/extensions/plan/web-chrome.ts +2 -1
- package/extensions/plan/web-client.ts +122 -178
- package/extensions/plan/web-collab.ts +207 -0
- package/extensions/plan/web-comments.ts +269 -0
- package/extensions/plan/web-page.ts +96 -17
- package/extensions/plan/web-router.ts +109 -47
- package/extensions/plan/web-share-session.ts +191 -0
- package/extensions/plan/web-share.ts +169 -0
- package/extensions/plan/web-streams.ts +124 -0
- package/extensions/plan/web-tokens.ts +63 -0
- package/extensions/plan/web.ts +245 -151
- package/extensions/question/dialogs.ts +91 -1
- package/extensions/question/tools.ts +2 -1
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
- package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
- package/extensions/rewind/{config.ts → config/config.ts} +1 -1
- package/extensions/rewind/index.ts +3 -3
- package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
- package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
- package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
- package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
- package/extensions/rewind/{format.ts → text/format.ts} +3 -3
- package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
- package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
- package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
- package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
- package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
- package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
- package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
- package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
- package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
- package/extensions/rewind/{status.ts → view/status.ts} +4 -4
- package/extensions/session-bus/commands.ts +113 -0
- package/extensions/session-bus/format.ts +101 -0
- package/extensions/session-bus/index.ts +75 -0
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +86 -0
- package/extensions/session-bus/overlap.ts +36 -0
- package/extensions/session-bus/protocol.ts +104 -0
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +208 -0
- package/extensions/session-bus/rows.ts +171 -0
- package/extensions/session-bus/runtime.ts +466 -0
- package/extensions/session-bus/store.ts +365 -0
- package/extensions/session-bus/tools.ts +192 -0
- package/extensions/session-bus/tui.ts +394 -0
- package/extensions/session-bus/types.ts +98 -0
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +112 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +105 -0
- package/extensions/team/index.ts +33 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +246 -0
- package/extensions/team/types.ts +48 -0
- package/extensions/watch/index.ts +15 -9
- package/extensions/web-access/fetch/page.ts +4 -1
- package/extensions/web-access/fetch/structured.ts +447 -0
- package/extensions/web-access/fetch/tool.ts +2 -1
- package/extensions/web-access/search/tool.ts +2 -1
- package/extensions/wechat/bridge.ts +76 -92
- package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
- package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
- package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
- package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
- package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
- package/extensions/wechat/dialog/channels.ts +136 -0
- package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
- package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
- package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
- package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
- package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
- package/extensions/wechat/{state.ts → state/state.ts} +14 -2
- package/extensions/wechat/{format.ts → text/format.ts} +3 -3
- package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
- package/extensions/wechat/tools.ts +29 -12
- package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
- package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
- package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
- package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
- package/extensions/wechat/transport/outbound.ts +142 -0
- package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
- package/extensions/wechat/types.ts +10 -1
- package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
- package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
- package/extensions/worktree/git.ts +5 -0
- package/extensions/worktree/guard.ts +3 -5
- package/extensions/worktree/registry.ts +11 -5
- package/extensions/worktree/status.ts +1 -3
- package/lib/README.md +12 -6
- package/lib/board/card.ts +290 -0
- package/lib/board/links.ts +117 -0
- package/lib/board/page-pointer.ts +256 -0
- package/lib/board/round-text.ts +72 -0
- package/lib/board/round.ts +89 -0
- package/lib/board/rules.ts +304 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +310 -0
- package/lib/board/view.ts +150 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +13 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/router.ts +40 -13
- package/lib/http/serve.ts +234 -0
- package/lib/http/server.ts +29 -13
- package/lib/http/sink.ts +110 -0
- package/lib/http/token.ts +38 -0
- package/lib/interaction.ts +10 -0
- package/lib/notice.ts +29 -2
- package/lib/plan-entry.ts +7 -2
- package/lib/policy.ts +12 -2
- package/lib/qr.ts +64 -0
- package/lib/repo-key.ts +74 -0
- package/lib/report.ts +52 -33
- package/lib/requirements.ts +7 -9
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +130 -0
- package/lib/session-tool.ts +184 -0
- package/lib/tool-names.ts +25 -2
- package/lib/tui/README.md +16 -2
- package/lib/tui/dock-screen.ts +85 -17
- package/lib/tui/glyphs.ts +2 -0
- package/lib/tui/line-pane.ts +15 -4
- package/lib/tui/tool.ts +23 -4
- package/lib/web/README.md +5 -2
- package/lib/web/components.ts +6 -1
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +27 -14
- package/skills/init/SKILL.md +80 -45
- package/skills/init/references/agents-md-quality.md +26 -2
- package/skills/init/references/interview-questions.md +36 -14
- package/skills/next/SKILL.md +77 -51
- package/skills/project-docs/SKILL.md +133 -0
- package/skills/project-docs/references/audit.md +131 -0
- package/skills/project-docs/references/changelog.md +105 -0
- package/skills/project-docs/references/discovery.md +127 -0
- package/skills/project-docs/references/readme.md +80 -0
- package/skills/project-docs/references/release.md +84 -0
- package/skills/project-docs/references/roadmap.md +93 -0
- package/skills/project-docs/scripts/state.ts +316 -0
- package/skills/refactor/SKILL.md +100 -125
- package/skills/refactor/references/handoff.md +35 -0
- package/skills/refactor/references/signals.md +54 -0
- package/skills/refactor/scripts/inventory.ts +587 -0
- package/extensions/file-browse/README.md +0 -467
- package/extensions/file-browse/server.ts +0 -134
- package/extensions/plan/README.md +0 -709
- package/extensions/plan/config.ts +0 -27
- package/extensions/wechat/outbound.ts +0 -100
- package/extensions/wechat/qrcode.ts +0 -46
- package/skills/changelog/SKILL.md +0 -148
- /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
- /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
- /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
- /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
- /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
- /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
- /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
- /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
- /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The browse pages' unified-diff body: one line per row, with the gutter, the
|
|
3
|
+
* sign, and the hunk sections.
|
|
4
|
+
*
|
|
5
|
+
* Split from `html.ts` when that module reached its named ceiling: the diff body
|
|
6
|
+
* is the one part of the page renderers with its own rule — old and new sides are
|
|
7
|
+
* highlighted as two documents per hunk, so the tokenizer carries state within a
|
|
8
|
+
* hunk and never across the unchanged lines a hunk does not show.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { escapeHtml } from "../../../lib/escape.ts";
|
|
12
|
+
import { highlightLines, type HighlightLanguage } from "../../../lib/highlight.ts";
|
|
13
|
+
import type { DiffLine } from "../text/diff.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The inner HTML per diff line. Hunk bodies are highlighted by rebuilding the
|
|
17
|
+
* old side (context + deleted) and the new side (context + added), so the
|
|
18
|
+
* tokenizer carries state within a hunk; meta, hunk, and note lines stay plain.
|
|
19
|
+
* State does not cross hunks, since the unchanged lines between them are absent.
|
|
20
|
+
*/
|
|
21
|
+
function highlightDiffText(lines: DiffLine[], language?: HighlightLanguage): string[] {
|
|
22
|
+
if (!language) return lines.map((line) => escapeHtml(line.text));
|
|
23
|
+
const out: string[] = new Array(lines.length);
|
|
24
|
+
let index = 0;
|
|
25
|
+
while (index < lines.length) {
|
|
26
|
+
const kind = lines[index].kind;
|
|
27
|
+
if (kind !== "ctx" && kind !== "add" && kind !== "del") {
|
|
28
|
+
out[index] = escapeHtml(lines[index].text);
|
|
29
|
+
index++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const start = index;
|
|
33
|
+
const oldDoc: string[] = [];
|
|
34
|
+
const newDoc: string[] = [];
|
|
35
|
+
while (index < lines.length) {
|
|
36
|
+
const body = lines[index].kind;
|
|
37
|
+
if (body !== "ctx" && body !== "add" && body !== "del") break;
|
|
38
|
+
if (body === "ctx") {
|
|
39
|
+
oldDoc.push(lines[index].text);
|
|
40
|
+
newDoc.push(lines[index].text);
|
|
41
|
+
} else if (body === "add") {
|
|
42
|
+
newDoc.push(lines[index].text);
|
|
43
|
+
} else {
|
|
44
|
+
oldDoc.push(lines[index].text);
|
|
45
|
+
}
|
|
46
|
+
index++;
|
|
47
|
+
}
|
|
48
|
+
const oldHtml = highlightLines(oldDoc, language);
|
|
49
|
+
const newHtml = highlightLines(newDoc, language);
|
|
50
|
+
let oldIndex = 0;
|
|
51
|
+
let newIndex = 0;
|
|
52
|
+
for (let cursor = start; cursor < index; cursor++) {
|
|
53
|
+
const body = lines[cursor].kind;
|
|
54
|
+
if (body === "ctx") {
|
|
55
|
+
out[cursor] = newHtml[newIndex];
|
|
56
|
+
newIndex++;
|
|
57
|
+
oldIndex++;
|
|
58
|
+
} else if (body === "add") {
|
|
59
|
+
out[cursor] = newHtml[newIndex];
|
|
60
|
+
newIndex++;
|
|
61
|
+
} else {
|
|
62
|
+
out[cursor] = oldHtml[oldIndex];
|
|
63
|
+
oldIndex++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Render unified-diff lines with a two-column gutter and per-kind styling. */
|
|
71
|
+
export function renderDiffLines(lines: DiffLine[], language?: HighlightLanguage): string {
|
|
72
|
+
const html = highlightDiffText(lines, language);
|
|
73
|
+
// Every hunk is a section so its sticky header is bounded by its own body;
|
|
74
|
+
// the section also gives the keyboard a stable, non-sticky scroll target.
|
|
75
|
+
const out: string[] = [];
|
|
76
|
+
let hunks = 0;
|
|
77
|
+
let open = false;
|
|
78
|
+
lines.forEach((line, index) => {
|
|
79
|
+
if (line.kind === "hunk") {
|
|
80
|
+
if (open) out.push("</span>");
|
|
81
|
+
hunks += 1;
|
|
82
|
+
out.push(`<span class="bf-diff-hunk" id="hunk-${hunks}" data-hunk="${hunks}">`);
|
|
83
|
+
open = true;
|
|
84
|
+
}
|
|
85
|
+
const sign = line.kind === "add" ? "+" : line.kind === "del" ? "-" : line.kind === "ctx" ? " " : "";
|
|
86
|
+
const gutter = `<span class="browse-diff-no" aria-hidden="true">${line.oldNo ?? ""}</span><span class="browse-diff-no" aria-hidden="true">${line.newNo ?? ""}</span>`;
|
|
87
|
+
const marker = `<span class="browse-diff-sign" aria-hidden="true">${sign}</span>`;
|
|
88
|
+
out.push(
|
|
89
|
+
`<span class="bf-code__line bf-diff-line bf-diff-line--${line.kind}">${gutter}${marker}${html[index]}</span>`,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
if (open) out.push("</span>");
|
|
93
|
+
return out.join("");
|
|
94
|
+
}
|
|
@@ -8,17 +8,19 @@
|
|
|
8
8
|
* the page-only `browse-*` rules from `styles.ts`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
11
|
+
import { brandLockup, docHead, type KeyHint, keysSheet, themeToggle } from "../../../lib/web/index.ts";
|
|
12
|
+
import { escapeHtml } from "../../../lib/escape.ts";
|
|
13
|
+
import { renderChanges, renderGit, renderLiveChrome, renderLiveStatus, type LivePage } from "./chrome.ts";
|
|
14
|
+
import { type DirEntry, type FileView, type Listing, type TreeNode } from "../files/files.ts";
|
|
15
|
+
import { formatSize } from "../../../lib/format.ts";
|
|
16
|
+
import { DIFF_MODES, statusLetter, type ChangedFile, type DiffMode } from "../text/diff.ts";
|
|
17
|
+
import { renderDiffLines } from "./diff-html.ts";
|
|
18
|
+
import type { DiffFileView, GitStatus } from "../git/git.ts";
|
|
19
|
+
import type { ChangeIndex } from "../live/live.ts";
|
|
20
|
+
import { detectLanguage, highlightLines, type HighlightLanguage } from "../../../lib/highlight.ts";
|
|
19
21
|
import { FOLDER_ICON, iconHref, languageOf, SPRITE, type IconSpec } from "./icons.ts";
|
|
20
22
|
import { renderMarkdown } from "./markdown.ts";
|
|
21
|
-
import { encodePath } from "
|
|
23
|
+
import { encodePath } from "../files/paths.ts";
|
|
22
24
|
import { BROWSE_CSS } from "./styles.ts";
|
|
23
25
|
|
|
24
26
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
@@ -36,16 +38,6 @@ function iconMarkup(spec: IconSpec, hidden = false): string {
|
|
|
36
38
|
return `<svg class="browse-cat ${spec.colorClass}"${hidden ? " hidden" : ""} aria-hidden="true" title="${escapeHtml(spec.label)}"><use href="${iconHref(spec.icon)}"/></svg>`;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
/** The header's git chip: the branch, or the detached head; changes are the changes chip's job. */
|
|
40
|
-
function renderGit(git: GitStatus | undefined): string {
|
|
41
|
-
if (!git) return "";
|
|
42
|
-
const name = git.branch ?? git.head;
|
|
43
|
-
if (!name) return "";
|
|
44
|
-
const glyph = git.branch ? GLYPHS.branch : GLYPHS.detached;
|
|
45
|
-
const aria = git.branch ? `git branch ${git.branch}` : `git detached at ${git.head ?? ""}`;
|
|
46
|
-
return `<span class="bf-chip browse-git" title="${escapeHtml(aria)}" aria-label="${escapeHtml(aria)}"><span class="browse-git__glyph" aria-hidden="true">${escapeHtml(glyph)}</span><span class="browse-git__name">${escapeHtml(name)}</span></span>`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
41
|
/**
|
|
50
42
|
* The changed-path index for markers, plus the totals shown in the chip. It is
|
|
51
43
|
* the same index the live status feed sends, so the page and its updates agree
|
|
@@ -66,55 +58,6 @@ function changeMark(summary: ChangeSummary | undefined, rel: string, isDir: bool
|
|
|
66
58
|
: "";
|
|
67
59
|
}
|
|
68
60
|
|
|
69
|
-
/** The live chrome a page renders: how often its stream is sent a frame. */
|
|
70
|
-
export interface LivePage {
|
|
71
|
-
intervalMs: number;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* The header's connection chip. It starts hidden and says "connecting": the
|
|
76
|
-
* client reveals and paints it from the event stream, so a page with JS off — or
|
|
77
|
-
* with live status disabled — never shows a state it cannot keep true.
|
|
78
|
-
*
|
|
79
|
-
* It is deliberately not a `role="status"` region: the header is where a reader
|
|
80
|
-
* looks for the page's state, not a place to announce every change to it.
|
|
81
|
-
*/
|
|
82
|
-
function renderLiveChip(live: LivePage | undefined): string {
|
|
83
|
-
if (!live) return "";
|
|
84
|
-
return `<span class="bf-chip bf-chip--accent browse-live" id="live-state" title="Live status from the browse server" data-interval="${live.intervalMs}" hidden><span class="browse-live__glyph" aria-hidden="true">${escapeHtml(GLYPHS.browse)}</span><span class="browse-live__text">connecting</span></span>`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The footer's live line, hidden until the first frame: uptime and requests are
|
|
89
|
-
* server facts the rendered page does not know at build time.
|
|
90
|
-
*/
|
|
91
|
-
function renderLiveStatus(): string {
|
|
92
|
-
return `<span class="browse-live-line" id="live-status" hidden></span>`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The header's changes chip, linking to the diff overview. With live status on,
|
|
97
|
-
* a hidden shell is always rendered so the chip can appear and disappear as the
|
|
98
|
-
* working tree changes, without the client inserting elements into the header.
|
|
99
|
-
*/
|
|
100
|
-
function renderChanges(summary: ChangeSummary | undefined, live: LivePage | undefined): string {
|
|
101
|
-
const count = summary?.count ?? 0;
|
|
102
|
-
if (count <= 0) {
|
|
103
|
-
return live
|
|
104
|
-
? `<a class="bf-chip browse-diff" id="browse-changes" href="/diff" title="View uncommitted changes" hidden></a>`
|
|
105
|
-
: "";
|
|
106
|
-
}
|
|
107
|
-
const label = `${count} change${count === 1 ? "" : "s"}`;
|
|
108
|
-
const insertions = summary?.insertions ?? 0;
|
|
109
|
-
const deletions = summary?.deletions ?? 0;
|
|
110
|
-
const totals =
|
|
111
|
-
insertions + deletions > 0
|
|
112
|
-
? ` <span class="browse-diff-totals"><span class="browse-diff-add">+${insertions}</span> <span class="browse-diff-del">−${deletions}</span></span>`
|
|
113
|
-
: "";
|
|
114
|
-
const aria = `${label} against HEAD${insertions + deletions > 0 ? `, ${insertions} insertions and ${deletions} deletions` : ""}`;
|
|
115
|
-
return `<a class="bf-chip browse-diff" id="browse-changes" href="/diff" title="View uncommitted changes" aria-label="${escapeHtml(aria)}"><span class="browse-diff__dot" aria-hidden="true">●</span>${escapeHtml(label)}${totals}</a>`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
61
|
function hrefFor(entry: { rel: string; isDir: boolean }): string {
|
|
119
62
|
return entry.isDir ? `/browse/${encodePath(entry.rel)}` : `/view/${encodePath(entry.rel)}`;
|
|
120
63
|
}
|
|
@@ -157,7 +100,7 @@ ${SPRITE}
|
|
|
157
100
|
${renderGit(layout.git)}
|
|
158
101
|
${renderChanges(layout.summary, layout.live)}
|
|
159
102
|
<span class="bf-chip bf-chip--accent">read-only</span>
|
|
160
|
-
${
|
|
103
|
+
${renderLiveChrome(layout.live)}
|
|
161
104
|
<button type="button" class="bf-btn bf-btn--icon browse-keys-btn" id="keys-open" aria-label="Keyboard shortcuts" title="Keyboard shortcuts">?</button>
|
|
162
105
|
${themeToggle()}
|
|
163
106
|
</div><div class="browse-progress" aria-hidden="true"><span class="browse-progress__fill" id="progress-fill"></span></div></header>
|
|
@@ -339,8 +282,14 @@ function pageKeys(...extra: KeyHint[]): KeyHint[] {
|
|
|
339
282
|
];
|
|
340
283
|
}
|
|
341
284
|
|
|
342
|
-
/**
|
|
343
|
-
function
|
|
285
|
+
/** Whether the page renders lines with a number beside them: plain text, or raw Markdown. */
|
|
286
|
+
function numberedLines(page: FilePage): boolean {
|
|
287
|
+
return page.file.kind === "text" || (page.file.kind === "markdown" && page.markdownRaw === true);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** The card-head controls: Markdown mode, line wrapping, and the copy actions,
|
|
291
|
+
* plus the download the diff head keeps in the same group. */
|
|
292
|
+
function renderFileActions(page: FilePage, download: string): string {
|
|
344
293
|
const isMarkdown = page.file.kind === "markdown";
|
|
345
294
|
const rawHref = `/raw/${encodePath(page.rel)}`;
|
|
346
295
|
const viewHref = `/view/${encodePath(page.rel)}`;
|
|
@@ -359,7 +308,7 @@ function renderFileActions(page: FilePage): string {
|
|
|
359
308
|
: `<a class="bf-segmented__item" href="${viewHref}?raw=1">raw</a>`;
|
|
360
309
|
groups.push(`<span class="bf-segmented" role="group" aria-label="Markdown view">${rendered}${raw}</span>`);
|
|
361
310
|
}
|
|
362
|
-
if (
|
|
311
|
+
if (numberedLines(page)) {
|
|
363
312
|
groups.push(
|
|
364
313
|
`<button type="button" class="bf-btn" id="wrap-toggle" aria-pressed="false" title="Toggle line wrapping">wrap</button>`,
|
|
365
314
|
);
|
|
@@ -372,7 +321,12 @@ function renderFileActions(page: FilePage): string {
|
|
|
372
321
|
groups.push(
|
|
373
322
|
`<button type="button" class="bf-btn" data-copy="path" data-value="${escapeHtml(page.rel)}" title="Copy the root-relative path">copy path</button>`,
|
|
374
323
|
);
|
|
375
|
-
|
|
324
|
+
if (numberedLines(page)) {
|
|
325
|
+
groups.push(
|
|
326
|
+
`<button type="button" class="bf-btn" id="copy-reference" disabled title="Select lines in the file first">copy reference</button>`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
return `<span class="bf-card__actions">${groups.join("")}${download}</span>`;
|
|
376
330
|
}
|
|
377
331
|
|
|
378
332
|
/** Render a file page (text, Markdown, image, binary, or over-size). */
|
|
@@ -384,14 +338,15 @@ export function renderFilePage(page: FilePage): string {
|
|
|
384
338
|
// about the file; the language label, size, and date are the useful metadata.
|
|
385
339
|
const meta = `${spec.label} · ${formatSize(page.file.size)} · ${formatDate(page.file.mtimeMs)}`;
|
|
386
340
|
const download = `<a class="bf-btn" href="/raw/${encodePath(page.rel)}" download>download</a>`;
|
|
387
|
-
const head = `<div class="bf-card__head">${iconMarkup(spec)}<h1 class="bf-card__title">${escapeHtml(name)}</h1><span class="bf-card__meta">${escapeHtml(meta)}</span><span class="bf-spacer"></span>${renderFileActions(page)}
|
|
341
|
+
const head = `<div class="bf-card__head">${iconMarkup(spec)}<h1 class="bf-card__title">${escapeHtml(name)}</h1><span class="bf-card__meta">${escapeHtml(meta)}</span><span class="bf-spacer"></span>${renderFileActions(page, download)}</div>`;
|
|
388
342
|
|
|
389
343
|
const isMarkdown = page.file.kind === "markdown";
|
|
390
344
|
const rendered = isMarkdown && !page.markdownRaw;
|
|
391
|
-
const
|
|
345
|
+
const numbered = numberedLines(page);
|
|
392
346
|
const keys = pageKeys(
|
|
393
347
|
...(isMarkdown ? [{ keys: "r", label: "Toggle rendered and raw" }] : []),
|
|
394
|
-
...(
|
|
348
|
+
...(numbered ? [{ keys: "u", label: "Toggle line wrapping" }] : []),
|
|
349
|
+
...(numbered ? [{ keys: "y", label: "Copy a reference to the selected lines" }] : []),
|
|
395
350
|
);
|
|
396
351
|
|
|
397
352
|
let content: string;
|
|
@@ -403,7 +358,7 @@ export function renderFilePage(page: FilePage): string {
|
|
|
403
358
|
});
|
|
404
359
|
content = `<article class="bf-md">${html}</article>`;
|
|
405
360
|
} else {
|
|
406
|
-
content = `<pre class="bf-code bf-code--numbered"><code>${renderCodeLines(page.file.lines, language)}</code></pre>`;
|
|
361
|
+
content = `<pre class="bf-code bf-code--numbered" data-path="${escapeHtml(page.rel)}"><code>${renderCodeLines(page.file.lines, language)}</code></pre>`;
|
|
407
362
|
}
|
|
408
363
|
if (page.file.truncated)
|
|
409
364
|
note = `<p class="bf-note">Showing the first ${page.file.lines.length} of ${page.file.totalLines} lines.</p>`;
|
|
@@ -447,87 +402,6 @@ function statusWord(letter: string): string {
|
|
|
447
402
|
return STATUS_LABEL[letter] ?? "changed";
|
|
448
403
|
}
|
|
449
404
|
|
|
450
|
-
/**
|
|
451
|
-
* The inner HTML per diff line. Hunk bodies are highlighted by rebuilding the
|
|
452
|
-
* old side (context + deleted) and the new side (context + added), so the
|
|
453
|
-
* tokenizer carries state within a hunk; meta, hunk, and note lines stay plain.
|
|
454
|
-
* State does not cross hunks, since the unchanged lines between them are absent.
|
|
455
|
-
*/
|
|
456
|
-
function highlightDiffText(lines: DiffLine[], language?: HighlightLanguage): string[] {
|
|
457
|
-
if (!language) return lines.map((line) => escapeHtml(line.text));
|
|
458
|
-
const out: string[] = new Array(lines.length);
|
|
459
|
-
let index = 0;
|
|
460
|
-
while (index < lines.length) {
|
|
461
|
-
const kind = lines[index].kind;
|
|
462
|
-
if (kind !== "ctx" && kind !== "add" && kind !== "del") {
|
|
463
|
-
out[index] = escapeHtml(lines[index].text);
|
|
464
|
-
index++;
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
const start = index;
|
|
468
|
-
const oldDoc: string[] = [];
|
|
469
|
-
const newDoc: string[] = [];
|
|
470
|
-
while (index < lines.length) {
|
|
471
|
-
const body = lines[index].kind;
|
|
472
|
-
if (body !== "ctx" && body !== "add" && body !== "del") break;
|
|
473
|
-
if (body === "ctx") {
|
|
474
|
-
oldDoc.push(lines[index].text);
|
|
475
|
-
newDoc.push(lines[index].text);
|
|
476
|
-
} else if (body === "add") {
|
|
477
|
-
newDoc.push(lines[index].text);
|
|
478
|
-
} else {
|
|
479
|
-
oldDoc.push(lines[index].text);
|
|
480
|
-
}
|
|
481
|
-
index++;
|
|
482
|
-
}
|
|
483
|
-
const oldHtml = highlightLines(oldDoc, language);
|
|
484
|
-
const newHtml = highlightLines(newDoc, language);
|
|
485
|
-
let oldIndex = 0;
|
|
486
|
-
let newIndex = 0;
|
|
487
|
-
for (let cursor = start; cursor < index; cursor++) {
|
|
488
|
-
const body = lines[cursor].kind;
|
|
489
|
-
if (body === "ctx") {
|
|
490
|
-
out[cursor] = newHtml[newIndex];
|
|
491
|
-
newIndex++;
|
|
492
|
-
oldIndex++;
|
|
493
|
-
} else if (body === "add") {
|
|
494
|
-
out[cursor] = newHtml[newIndex];
|
|
495
|
-
newIndex++;
|
|
496
|
-
} else {
|
|
497
|
-
out[cursor] = oldHtml[oldIndex];
|
|
498
|
-
oldIndex++;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
return out;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/** Render unified-diff lines with a two-column gutter and per-kind styling. */
|
|
506
|
-
export function renderDiffLines(lines: DiffLine[], language?: HighlightLanguage): string {
|
|
507
|
-
const html = highlightDiffText(lines, language);
|
|
508
|
-
// Every hunk is a section so its sticky header is bounded by its own body;
|
|
509
|
-
// the section also gives the keyboard a stable, non-sticky scroll target.
|
|
510
|
-
const out: string[] = [];
|
|
511
|
-
let hunks = 0;
|
|
512
|
-
let open = false;
|
|
513
|
-
lines.forEach((line, index) => {
|
|
514
|
-
if (line.kind === "hunk") {
|
|
515
|
-
if (open) out.push("</span>");
|
|
516
|
-
hunks += 1;
|
|
517
|
-
out.push(`<span class="bf-diff-hunk" id="hunk-${hunks}" data-hunk="${hunks}">`);
|
|
518
|
-
open = true;
|
|
519
|
-
}
|
|
520
|
-
const sign = line.kind === "add" ? "+" : line.kind === "del" ? "-" : line.kind === "ctx" ? " " : "";
|
|
521
|
-
const gutter = `<span class="browse-diff-no" aria-hidden="true">${line.oldNo ?? ""}</span><span class="browse-diff-no" aria-hidden="true">${line.newNo ?? ""}</span>`;
|
|
522
|
-
const marker = `<span class="browse-diff-sign" aria-hidden="true">${sign}</span>`;
|
|
523
|
-
out.push(
|
|
524
|
-
`<span class="bf-code__line bf-diff-line bf-diff-line--${line.kind}">${gutter}${marker}${html[index]}</span>`,
|
|
525
|
-
);
|
|
526
|
-
});
|
|
527
|
-
if (open) out.push("</span>");
|
|
528
|
-
return out.join("");
|
|
529
|
-
}
|
|
530
|
-
|
|
531
405
|
/** The scope label for a mode. */
|
|
532
406
|
function modeScope(mode: DiffMode): string {
|
|
533
407
|
if (mode === "staged") return "staged vs HEAD";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { extname } from "node:path";
|
|
8
|
-
import { IMAGE_EXTENSIONS } from "
|
|
8
|
+
import { IMAGE_EXTENSIONS } from "../files/files.ts";
|
|
9
9
|
|
|
10
10
|
export type IconName = "folder" | "file-code" | "file-text" | "file-image" | "file-json" | "file-archive" | "file";
|
|
11
11
|
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
* It does nothing at all unless the server rendered the connection chip, which
|
|
11
11
|
* is what makes `live: false` and JS-off leave the page exactly as it was.
|
|
12
|
+
*
|
|
13
|
+
* The folder check is the other half: the stream is shared by every open page, so
|
|
14
|
+
* it cannot say what *this* page's rows show. A page that lists a folder carries
|
|
15
|
+
* the revision of what it drew, and asks the server for the folder's current one
|
|
16
|
+
* once per interval; a difference reveals a reload control instead of touching
|
|
17
|
+
* the document, because a reload is the only honest way to add a row.
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
20
|
export const LIVE_CLIENT_JS = ` // Live status: one event stream feeds the header chips, the tree and listing
|
|
@@ -19,9 +25,14 @@ export const LIVE_CLIENT_JS = ` // Live status: one event stream feeds the heade
|
|
|
19
25
|
var liveLine = document.getElementById("live-status");
|
|
20
26
|
var liveGit = document.querySelector(".browse-git");
|
|
21
27
|
var liveChanges = document.getElementById("browse-changes");
|
|
22
|
-
var liveInterval = Number(liveChip.getAttribute("data-interval")) ||
|
|
28
|
+
var liveInterval = Number(liveChip.getAttribute("data-interval")) || 5000;
|
|
23
29
|
var liveStale = 0;
|
|
24
30
|
var liveTone = "";
|
|
31
|
+
var liveRev = "";
|
|
32
|
+
var liveDir = liveChip.getAttribute("data-dir");
|
|
33
|
+
var liveDirRev = liveChip.getAttribute("data-rev");
|
|
34
|
+
var liveReload = document.getElementById("reload-hint");
|
|
35
|
+
var liveDirTimer = 0;
|
|
25
36
|
|
|
26
37
|
function formatSpan(ms) {
|
|
27
38
|
var seconds = Math.max(0, Math.round(ms / 1000));
|
|
@@ -88,7 +99,12 @@ export const LIVE_CLIENT_JS = ` // Live status: one event stream feeds the heade
|
|
|
88
99
|
});
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
// One revision covers the chip and the markers: a frame that did not move the
|
|
103
|
+
// change set is not painted at all, so a tick on an idle tree costs no DOM
|
|
104
|
+
// work however many rows the page drew.
|
|
91
105
|
function paintLiveChanges(changes) {
|
|
106
|
+
if (changes && changes.rev === liveRev) return;
|
|
107
|
+
liveRev = changes && changes.rev ? changes.rev : "";
|
|
92
108
|
var count = changes ? changes.count : 0;
|
|
93
109
|
if (liveChanges) {
|
|
94
110
|
liveChanges.hidden = count <= 0;
|
|
@@ -136,6 +152,32 @@ export const LIVE_CLIENT_JS = ` // Live status: one event stream feeds the heade
|
|
|
136
152
|
paintLiveChanges(snapshot.changes);
|
|
137
153
|
}
|
|
138
154
|
|
|
155
|
+
// The folder the page drew, checked against the disk on the frame cadence. It
|
|
156
|
+
// waits for the first frame, never runs in a hidden tab, and stops as soon as
|
|
157
|
+
// it has something to say: the control is the fix, and a reload ends the
|
|
158
|
+
// question.
|
|
159
|
+
function showLiveReload() {
|
|
160
|
+
window.clearInterval(liveDirTimer);
|
|
161
|
+
liveDirTimer = 0;
|
|
162
|
+
if (!liveReload || !liveReload.hidden) return;
|
|
163
|
+
liveReload.hidden = false;
|
|
164
|
+
liveReload.addEventListener("click", function () { window.location.reload(); });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function checkLiveDir() {
|
|
168
|
+
if (document.hidden || liveTone !== "success") return;
|
|
169
|
+
fetch("/api/dir-rev?path=" + encodeURIComponent(liveDir))
|
|
170
|
+
.then(function (response) {
|
|
171
|
+
if (!response.ok) throw new Error("check failed");
|
|
172
|
+
return response.json();
|
|
173
|
+
})
|
|
174
|
+
.then(function (answer) {
|
|
175
|
+
var rev = answer.rev;
|
|
176
|
+
if (typeof rev === "string" && rev !== liveDirRev) showLiveReload();
|
|
177
|
+
})
|
|
178
|
+
.catch(function () { /* the stream already reports a dead server */ });
|
|
179
|
+
}
|
|
180
|
+
|
|
139
181
|
if (window.EventSource) {
|
|
140
182
|
var liveStream = new EventSource("/events");
|
|
141
183
|
liveStream.addEventListener("open", function () {
|
|
@@ -152,7 +194,11 @@ export const LIVE_CLIENT_JS = ` // Live status: one event stream feeds the heade
|
|
|
152
194
|
window.addEventListener("pagehide", function () {
|
|
153
195
|
liveStream.close();
|
|
154
196
|
window.clearTimeout(liveStale);
|
|
197
|
+
window.clearInterval(liveDirTimer);
|
|
155
198
|
});
|
|
199
|
+
if (liveDir !== null && liveDirRev && liveReload) {
|
|
200
|
+
liveDirTimer = window.setInterval(checkLiveDir, liveInterval);
|
|
201
|
+
}
|
|
156
202
|
}
|
|
157
203
|
}
|
|
158
204
|
`;
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
* policy and raw HTML do not.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { decodeCodePoint, escapeHtml } from "
|
|
29
|
-
import { fenceLanguage, highlightLines } from "
|
|
28
|
+
import { decodeCodePoint, escapeHtml } from "../../../lib/escape.ts";
|
|
29
|
+
import { fenceLanguage, highlightLines } from "../../../lib/highlight.ts";
|
|
30
30
|
import {
|
|
31
31
|
closesSuppressed,
|
|
32
32
|
closeHtmlSink,
|
|
@@ -126,8 +126,8 @@ function createContext(baseDir: string, defs: Map<string, LinkDefinition>, html:
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/** Fold a reference label the way CommonMark does: trim, collapse, case-fold. */
|
|
130
|
-
function
|
|
129
|
+
/** Fold a reference label the way CommonMark does: trim, collapse, case-fold. Not `lib/session-label.ts`, which names a session. */
|
|
130
|
+
function referenceLabel(label: string): string {
|
|
131
131
|
return label.trim().replace(/\s+/g, " ").toLowerCase();
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -180,7 +180,7 @@ function collectDefinitions(lines: string[], html: boolean): { defs: Map<string,
|
|
|
180
180
|
body.push(line);
|
|
181
181
|
continue;
|
|
182
182
|
}
|
|
183
|
-
const key =
|
|
183
|
+
const key = referenceLabel(match[1]);
|
|
184
184
|
if (!defs.has(key)) {
|
|
185
185
|
const dest = match[2].startsWith("<") && match[2].endsWith(">") ? match[2].slice(1, -1) : match[2];
|
|
186
186
|
defs.set(key, { dest, title: match[3] });
|
|
@@ -630,11 +630,11 @@ function parseReferenceLink(text: string, start: number, ctx: Context): ParsedLi
|
|
|
630
630
|
const end = matchBracket(text, close + 1);
|
|
631
631
|
if (end !== -1) {
|
|
632
632
|
const raw = text.slice(close + 2, end);
|
|
633
|
-
const def = ctx.defs.get(
|
|
633
|
+
const def = ctx.defs.get(referenceLabel(raw === "" ? label : raw));
|
|
634
634
|
if (def) return { label, dest: def.dest, title: def.title, end: end + 1 };
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
|
-
const def = ctx.defs.get(
|
|
637
|
+
const def = ctx.defs.get(referenceLabel(label));
|
|
638
638
|
if (def) return { label, dest: def.dest, title: def.title, end: close + 1 };
|
|
639
639
|
return undefined;
|
|
640
640
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* how `<details>` wraps Markdown.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { decodeCodePoint, escapeHtml } from "
|
|
28
|
+
import { decodeCodePoint, escapeHtml } from "../../../lib/escape.ts";
|
|
29
29
|
import { resolveImage, resolveLink } from "./urls.ts";
|
|
30
30
|
|
|
31
31
|
/** Elements re-emitted with sanitized attributes. */
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
WEB_COMPONENTS_CSS,
|
|
13
13
|
WEB_LAYOUT_CSS,
|
|
14
14
|
WEB_THEME_CSS,
|
|
15
|
-
} from "
|
|
15
|
+
} from "../../../lib/web/index.ts";
|
|
16
16
|
|
|
17
17
|
/** The shared shell, components, and the page-only `browse-*` rules. */
|
|
18
18
|
export const BROWSE_CSS = `${WEB_BASE_CSS}
|
|
@@ -37,7 +37,6 @@ ${WEB_COMPONENTS_CSS}
|
|
|
37
37
|
.browse-dotfile .bf-table__name a{opacity:.72}
|
|
38
38
|
.browse-broken{color:var(--muted);text-decoration:line-through}
|
|
39
39
|
.browse-link-mark{color:var(--muted);font-size:var(--fs-xs)}
|
|
40
|
-
.browse-when{display:none}
|
|
41
40
|
body.palette-open{overflow:hidden}
|
|
42
41
|
.browse-diff{font-family:var(--font-mono);gap:5px}
|
|
43
42
|
.browse-diff:hover{color:var(--accent);text-decoration:none}
|
|
@@ -47,6 +46,8 @@ body.palette-open{overflow:hidden}
|
|
|
47
46
|
swap only changes the colour, and the footer line reads as one muted clause. */
|
|
48
47
|
.browse-live{font-family:var(--font-mono);border:1px solid currentColor;border-radius:var(--radius-pill);padding:2px 10px}
|
|
49
48
|
.browse-live__glyph{font-size:9px;line-height:1}
|
|
49
|
+
.browse-reload{border:1px solid currentColor;border-radius:var(--radius-pill);padding:2px 10px;cursor:pointer;font-family:var(--font-mono)}
|
|
50
|
+
.browse-reload:hover{text-decoration:underline}
|
|
50
51
|
.browse-live-line{color:var(--muted);font-variant-numeric:tabular-nums}
|
|
51
52
|
.browse-live-line::after{content:" · "}
|
|
52
53
|
.browse-diff-totals{font-family:var(--font-mono)}
|
|
@@ -91,8 +92,24 @@ body.palette-open{overflow:hidden}
|
|
|
91
92
|
.browse-measure .bf-md>*{max-width:var(--browse-measure);margin-inline:auto}
|
|
92
93
|
/* Tables start at the prose column and take only the gutter to its right, so a
|
|
93
94
|
wide window widens them without a dead band on their left. Code stays inside
|
|
94
|
-
the measure with the rest of the prose.
|
|
95
|
-
|
|
95
|
+
the measure with the rest of the prose. The frame is also the container a
|
|
96
|
+
squeezed table asks about: it is the pane, not the window, that narrows one. */
|
|
97
|
+
.browse-measure .bf-md>.md-table-frame{max-width:none;padding-left:var(--browse-gutter);container-type:inline-size}
|
|
98
|
+
/* A table spans the reading column rather than stopping at its own content: a
|
|
99
|
+
shrink-wrapped table leaves a ragged band beside itself — 254px on a 2560px
|
|
100
|
+
screen, and 300px beside the three-column tables at 1440 — which the reader
|
|
101
|
+
cannot tell from a column that failed to draw. The card is wider than the text
|
|
102
|
+
it holds, so the measure is the ceiling: a table ends where the prose does, and
|
|
103
|
+
below the floor's threshold the minimum still wins and the wrapper scrolls. */
|
|
104
|
+
.browse-measure .bf-md .md-table-frame table{width:100%;max-width:var(--browse-measure)}
|
|
105
|
+
/* In a pane this narrow, auto layout gives the prose cell whatever an unbreakable
|
|
106
|
+
code span leaves — one word per line, and a four-column table read as a
|
|
107
|
+
300px-tall row. A floor on the cells makes the table wider than the pane
|
|
108
|
+
instead, so every column keeps a readable width and the wrapper scrolls them;
|
|
109
|
+
a table that still fits is left as it was. */
|
|
110
|
+
@container (max-width:600px){
|
|
111
|
+
.browse-measure .bf-md .md-table-frame td{min-width:14em}
|
|
112
|
+
}
|
|
96
113
|
/* A table wider than the pane scrolls, but an overlay scrollbar is invisible
|
|
97
114
|
until it moves; a gradient on the frame marks the columns still out of view.
|
|
98
115
|
It has to be the frame: the scroller clips its own content, so a pseudo-element
|
|
@@ -114,10 +131,19 @@ body.palette-open{overflow:hidden}
|
|
|
114
131
|
.browse-side .bf-tree{flex:1 1 auto;min-height:0;overflow:auto}
|
|
115
132
|
}
|
|
116
133
|
@media (max-width:820px){
|
|
117
|
-
.browse-root{order:9;flex:1 1 100
|
|
134
|
+
.browse-root{order:9;flex:1 1 100%}
|
|
118
135
|
}
|
|
119
136
|
@media (max-width:520px){
|
|
120
137
|
.bf-toolbar .bf-theme-toggle{margin-left:auto}
|
|
138
|
+
/* A phone has no room for four button rows above the file: the actions keep one
|
|
139
|
+
line and scroll instead, so the document starts where it should. A control
|
|
140
|
+
keeps its own width — shrunk, a label wraps and a segmented control clips. */
|
|
141
|
+
.bf-card__actions{max-width:100%;flex-wrap:nowrap;overflow-x:auto}
|
|
142
|
+
.bf-card__actions>*{flex:0 0 auto}
|
|
143
|
+
/* A trailing date or one-word change state costs the column a reader is choosing
|
|
144
|
+
between: at 390px the listing gave the name 106px and every entry truncated,
|
|
145
|
+
and the diff gave a path 140px while "modified" held 130. */
|
|
146
|
+
.browse-listing .bf-table__when,.browse-diff .browse-diff-word{display:none}
|
|
121
147
|
}
|
|
122
148
|
/* A printed page scrolls nothing: the card and its body open up, the pinned hunk
|
|
123
149
|
header stops pinning, and the progress hairline belongs to the screen. */
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* intent, and the one row the keyboard is on.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { SIDEBAR_COLLAPSE_MAX } from "
|
|
11
|
+
import { SIDEBAR_COLLAPSE_MAX } from "../../../lib/web/index.ts";
|
|
12
12
|
|
|
13
13
|
export const TREE_CLIENT_JS = ` var tree = document.querySelector("nav.bf-tree");
|
|
14
14
|
var filter = document.getElementById("filter");
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Type, type Static } from "typebox";
|
|
6
|
+
import { READ_DOC_TOOL } from "../../lib/tool-names.ts";
|
|
6
7
|
import type { FormatId } from "./formats.ts";
|
|
7
8
|
|
|
8
9
|
/** The tool's registered name, shared by the definition and its renderer. */
|
|
9
|
-
export const TOOL_NAME =
|
|
10
|
+
export const TOOL_NAME = READ_DOC_TOOL;
|
|
10
11
|
|
|
11
12
|
export const MIN_CHARS = 200;
|
|
12
13
|
export const MAX_CHARS = 100_000;
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { isPursuedGoal } from "../../lib/goal-state.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { isRemoteTurn, remoteLanguage } from "../../lib/interaction.ts";
|
|
23
|
+
import { PLAN_CONSENT, PLAN_CONSENT_SHORT, PLAN_CONSENT_SHORT_ZH, requestPlanMode } from "../../lib/plan-entry.ts";
|
|
23
24
|
import { isPlanModeEnabled } from "../../lib/plan-state.ts";
|
|
24
25
|
import { PLAN_ENTER_TOOL } from "../../lib/tool-names.ts";
|
|
25
26
|
import type { Goal, GoalStatus } from "./types.ts";
|
|
@@ -45,7 +46,12 @@ export async function goalPhase(
|
|
|
45
46
|
if (!options.ask || !ctx.hasUI) return "active";
|
|
46
47
|
// Do not ask about a phase this session cannot enter.
|
|
47
48
|
if (!pi.getAllTools().some((tool) => tool.name === PLAN_ENTER_TOOL)) return "active";
|
|
48
|
-
const
|
|
49
|
+
const consent = isRemoteTurn(ctx)
|
|
50
|
+
? remoteLanguage(ctx) === "zh"
|
|
51
|
+
? PLAN_CONSENT_SHORT_ZH
|
|
52
|
+
: PLAN_CONSENT_SHORT
|
|
53
|
+
: PLAN_CONSENT;
|
|
54
|
+
const approved = await ctx.ui.confirm(PLAN_ASK, consent);
|
|
49
55
|
if (!approved) return "active";
|
|
50
56
|
return (await requestPlanMode(pi, ctx)) ? "planning" : "active";
|
|
51
57
|
}
|