@gavin-hu/my-pi-agent 0.18.0 → 0.20.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 +66 -52
- package/README.zh-CN.md +56 -46
- package/docs/extensions/README.md +34 -0
- package/docs/extensions/board.md +507 -0
- package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
- 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 +561 -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} +62 -52
- package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
- package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
- package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
- package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
- package/docs/extensions/session-bus.md +298 -0
- package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
- package/docs/extensions/team.md +254 -0
- package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
- package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
- package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
- package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
- package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
- package/extensions/board/commands.ts +105 -0
- package/extensions/board/config.ts +36 -0
- package/extensions/board/format.ts +170 -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 +388 -0
- package/extensions/board/result.ts +108 -0
- package/extensions/board/rows.ts +48 -0
- package/extensions/board/schema.ts +90 -0
- package/extensions/board/seat.ts +28 -0
- package/extensions/board/snapshot.ts +42 -0
- package/extensions/board/tools.ts +444 -0
- package/extensions/board/transport/router.ts +91 -0
- package/extensions/board/transport/server.ts +138 -0
- package/extensions/board/types.ts +121 -0
- package/extensions/board/view/client.ts +51 -0
- package/extensions/board/view/dialog.ts +43 -0
- package/extensions/board/view/keys.ts +125 -0
- package/extensions/board/view/live.ts +231 -0
- package/extensions/board/view/names.ts +91 -0
- package/extensions/board/view/page.ts +118 -0
- package/extensions/board/view/render.ts +519 -0
- package/extensions/board/view/styles.ts +228 -0
- package/extensions/board/view/team.ts +327 -0
- package/extensions/bytefly/footer/budget.ts +6 -5
- package/extensions/bytefly/footer/collect/entries.ts +12 -4
- package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
- package/extensions/bytefly/footer/config.ts +19 -8
- package/extensions/bytefly/footer/price/price.ts +30 -8
- package/extensions/bytefly/footer/register.ts +5 -5
- package/extensions/bytefly/footer/render/format.ts +18 -0
- package/extensions/bytefly/footer/render/lines.ts +23 -7
- package/extensions/bytefly/footer/render/segments.ts +117 -31
- package/extensions/bytefly/footer/types.ts +29 -5
- package/extensions/bytefly/footer/unpriced.ts +42 -17
- package/extensions/bytefly/panel/commands.ts +67 -7
- package/extensions/bytefly/panel/filter.ts +68 -0
- package/extensions/bytefly/panel/format.ts +16 -15
- package/extensions/bytefly/panel/join.ts +56 -13
- package/extensions/bytefly/panel/prices.ts +43 -0
- package/extensions/bytefly/panel/settings-runtime.ts +0 -6
- package/extensions/bytefly/panel/settings.ts +75 -43
- package/extensions/bytefly/panel/tui/entries.ts +26 -8
- package/extensions/bytefly/panel/tui/frame.ts +130 -93
- package/extensions/bytefly/panel/tui/keys.ts +352 -0
- package/extensions/bytefly/panel/tui/list-view.ts +25 -46
- package/extensions/bytefly/panel/tui/panes.ts +76 -0
- package/extensions/bytefly/panel/tui/section-view.ts +126 -0
- package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
- package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
- package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
- package/extensions/bytefly/panel/tui.ts +306 -259
- 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/check/commands.ts +2 -1
- package/extensions/context/tui.ts +1 -1
- package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
- package/extensions/file-browse/files/classify.ts +136 -0
- package/extensions/file-browse/files/files.ts +12 -0
- package/extensions/file-browse/files/index.ts +90 -0
- package/extensions/file-browse/files/list.ts +261 -0
- package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
- package/extensions/file-browse/files/tree.ts +101 -0
- 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} +20 -69
- package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
- package/extensions/file-browse/transport/server.ts +110 -0
- package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
- package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
- package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
- package/extensions/file-browse/view/emit.ts +51 -0
- package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
- package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
- package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
- package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
- package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -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/job/runtime.ts +4 -4
- package/extensions/memory/store.ts +12 -5
- package/extensions/plan/blocks.ts +23 -2
- package/extensions/plan/commands.ts +21 -16
- package/extensions/plan/feedback.ts +185 -52
- package/extensions/plan/format.ts +10 -3
- package/extensions/plan/index.ts +23 -5
- package/extensions/plan/plan-files.ts +88 -0
- package/extensions/plan/plan-index.ts +43 -9
- package/extensions/plan/plan-text.ts +33 -23
- package/extensions/plan/plans.ts +27 -34
- package/extensions/plan/policy.ts +30 -17
- package/extensions/plan/review.ts +3 -27
- package/extensions/plan/runtime.ts +52 -11
- package/extensions/plan/sections.ts +112 -0
- package/extensions/plan/steps.ts +3 -22
- package/extensions/plan/todos.ts +51 -3
- package/extensions/plan/tools/exit.ts +20 -95
- package/extensions/plan/tools/todo-seed.ts +4 -2
- package/extensions/plan/tools/write.ts +12 -1
- package/extensions/plan/tui.ts +5 -34
- package/extensions/plan/types.ts +15 -8
- package/extensions/plan/web-blocks.ts +89 -0
- package/extensions/plan/web-chrome.ts +3 -5
- package/extensions/plan/web-client.ts +179 -330
- package/extensions/plan/web-collab.ts +3 -3
- package/extensions/plan/web-comments.ts +18 -8
- package/extensions/plan/web-composer.ts +80 -0
- package/extensions/plan/web-draft.ts +40 -0
- package/extensions/plan/web-keys.ts +134 -0
- package/extensions/plan/web-outline.ts +192 -0
- package/extensions/plan/web-page.ts +4 -8
- package/extensions/plan/web-plan.ts +150 -0
- package/extensions/plan/web-router.ts +3 -58
- package/extensions/plan/web-settle.ts +194 -0
- package/extensions/plan/web-share-session.ts +24 -30
- package/extensions/plan/web-streams.ts +14 -14
- package/extensions/plan/web-tokens.ts +2 -2
- package/extensions/plan/web.ts +66 -120
- package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
- package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
- 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 +17 -69
- package/extensions/session-bus/format.ts +22 -45
- package/extensions/session-bus/index.ts +13 -7
- package/extensions/session-bus/join-request.ts +40 -0
- package/extensions/session-bus/mail-watch.ts +134 -0
- package/extensions/session-bus/notice.ts +32 -4
- package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
- package/extensions/session-bus/protocol.ts +19 -49
- package/extensions/session-bus/resolve.ts +67 -0
- package/extensions/session-bus/result.ts +16 -51
- package/extensions/session-bus/rows.ts +11 -5
- package/extensions/session-bus/runtime.ts +220 -267
- package/extensions/session-bus/store.ts +55 -7
- package/extensions/session-bus/tools.ts +31 -66
- package/extensions/session-bus/tui.ts +5 -5
- package/extensions/session-bus/types.ts +23 -14
- package/extensions/subagent/agents.ts +9 -8
- package/extensions/subagent/commands.ts +2 -1
- package/extensions/subagent/invocation.ts +4 -2
- package/extensions/team/commands.ts +145 -0
- package/extensions/team/dispatch.ts +133 -0
- package/extensions/team/format.ts +119 -0
- package/extensions/team/index.ts +34 -0
- package/extensions/team/schema.ts +29 -0
- package/extensions/team/tools.ts +267 -0
- package/extensions/team/types.ts +65 -0
- package/extensions/todo/index.ts +4 -1
- package/extensions/todo/nudge.ts +8 -2
- 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 +106 -251
- 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} +37 -37
- package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
- package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
- package/extensions/wechat/index.ts +10 -10
- package/extensions/wechat/intake.ts +245 -0
- 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} +100 -22
- package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
- package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
- 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} +8 -0
- 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} +72 -28
- 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/commands.ts +24 -17
- package/extensions/worktree/exit.ts +12 -6
- package/extensions/worktree/git.ts +22 -14
- package/extensions/worktree/guard.ts +18 -290
- package/extensions/worktree/handoff.ts +10 -2
- package/extensions/worktree/index.ts +2 -1
- package/extensions/worktree/lifecycle.ts +2 -0
- package/extensions/worktree/registry.ts +20 -11
- package/extensions/worktree/root-tools.ts +13 -3
- package/extensions/worktree/runtime.ts +8 -2
- package/extensions/worktree/screen.ts +15 -8
- package/extensions/worktree/shell.ts +303 -0
- package/extensions/worktree/state.ts +1 -1
- package/extensions/worktree/status.ts +42 -9
- package/extensions/worktree/tools.ts +19 -13
- package/lib/README.md +11 -8
- package/lib/board/card.ts +479 -0
- package/lib/board/links.ts +141 -0
- package/lib/board/page-pointer.ts +310 -0
- package/lib/board/round-text.ts +81 -0
- package/lib/board/round.ts +94 -0
- package/lib/board/rules.ts +437 -0
- package/lib/board/session.ts +105 -0
- package/lib/board/store.ts +497 -0
- package/lib/board/view.ts +234 -0
- package/lib/errors.ts +12 -4
- package/lib/format.ts +35 -0
- package/lib/http/guard.ts +112 -0
- package/lib/http/index.ts +16 -5
- package/lib/http/serve.ts +237 -0
- package/lib/http/server.ts +12 -10
- package/lib/http/sink.ts +135 -0
- package/lib/http/token.ts +38 -0
- package/lib/plan-entry.ts +11 -0
- package/lib/policy.ts +25 -2
- package/lib/requirements.ts +6 -7
- package/lib/session-label.ts +64 -0
- package/lib/session-roster.ts +201 -0
- package/lib/session-tool.ts +184 -0
- package/lib/spawn.ts +37 -3
- package/lib/stray-fields.ts +45 -0
- package/lib/tool-names.ts +38 -2
- package/lib/tui/glyphs.ts +3 -2
- package/lib/web/README.md +57 -4
- package/lib/web/client-runtime.ts +123 -0
- package/lib/web/components.ts +5 -0
- package/lib/web/index.ts +4 -2
- package/lib/web/markup.ts +88 -1
- package/lib/web/names.ts +42 -0
- package/lib/web/runtime.ts +7 -1
- package/lib/web/tokens.ts +25 -2
- package/package.json +26 -14
- package/skills/next/SKILL.md +4 -3
- 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/bytefly/panel/tui/extensions-view.ts +0 -40
- package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
- package/extensions/file-browse/README.md +0 -506
- package/extensions/file-browse/files.ts +0 -564
- package/extensions/file-browse/server.ts +0 -141
- package/extensions/job/process.ts +0 -35
- package/extensions/plan/split.ts +0 -87
- package/extensions/session-bus/README.md +0 -247
- package/extensions/session-bus/team.ts +0 -102
- package/extensions/wechat/outbound.ts +0 -100
- package/lib/plans/sections.ts +0 -172
- package/lib/plans/setting.ts +0 -52
- package/lib/plans/split.ts +0 -215
- package/lib/plans/store.ts +0 -19
- package/lib/plans/team.ts +0 -151
- package/skills/changelog/SKILL.md +0 -148
- package/skills/parallel-dev/SKILL.md +0 -150
- /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
- /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/{live-client.ts → view/live-client.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,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP server for the browse extension.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the pure `handleRequest` in `lib/http`'s loopback server: it resolves the
|
|
5
|
+
* root once, feeds the live status channel, and exposes an idempotent `close()`.
|
|
6
|
+
* The request handler is injectable so tests can avoid a socket when they only
|
|
7
|
+
* need routing.
|
|
8
|
+
*
|
|
9
|
+
* What stays here is this extension's own policy: the request handler, its
|
|
10
|
+
* request count, the catch that turns a thrown handler into a plain-text 500, the
|
|
11
|
+
* cap that refuses a page rather than opening a stream for it, and how long a
|
|
12
|
+
* server with nobody reading it is worth keeping.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { realpathSync } from "node:fs";
|
|
16
|
+
import { type ServeResponse, serveLoopback, textResponse } from "../../../lib/http/index.ts";
|
|
17
|
+
import { IDLE_STOP_MS, type ServeConfig } from "../config/config.ts";
|
|
18
|
+
import { createFileIndex } from "../files/files.ts";
|
|
19
|
+
import type { GitDiffProvider, GitStatusProvider } from "../git/git.ts";
|
|
20
|
+
import { createLiveChannel, createLiveStatus } from "../live/live.ts";
|
|
21
|
+
import { handleRequest, type ServeContext } from "./router.ts";
|
|
22
|
+
|
|
23
|
+
export interface FileServer {
|
|
24
|
+
/** Origin URL, e.g. `http://127.0.0.1:49152/`. */
|
|
25
|
+
url: string;
|
|
26
|
+
/** The bound port. */
|
|
27
|
+
port: number;
|
|
28
|
+
/** Resolved absolute root that is actually served. */
|
|
29
|
+
root: string;
|
|
30
|
+
/** Stop listening. Safe to call more than once or before `listen` settles. */
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FileServerOptions {
|
|
35
|
+
root: string;
|
|
36
|
+
config: ServeConfig;
|
|
37
|
+
/** Read-only git context for the page header; omitted when unavailable. */
|
|
38
|
+
git?: GitStatusProvider;
|
|
39
|
+
/** Read-only diff provider for the change and diff views; omitted when unavailable. */
|
|
40
|
+
diff?: GitDiffProvider;
|
|
41
|
+
/** Called once the last page has been gone for `idleStopMs`; omitted, the server waits for `close()`. */
|
|
42
|
+
onIdle?: () => void;
|
|
43
|
+
/** Grace before `onIdle`, in ms; defaults to `IDLE_STOP_MS`. */
|
|
44
|
+
idleStopMs?: number;
|
|
45
|
+
/** Override the request handler (tests). */
|
|
46
|
+
handler?: typeof handleRequest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Start a read-only server rooted at `options.root` on 127.0.0.1. */
|
|
50
|
+
export async function createFileServer(options: FileServerOptions): Promise<FileServer> {
|
|
51
|
+
const root = realpathSync.native(options.root);
|
|
52
|
+
const { config } = options;
|
|
53
|
+
// The status feed's two server-owned facts: how long it has been up, and how
|
|
54
|
+
// many requests it has answered. Both are read per frame, never cached.
|
|
55
|
+
let requests = 0;
|
|
56
|
+
const status = config.live
|
|
57
|
+
? createLiveStatus({
|
|
58
|
+
git: options.git,
|
|
59
|
+
diff: config.gitDiff ? options.diff : undefined,
|
|
60
|
+
startedAt: Date.now(),
|
|
61
|
+
requests: () => requests,
|
|
62
|
+
})
|
|
63
|
+
: undefined;
|
|
64
|
+
const live = status ? createLiveChannel({ status, intervalMs: config.liveIntervalMs }) : undefined;
|
|
65
|
+
const context: ServeContext = {
|
|
66
|
+
root,
|
|
67
|
+
config,
|
|
68
|
+
git: options.git,
|
|
69
|
+
diff: options.diff,
|
|
70
|
+
index: createFileIndex(root, config.maxIndexEntries),
|
|
71
|
+
live: status,
|
|
72
|
+
};
|
|
73
|
+
const handler = options.handler ?? handleRequest;
|
|
74
|
+
|
|
75
|
+
const server = await serveLoopback({
|
|
76
|
+
ports: [config.port],
|
|
77
|
+
idle: options.onIdle === undefined ? undefined : { ms: options.idleStopMs ?? IDLE_STOP_MS, onIdle: options.onIdle },
|
|
78
|
+
handler: async (request) => {
|
|
79
|
+
requests += 1;
|
|
80
|
+
let result: ServeResponse;
|
|
81
|
+
try {
|
|
82
|
+
result = await handler(context, request.method, request.url, request.headers);
|
|
83
|
+
} catch {
|
|
84
|
+
return { result: textResponse(500, "Internal error") };
|
|
85
|
+
}
|
|
86
|
+
if (!result.stream || !live || request.method.toUpperCase() === "HEAD") return { result };
|
|
87
|
+
// Checked before the head is written: a refusal must not leave a stream the
|
|
88
|
+
// client would wait on. `204` is the one status an event source does not
|
|
89
|
+
// reconnect after, so a page beyond the cap goes quiet instead of retrying.
|
|
90
|
+
if (live.atCapacity()) {
|
|
91
|
+
return { result: { status: 204, headers: { "Cache-Control": "no-store" }, body: "" } };
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
result,
|
|
95
|
+
openSink: (sink) => live.attach({ write: (frame) => sink.write(frame), end: () => sink.end() }),
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
url: `${server.origin}/`,
|
|
102
|
+
port: server.port,
|
|
103
|
+
root,
|
|
104
|
+
close: async () => {
|
|
105
|
+
// End the streams first, so a connected page does not hold the close open.
|
|
106
|
+
live?.close();
|
|
107
|
+
await server.close();
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* it reaches the page. The page shell and the page bodies stay in `html.ts`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { escapeHtml } from "
|
|
12
|
-
import { GLYPHS } from "
|
|
13
|
-
import type { GitStatus } from "
|
|
14
|
-
import type { ChangeIndex } from "
|
|
11
|
+
import { escapeHtml } from "../../../lib/escape.ts";
|
|
12
|
+
import { GLYPHS } from "../../../lib/tui/glyphs.ts";
|
|
13
|
+
import type { GitStatus } from "../git/git.ts";
|
|
14
|
+
import type { ChangeIndex } from "../live/live.ts";
|
|
15
15
|
|
|
16
16
|
/** The header's git chip: the branch, or the detached head; changes are the changes chip's job. */
|
|
17
17
|
export function renderGit(git: GitStatus | undefined): string {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* and `bun build --no-bundle` needs no separate asset.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { keysDialogScript, THEME_TOGGLE_JS } from "
|
|
15
|
+
import { keysDialogScript, THEME_TOGGLE_JS } from "../../../lib/web/index.ts";
|
|
16
16
|
import { LIVE_CLIENT_JS } from "./live-client.ts";
|
|
17
17
|
import { PALETTE_CLIENT_JS } from "./palette.ts";
|
|
18
18
|
import { TREE_CLIENT_JS } from "./tree.ts";
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* hunk and never across the unchanged lines a hunk does not show.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { escapeHtml } from "
|
|
12
|
-
import { highlightLines, type HighlightLanguage } from "
|
|
13
|
-
import type { DiffLine } from "
|
|
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
14
|
|
|
15
15
|
/**
|
|
16
16
|
* The inner HTML per diff line. Hunk bodies are highlighted by rebuilding the
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The elements a page emits for a resolved Markdown link or image.
|
|
3
|
+
*
|
|
4
|
+
* Two renderers reach a link: the Markdown renderer (`markdown.ts`) and the raw-HTML
|
|
5
|
+
* sanitizer (`raw-html.ts`). Both ask `urls.ts` what a destination resolves to, and both
|
|
6
|
+
* then build the same anchor and the same image — the external-rel attributes, the
|
|
7
|
+
* `md-img` class, and the lazy decoding — so those live here rather than in a fourth and
|
|
8
|
+
* fifth copy. Values are escaped here; the callers hand in what the document said.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { escapeHtml } from "../../../lib/escape.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The attributes an external link carries, for a caller that is building the element
|
|
15
|
+
* itself: the raw-HTML sanitizer rewrites an existing tag's attribute list and has no
|
|
16
|
+
* content string to hand an emitter.
|
|
17
|
+
*
|
|
18
|
+
* `noopener` because the target is another origin, and `noreferrer` because the page is a
|
|
19
|
+
* local view of the reader's own repository and should not tell anyone where they read it.
|
|
20
|
+
*/
|
|
21
|
+
export const EXTERNAL_ATTRS = ' target="_blank" rel="noopener noreferrer"';
|
|
22
|
+
|
|
23
|
+
/** An `<a>` for one destination; `title` and the rel attributes are omitted when absent. */
|
|
24
|
+
export function emitAnchor(input: { href: string; content: string; title?: string; external: boolean }): string {
|
|
25
|
+
const title = input.title === undefined ? "" : ` title="${escapeHtml(input.title)}"`;
|
|
26
|
+
return `<a href="${escapeHtml(input.href)}"${title}${input.external ? EXTERNAL_ATTRS : ""}>${input.content}</a>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A link standing in for an image the page may not show: an external `src` is blocked by
|
|
31
|
+
* the CSP, so the reader gets something to click, labelled by the alt text.
|
|
32
|
+
*/
|
|
33
|
+
export function emitImageLink(href: string, label: string): string {
|
|
34
|
+
return `<a href="${escapeHtml(href)}"${EXTERNAL_ATTRS}>${escapeHtml(label)}</a>`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** An `<img>`: block-level spacing class, alt text, and the sizes a document may pin. */
|
|
38
|
+
export function emitImage(input: {
|
|
39
|
+
href: string;
|
|
40
|
+
alt: string;
|
|
41
|
+
title?: string;
|
|
42
|
+
/** A document may pin a pixel size; escaped like every other value the page emits. */
|
|
43
|
+
width?: string | number;
|
|
44
|
+
height?: string | number;
|
|
45
|
+
}): string {
|
|
46
|
+
const size = `${input.width === undefined ? "" : ` width="${escapeHtml(String(input.width))}"`}${
|
|
47
|
+
input.height === undefined ? "" : ` height="${escapeHtml(String(input.height))}"`
|
|
48
|
+
}`;
|
|
49
|
+
const title = input.title === undefined ? "" : ` title="${escapeHtml(input.title)}"`;
|
|
50
|
+
return `<img class="md-img" src="${escapeHtml(input.href)}" alt="${escapeHtml(input.alt)}"${size}${title} loading="lazy" decoding="async">`;
|
|
51
|
+
}
|
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
* the page-only `browse-*` rules from `styles.ts`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { brandLockup, docHead, type KeyHint, keysSheet, themeToggle } from "
|
|
12
|
-
import { escapeHtml } from "
|
|
11
|
+
import { brandLockup, docHead, type KeyHint, keysSheet, tabTitle, themeToggle } from "../../../lib/web/index.ts";
|
|
12
|
+
import { escapeHtml } from "../../../lib/escape.ts";
|
|
13
13
|
import { renderChanges, renderGit, renderLiveChrome, renderLiveStatus, type LivePage } from "./chrome.ts";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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";
|
|
16
17
|
import { renderDiffLines } from "./diff-html.ts";
|
|
17
|
-
import type { DiffFileView, GitStatus } from "
|
|
18
|
-
import type { ChangeIndex } from "
|
|
19
|
-
import { detectLanguage, highlightLines, type HighlightLanguage } from "
|
|
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";
|
|
20
21
|
import { FOLDER_ICON, iconHref, languageOf, SPRITE, type IconSpec } from "./icons.ts";
|
|
21
22
|
import { renderMarkdown } from "./markdown.ts";
|
|
22
|
-
import { encodePath } from "
|
|
23
|
+
import { encodePath } from "../files/paths.ts";
|
|
23
24
|
import { BROWSE_CSS } from "./styles.ts";
|
|
24
25
|
|
|
25
26
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
@@ -86,7 +87,6 @@ function renderLayout(layout: Layout): string {
|
|
|
86
87
|
<html lang="en">
|
|
87
88
|
<head>
|
|
88
89
|
${docHead({ title: layout.title, css: BROWSE_CSS })}
|
|
89
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='6' fill='%2358a6ff'/%3E%3C/svg%3E">
|
|
90
90
|
</head>
|
|
91
91
|
<body class="bf-app">
|
|
92
92
|
<a class="skip" href="#main">Skip to content</a>
|
|
@@ -220,7 +220,7 @@ ${page.listing.entries.map((entry) => renderRow(entry, page.maxThumbBytes, page.
|
|
|
220
220
|
body += `<p class="bf-note">Showing the first ${page.listing.entries.length} of ${page.listing.total} entries.</p>`;
|
|
221
221
|
|
|
222
222
|
return renderLayout({
|
|
223
|
-
title:
|
|
223
|
+
title: tabTitle("browse", page.rel || folderName),
|
|
224
224
|
rootLabel: page.rootLabel,
|
|
225
225
|
tree: page.tree,
|
|
226
226
|
currentRel: page.rel,
|
|
@@ -286,8 +286,9 @@ function numberedLines(page: FilePage): boolean {
|
|
|
286
286
|
return page.file.kind === "text" || (page.file.kind === "markdown" && page.markdownRaw === true);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
/** The card-head controls: Markdown mode, line wrapping, and copy actions
|
|
290
|
-
|
|
289
|
+
/** The card-head controls: Markdown mode, line wrapping, and the copy actions,
|
|
290
|
+
* plus the download the diff head keeps in the same group. */
|
|
291
|
+
function renderFileActions(page: FilePage, download: string): string {
|
|
291
292
|
const isMarkdown = page.file.kind === "markdown";
|
|
292
293
|
const rawHref = `/raw/${encodePath(page.rel)}`;
|
|
293
294
|
const viewHref = `/view/${encodePath(page.rel)}`;
|
|
@@ -324,7 +325,7 @@ function renderFileActions(page: FilePage): string {
|
|
|
324
325
|
`<button type="button" class="bf-btn" id="copy-reference" disabled title="Select lines in the file first">copy reference</button>`,
|
|
325
326
|
);
|
|
326
327
|
}
|
|
327
|
-
return `<span class="bf-card__actions">${groups.join("")}</span>`;
|
|
328
|
+
return `<span class="bf-card__actions">${groups.join("")}${download}</span>`;
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
/** Render a file page (text, Markdown, image, binary, or over-size). */
|
|
@@ -336,7 +337,7 @@ export function renderFilePage(page: FilePage): string {
|
|
|
336
337
|
// about the file; the language label, size, and date are the useful metadata.
|
|
337
338
|
const meta = `${spec.label} · ${formatSize(page.file.size)} · ${formatDate(page.file.mtimeMs)}`;
|
|
338
339
|
const download = `<a class="bf-btn" href="/raw/${encodePath(page.rel)}" download>download</a>`;
|
|
339
|
-
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)}
|
|
340
|
+
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>`;
|
|
340
341
|
|
|
341
342
|
const isMarkdown = page.file.kind === "markdown";
|
|
342
343
|
const rendered = isMarkdown && !page.markdownRaw;
|
|
@@ -369,7 +370,7 @@ export function renderFilePage(page: FilePage): string {
|
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
return renderLayout({
|
|
372
|
-
title:
|
|
373
|
+
title: tabTitle("browse", page.rel),
|
|
373
374
|
rootLabel: page.rootLabel,
|
|
374
375
|
tree: page.tree,
|
|
375
376
|
currentRel: page.rel,
|
|
@@ -442,7 +443,7 @@ ${page.files.map((file) => renderChangedRow(file, page.mode)).join("\n")}
|
|
|
442
443
|
</table>`;
|
|
443
444
|
const stat = page.stat ? `<pre class="browse-diff-stat">${escapeHtml(page.stat)}</pre>` : "";
|
|
444
445
|
return renderLayout({
|
|
445
|
-
title: "browse
|
|
446
|
+
title: tabTitle("browse", "changes"),
|
|
446
447
|
rootLabel: page.rootLabel,
|
|
447
448
|
tree: page.tree,
|
|
448
449
|
currentRel: "",
|
|
@@ -514,7 +515,7 @@ export function renderFileDiffPage(page: FileDiffPage): string {
|
|
|
514
515
|
if (page.diff.truncated) note = `<p class="bf-note">Showing the first ${page.diff.lines.length} diff lines.</p>`;
|
|
515
516
|
}
|
|
516
517
|
return renderLayout({
|
|
517
|
-
title:
|
|
518
|
+
title: tabTitle("browse", page.rel),
|
|
518
519
|
rootLabel: page.rootLabel,
|
|
519
520
|
tree: page.tree,
|
|
520
521
|
currentRel: page.rel,
|
|
@@ -542,7 +543,7 @@ export interface ErrorPage {
|
|
|
542
543
|
/** Render a status/error page with the same chrome. */
|
|
543
544
|
export function renderErrorPage(page: ErrorPage): string {
|
|
544
545
|
return renderLayout({
|
|
545
|
-
title:
|
|
546
|
+
title: tabTitle("browse", String(page.status)),
|
|
546
547
|
rootLabel: page.rootLabel,
|
|
547
548
|
tree: page.tree,
|
|
548
549
|
currentRel: "",
|
|
@@ -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
|
|
|
@@ -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,
|
|
@@ -38,9 +38,9 @@ import {
|
|
|
38
38
|
sanitizeHtmlBlock,
|
|
39
39
|
sanitizeInlineTag,
|
|
40
40
|
} from "./raw-html.ts";
|
|
41
|
+
import { emitAnchor, emitImage, emitImageLink } from "./emit.ts";
|
|
41
42
|
import { resolveImage, resolveLink } from "./urls.ts";
|
|
42
43
|
|
|
43
|
-
const HTTP_SCHEME = /^https?:/i;
|
|
44
44
|
/** A GFM extended autolink: `http(s)://` or `www.` followed by a domain. */
|
|
45
45
|
const AUTOLINK = /^(?:https?:\/\/|www\.)[^\s<>]*/i;
|
|
46
46
|
|
|
@@ -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
|
}
|
|
@@ -643,20 +643,17 @@ function renderLink(parsed: ParsedLink, ctx: Context): string {
|
|
|
643
643
|
const label = renderInline(parsed.label, ctx);
|
|
644
644
|
const target = resolveLink(parsed.dest, ctx.baseDir);
|
|
645
645
|
if (!target) return label;
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return
|
|
646
|
+
// The anchor's shape is `emit.ts`'s, the same one the raw-HTML sanitizer emits: a link
|
|
647
|
+
// written in Markdown and the same link written as HTML are the same link.
|
|
648
|
+
return emitAnchor({ href: target.href, content: label, title: parsed.title, external: target.external });
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
function renderImage(parsed: ParsedLink, ctx: Context): string {
|
|
652
652
|
const alt = escapeHtml(parsed.label);
|
|
653
653
|
const target = resolveImage(parsed.dest, ctx.baseDir);
|
|
654
654
|
if (!target) return alt;
|
|
655
|
-
if (!target.image)
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
const title = parsed.title ? ` title="${escapeHtml(parsed.title)}"` : "";
|
|
659
|
-
return `<img class="md-img" src="${escapeHtml(target.href)}" alt="${alt}" loading="lazy" decoding="async"${title}>`;
|
|
655
|
+
if (!target.image) return emitImageLink(target.href, parsed.label || parsed.dest);
|
|
656
|
+
return emitImage({ href: target.href, alt, title: parsed.title });
|
|
660
657
|
}
|
|
661
658
|
|
|
662
659
|
function matchRun(text: string, start: number, char: string): number {
|
|
@@ -812,8 +809,10 @@ function renderInline(text: string, ctx: Context): string {
|
|
|
812
809
|
const match = /^<((?:https?|mailto):[^>\s]+)>/i.exec(text.slice(i));
|
|
813
810
|
if (match) {
|
|
814
811
|
const href = match[1];
|
|
815
|
-
|
|
816
|
-
|
|
812
|
+
// Resolved rather than pattern-matched: `urls.ts` owns which schemes open in a
|
|
813
|
+
// new tab, and a `<mailto:>` link is external but does not.
|
|
814
|
+
const external = resolveLink(href, ctx.baseDir)?.external ?? false;
|
|
815
|
+
out += emitAnchor({ href, content: escapeHtml(href), external });
|
|
817
816
|
i += match[0].length;
|
|
818
817
|
continue;
|
|
819
818
|
}
|
|
@@ -836,8 +835,7 @@ function renderInline(text: string, ctx: Context): string {
|
|
|
836
835
|
// Require a domain, so a bare scheme is not a link.
|
|
837
836
|
if (dots > 0 && /\.[^./]/.test(label.slice(dots))) {
|
|
838
837
|
const href = /^www\./i.test(label) ? `http://${label}` : label;
|
|
839
|
-
|
|
840
|
-
out += `<a href="${escapeHtml(href)}"${external}>${escapeHtml(label)}</a>`;
|
|
838
|
+
out += emitAnchor({ href, content: escapeHtml(label), external: true });
|
|
841
839
|
i += label.length;
|
|
842
840
|
continue;
|
|
843
841
|
}
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
* how `<details>` wraps Markdown.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import { decodeCodePoint, escapeHtml } from "
|
|
28
|
+
import { decodeCodePoint, escapeHtml } from "../../../lib/escape.ts";
|
|
29
|
+
import { emitImage, emitImageLink, EXTERNAL_ATTRS } from "./emit.ts";
|
|
29
30
|
import { resolveImage, resolveLink } from "./urls.ts";
|
|
30
31
|
|
|
31
32
|
/** Elements re-emitted with sanitized attributes. */
|
|
@@ -139,6 +140,12 @@ const BLOCK_OPEN = /^ {0,3}<(\/?)([a-zA-Z][a-zA-Z0-9-]*)/;
|
|
|
139
140
|
const COMMENT_OPEN = /^ {0,3}<!--/;
|
|
140
141
|
const DECLARATION_OPEN = /^ {0,3}<[!?]/;
|
|
141
142
|
/** The five XML references, plus numeric ones, are the only refs decoded in a value. */
|
|
143
|
+
/**
|
|
144
|
+
* The five references an attribute value may carry, decoded.
|
|
145
|
+
*
|
|
146
|
+
* Not the Markdown renderer's table: a tag's attribute is not running text, and decoding
|
|
147
|
+
* ` ` or `—` inside one would invent characters the author did not write.
|
|
148
|
+
*/
|
|
142
149
|
const ATTRIBUTE_ENTITIES: Record<string, string> = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
143
150
|
const ATTRIBUTE_ENTITY = /&(?:#([0-9]{1,7})|#[xX]([0-9a-fA-F]{1,6})|([a-zA-Z]+));/g;
|
|
144
151
|
|
|
@@ -407,7 +414,7 @@ function sanitizeOpenTag(tag: RawTag, baseDir: string): string {
|
|
|
407
414
|
const target = href === undefined ? undefined : resolveLink(href, baseDir);
|
|
408
415
|
if (target !== undefined) {
|
|
409
416
|
out.push(` href="${escapeHtml(target.href)}"`);
|
|
410
|
-
if (target.external) out.push(
|
|
417
|
+
if (target.external) out.push(EXTERNAL_ATTRS);
|
|
411
418
|
}
|
|
412
419
|
break;
|
|
413
420
|
}
|
|
@@ -456,16 +463,14 @@ function sanitizeImage(tag: RawTag, baseDir: string): string {
|
|
|
456
463
|
const src = attribute(tag, "src");
|
|
457
464
|
const target = src === undefined ? undefined : resolveImage(src, baseDir);
|
|
458
465
|
if (target === undefined) return alt === "" ? "" : escapeHtml(alt);
|
|
459
|
-
if (!target.image)
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (title !== undefined) out.push(` title="${escapeHtml(title)}"`);
|
|
470
|
-
return `<img${out.join("")} loading="lazy" decoding="async">`;
|
|
466
|
+
if (!target.image) return emitImageLink(target.href, alt === "" ? target.href : alt);
|
|
467
|
+
// The element is `emit.ts`'s, so an `<img>` written as HTML and the same one written as
|
|
468
|
+
// Markdown are the same element, down to the lazy decoding.
|
|
469
|
+
return emitImage({
|
|
470
|
+
href: target.href,
|
|
471
|
+
alt,
|
|
472
|
+
title: attribute(tag, "title"),
|
|
473
|
+
width: numeric(attribute(tag, "width")),
|
|
474
|
+
height: numeric(attribute(tag, "height")),
|
|
475
|
+
});
|
|
471
476
|
}
|
|
@@ -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}
|
|
@@ -93,8 +92,24 @@ body.palette-open{overflow:hidden}
|
|
|
93
92
|
.browse-measure .bf-md>*{max-width:var(--browse-measure);margin-inline:auto}
|
|
94
93
|
/* Tables start at the prose column and take only the gutter to its right, so a
|
|
95
94
|
wide window widens them without a dead band on their left. Code stays inside
|
|
96
|
-
the measure with the rest of the prose.
|
|
97
|
-
|
|
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
|
+
}
|
|
98
113
|
/* A table wider than the pane scrolls, but an overlay scrollbar is invisible
|
|
99
114
|
until it moves; a gradient on the frame marks the columns still out of view.
|
|
100
115
|
It has to be the frame: the scroller clips its own content, so a pseudo-element
|
|
@@ -116,10 +131,19 @@ body.palette-open{overflow:hidden}
|
|
|
116
131
|
.browse-side .bf-tree{flex:1 1 auto;min-height:0;overflow:auto}
|
|
117
132
|
}
|
|
118
133
|
@media (max-width:820px){
|
|
119
|
-
.browse-root{order:9;flex:1 1 100
|
|
134
|
+
.browse-root{order:9;flex:1 1 100%}
|
|
120
135
|
}
|
|
121
136
|
@media (max-width:520px){
|
|
122
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}
|
|
123
147
|
}
|
|
124
148
|
/* A printed page scrolls nothing: the card and its body open up, the pinned hunk
|
|
125
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;
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type KillTreeFn,
|
|
30
30
|
type SpawnedProcess,
|
|
31
31
|
type SpawnFn,
|
|
32
|
+
startTokenOf,
|
|
32
33
|
type StdinSink,
|
|
33
34
|
} from "../../lib/spawn.ts";
|
|
34
35
|
import { loadConfig, type JobsConfig } from "./config.ts";
|
|
@@ -38,7 +39,6 @@ import { defaultInterval } from "../../lib/timer.ts";
|
|
|
38
39
|
import { clipLabel } from "../../lib/tui/columns.ts";
|
|
39
40
|
import { compareJobs, formatLogs } from "./format.ts";
|
|
40
41
|
import { readLogTail } from "./logs.ts";
|
|
41
|
-
import { defaultStartToken, type LivenessFn, type StartTokenFn } from "./process.ts";
|
|
42
42
|
import {
|
|
43
43
|
loadRegistries,
|
|
44
44
|
mergeRecords,
|
|
@@ -114,9 +114,9 @@ export interface SendResult {
|
|
|
114
114
|
|
|
115
115
|
export interface RuntimeOptions {
|
|
116
116
|
spawn?: SpawnFn;
|
|
117
|
-
liveness?:
|
|
117
|
+
liveness?: typeof isPidAlive;
|
|
118
118
|
killTree?: KillTreeFn;
|
|
119
|
-
startToken?:
|
|
119
|
+
startToken?: typeof startTokenOf;
|
|
120
120
|
now?: () => number;
|
|
121
121
|
/** Platform seam: whether this is Windows. Defaults to the running platform. */
|
|
122
122
|
isWindows?: () => boolean;
|
|
@@ -183,7 +183,7 @@ export function createJobsRuntime(options: RuntimeOptions = {}): JobsRuntime {
|
|
|
183
183
|
const spawn = options.spawn ?? defaultSpawn;
|
|
184
184
|
const liveness = options.liveness ?? isPidAlive;
|
|
185
185
|
const killTree = options.killTree ?? defaultKillTree;
|
|
186
|
-
const startToken = options.startToken ??
|
|
186
|
+
const startToken = options.startToken ?? startTokenOf;
|
|
187
187
|
const now = options.now ?? (() => Date.now());
|
|
188
188
|
const isWindows = options.isWindows ?? (() => process.platform === "win32");
|
|
189
189
|
|