diffninja 0.1.1 → 0.3.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 +92 -228
- package/dist/review/call-flow-html.d.ts +1 -1
- package/dist/review/call-flow-html.js +115 -21
- package/dist/review/connected-analysis.d.ts +29 -2
- package/dist/review/connected-analysis.js +28 -0
- package/dist/review/connected-html.js +372 -32
- package/dist/review/connected.d.ts +17 -3
- package/dist/review/connected.js +23 -2
- package/dist/review/explanation.d.ts +143 -0
- package/dist/review/explanation.js +310 -0
- package/dist/review/html.d.ts +7 -0
- package/dist/review/html.js +98 -4
- package/dist/review/markdown.d.ts +68 -0
- package/dist/review/markdown.js +339 -0
- package/dist/review/mcp.js +102 -13
- package/dist/review/process-html.d.ts +93 -0
- package/dist/review/process-html.js +525 -0
- package/dist/review/report-pages.d.ts +37 -3
- package/dist/review/report-pages.js +69 -3
- package/dist/review/service.js +3 -1
- package/dist/review/types.d.ts +34 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,259 +1,123 @@
|
|
|
1
1
|
# diffninja
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
3
|
+
diffninja helps you review pull requests faster without handing the review to
|
|
4
|
+
a bot. You ask your coding agent (Claude Code, Codex, and others) to review a
|
|
5
|
+
PR. diffninja gives you a review page that shows the changes in the order worth
|
|
6
|
+
reading them, with the agent's notes next to the code. You read, comment, and
|
|
7
|
+
submit the review to GitHub yourself.
|
|
8
|
+
|
|
9
|
+
## Why use it
|
|
10
|
+
|
|
11
|
+
- **Read the important changes first.** Big PRs are hard to follow file by
|
|
12
|
+
file. diffninja numbers each change, most important first, and lets you step
|
|
13
|
+
through them with `j` and `k`.
|
|
14
|
+
- **See what the change does to the product, not just the code.** Your agent
|
|
15
|
+
explains the change in business terms: the processes it touches as
|
|
16
|
+
flowcharts, with new and changed steps highlighted, the business rules it
|
|
17
|
+
adds or changes (before and after), and one plain sentence on what each
|
|
18
|
+
function does. Call flows show that sentence above each function's name.
|
|
19
|
+
- **Your agent does the first pass.** For each change, the agent answers simple
|
|
20
|
+
questions: does it change behavior, is it tested, does it match the PR's
|
|
21
|
+
goal. The answers sit above the code.
|
|
22
|
+
- **Suggested comments, never posted for you.** The agent can suggest short
|
|
23
|
+
line comments. You add them with one click, edit them, or dismiss them.
|
|
24
|
+
Nothing reaches GitHub until you press Submit.
|
|
25
|
+
- **Facts you can check.** diffninja points at the exact lines that changed a
|
|
26
|
+
comparison, a limit, an input check, or error handling. It also shows call
|
|
27
|
+
flows: which functions call the changed code.
|
|
28
|
+
- **Private.** The analysis runs on your machine. diffninja calls no AI model,
|
|
29
|
+
needs no API key, and sends your code nowhere. It uses your existing GitHub
|
|
30
|
+
CLI login to read the PR and post your review.
|
|
20
31
|
|
|
21
32
|
## What you need
|
|
22
33
|
|
|
23
|
-
- **Node.js 22.18 or newer
|
|
24
|
-
- **An
|
|
25
|
-
|
|
26
|
-
- **GitHub CLI (`gh`) 2.45.0
|
|
27
|
-
|
|
28
|
-
`gh` session.
|
|
29
|
-
- No API key. Git ranges may install missing parsing grammars through npm the
|
|
30
|
-
first time a language is seen.
|
|
34
|
+
- **Node.js 22.18 or newer**
|
|
35
|
+
- **An agent CLI:** Claude Code, Codex, OMP, or pi (or any tool that supports
|
|
36
|
+
MCP servers)
|
|
37
|
+
- **GitHub CLI (`gh`) 2.45.0 or newer, logged in:** run `gh auth login` once.
|
|
38
|
+
Only needed for pull requests.
|
|
31
39
|
|
|
32
40
|
## Install
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
pi) with one command:
|
|
42
|
+
Run this once:
|
|
36
43
|
|
|
37
44
|
```bash
|
|
38
|
-
npx -y diffninja setup
|
|
45
|
+
npx -y diffninja@latest setup
|
|
39
46
|
```
|
|
40
47
|
|
|
41
|
-
It installs
|
|
42
|
-
|
|
43
|
-
`--uninstall`, `--dry-run`, `--no-install`); `docs/mcp-setup.md` has the manual
|
|
44
|
-
entries.
|
|
45
|
-
|
|
46
|
-
The package ships `diffninja` (setup only) and `diffninja-mcp` (the MCP server).
|
|
47
|
-
`npx` fetches the latest published version on first run; if a cached copy feels
|
|
48
|
-
stale, pin it explicitly (`npx -y diffninja@latest setup`).
|
|
49
|
-
|
|
50
|
-
On npm 12 and later, which block dependency install scripts by default, setup
|
|
51
|
-
names the ones diffninja needs (`--allow-scripts=diffninja,tree-sitter,...`), so
|
|
52
|
-
nothing extra is required. If you install by hand, pass the same flag:
|
|
53
|
-
`npm install -g --allow-scripts=diffninja,tree-sitter,tree-sitter-javascript,tree-sitter-typescript diffninja`.
|
|
54
|
-
|
|
55
|
-
## Review a pull request
|
|
56
|
-
|
|
57
|
-
Ask your agent to review `https://github.com/OWNER/REPO/pull/123`. It calls
|
|
58
|
-
`review_diff` with the link, reads the pull request, sends its reading with
|
|
59
|
-
`finish_review`, and then gives you a loopback review page, loaded from the
|
|
60
|
-
canonical GitHub patch through your `gh` authentication. The link arrives
|
|
61
|
-
once the agent has read the change, so the page opens complete: the agent's
|
|
62
|
-
reading order, its answers, and its suggested comments. Select diff lines,
|
|
63
|
-
write single-line inline comments and a review body, pick Comment, Approve, or
|
|
64
|
-
Request changes, preview the exact payload, and submit. Comments your agent
|
|
65
|
-
suggests wait under their lines until you add them; every word you submit is one
|
|
66
|
-
you chose, and diffninja only carries it to GitHub. It never approves, blocks, or merges
|
|
67
|
-
anything on its own. The page belongs to the agent's MCP connection and closes
|
|
68
|
-
when the agent exits.
|
|
69
|
-
|
|
70
|
-
The diff opens in the **reading order**: each hunk is a numbered change,
|
|
71
|
-
most important first, with your agent's answers (does this change behavior,
|
|
72
|
-
does a test exercise it, does it serve the stated goal) and the facts to look
|
|
73
|
-
at above its code. Hunks outside the order follow under **Other changes**.
|
|
74
|
-
Press `j` and `k` to step through the changes. A rail beside the diff lists
|
|
75
|
-
them, marks the one you are reading, and fills in as you read. **By file**
|
|
76
|
-
switches to one block per file, with each change's number where it starts,
|
|
77
|
-
and keeps you on the change you were reading. When the agent works inside your local
|
|
78
|
-
clone it passes it as `repo`; once the clone has the pull request's commits,
|
|
79
|
-
the analysis adds definitions and call flows, and the page opens a file's call
|
|
80
|
-
flow in a drawer beside the diff. diffninja never fetches or writes in the
|
|
81
|
-
clone; when it lacks the commits, the page says so and names the `git fetch`
|
|
82
|
-
that would add them.
|
|
83
|
-
|
|
84
|
-
## Review a diff or a git range
|
|
48
|
+
It installs diffninja and adds it to every agent CLI it finds on your machine.
|
|
49
|
+
Then restart your agent CLI.
|
|
85
50
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
expected outcome when supplied (`expectedOutcome`), a short reading agenda,
|
|
90
|
-
bounded automatic findings, explicit check coverage, and every hunk, ranked as
|
|
91
|
-
**attention**, **uncertain**, **low**, or **passed**; git ranges add call flows
|
|
92
|
-
and snapshot-bound source. Claims in a description are not proof that the code
|
|
93
|
-
fulfills them. Once the agent has sent its reading (below), it gets `reportUrl`:
|
|
94
|
-
a read-only page on `127.0.0.1` with the same report for you to read (the
|
|
95
|
-
agenda, call-flow graphs, and every hunk), served from memory for as long as
|
|
96
|
-
the agent's session lasts.
|
|
97
|
-
The tool writes no report files. Arguments and examples:
|
|
98
|
-
[docs/mcp-setup.md](docs/mcp-setup.md). Trying it on real reviews:
|
|
99
|
-
[docs/pilot.md](docs/pilot.md).
|
|
51
|
+
To update later, run the same command again. To remove diffninja from your
|
|
52
|
+
agent CLIs, run `npx -y diffninja setup --uninstall`. Other options:
|
|
53
|
+
`npx -y diffninja setup --help`.
|
|
100
54
|
|
|
101
|
-
## How
|
|
55
|
+
## How to use it
|
|
102
56
|
|
|
103
|
-
|
|
104
|
-
Repository snapshots support bounded checks for duplicate function bodies and
|
|
105
|
-
unread `errors` response fields, plus caller and contract source cards.
|
|
106
|
-
Optional TypeScript reference checking compares before/after diagnostics,
|
|
107
|
-
including unchanged consumers, using an explicitly trusted installed compiler.
|
|
108
|
-
Unsupported or incomplete checks say so.
|
|
109
|
-
2. **Change facts per hunk.** The added and removed lines are read lexically —
|
|
110
|
-
strings and comments set aside, moved lines cancelling out. Code gets six facts:
|
|
111
|
-
comparison changed, limit changed (a numeric bound, or `<` turned into `<=`),
|
|
112
|
-
input check changed (type/shape checks, or a changed guard in front of a
|
|
113
|
-
raise), failure handed to the caller, failure deferred or retried, failure
|
|
114
|
-
discarded (an empty or defaulting `catch`, `except: pass`, …). Documentation
|
|
115
|
-
(`.md`, `.rst`, `.txt`, …) is asked whether an instruction to readers changed
|
|
116
|
-
(must, never, only, at most, …), a link target changed, or a numeric limit
|
|
117
|
-
changed. Configuration (`.yml`, `.json`, `.toml`, Dockerfiles, `.env`, …) is
|
|
118
|
-
asked whether a CI gate was weakened (`continue-on-error`, `|| true`, a
|
|
119
|
-
failure turned into a warning, a check step removed), permissions or secret
|
|
120
|
-
access changed, a version pin changed, or a limit changed. Each `yes` cites
|
|
121
|
-
its changed line. `no` speaks only about the lines the hunk shows. A change
|
|
122
|
-
that only touches formatting, comments, or line breaks is recognized as such.
|
|
123
|
-
JS/TS, Java, C#, Go, Rust, C/C++, Kotlin, Swift, PHP, Python, Ruby,
|
|
124
|
-
documentation and configuration are read; any other file type says that no
|
|
125
|
-
facts were established instead of claiming none.
|
|
126
|
-
3. **Status and order.** A code or configuration change outside a test file
|
|
127
|
-
reads **attention**; documentation reads **attention** when it changes an
|
|
128
|
-
instruction, a link, or a limit, **low** otherwise; a test-file change reads
|
|
129
|
-
**attention** only when it changes a limit, discards a failure, or weakens a
|
|
130
|
-
gate, **low** otherwise; a formatting-only change **passed**;
|
|
131
|
-
an unread file type **uncertain**, for a person to read. Priority orders hunks:
|
|
132
|
-
a fixed base, plus 10 for a real change, plus the heaviest fact of the
|
|
133
|
-
boundary group (what the change says or bounds) and of the failure group
|
|
134
|
-
(failures, CI gates, permissions) — each group counts once, never summed. The report lists
|
|
135
|
-
manual work first (binary and other metadata-only units), then the read hunks
|
|
136
|
-
by priority, then passes; among equal priorities the hunk that changes more
|
|
137
|
-
lines comes first. Hunks in test files (by path convention: `test/`,
|
|
138
|
-
`*.test.ts`, `test_*.py`, `*_test.go`, …, including snapshots diffninja does
|
|
139
|
-
not read) come after the other hunks, still ordered by their own priority: a
|
|
140
|
-
regression test changes as much as its fix.
|
|
141
|
-
Documentation is not demoted, because prose can be normative. Status is a
|
|
142
|
-
label for filtering and never reorders the report.
|
|
143
|
-
4. **Questions for your agent.** Where a judgment needs meaning rather than
|
|
144
|
-
syntax, the report asks the agent that requested it — does this hunk change
|
|
145
|
-
what callers observe, does a test exercise it, does a test change weaken it,
|
|
146
|
-
do the docs match the code, does the hunk serve the stated goal. Questions
|
|
147
|
-
are fixed templates with closed options (always including `cannot-tell`).
|
|
148
|
-
diffninja itself still calls no model.
|
|
149
|
-
The agent sends its whole reading in one `finish_review` call: an answer to
|
|
150
|
-
every question, the reading order of every hunk (most important first),
|
|
151
|
-
and the line comments it would leave (or none). diffninja checks all of it
|
|
152
|
-
and only then hands out the page link, so every page you open already
|
|
153
|
-
carries the agent's answers, its order, and its comment decision; an agent
|
|
154
|
-
cannot give you a half-read page. The pages list every hunk in the agent's
|
|
155
|
-
order, attributed to it; diffninja's own order stays available one click
|
|
156
|
-
away, and statuses stay diffninja's. `record_answers`, `record_order`, and
|
|
157
|
-
`suggest_comments` update a review afterwards. On 159 held-out open-source
|
|
158
|
-
pull requests, weighted by the severity of maintainers' actual review
|
|
159
|
-
comments, a host model that read diffninja's report put the serious
|
|
160
|
-
comments earlier than diffninja's deterministic order did.
|
|
161
|
-
On a pull request, the suggested line comments are short, in the reviewer's
|
|
162
|
-
own voice, with no "Finding 1:" scaffolding. The page shows each under its line; you add one
|
|
163
|
-
or all of them to your draft with a click, edit or dismiss them, and submit
|
|
164
|
-
the review yourself. Nothing is posted without you.
|
|
165
|
-
5. **Project context (git ranges only).** What a diff does not show is often
|
|
166
|
-
the project around it. From the local clone alone — nothing is fetched —
|
|
167
|
-
the report names the commits that last changed each hunk's removed lines
|
|
168
|
-
(`git blame` at the base), earlier revert commits that touched a changed
|
|
169
|
-
file or share a rare word with the goal or the changed file names,
|
|
170
|
-
contributor guidelines that apply (`CONTRIBUTING`, `AGENTS.md`, `.github/`,
|
|
171
|
-
docs policy pages such as versioning or preview rules), and, for a new
|
|
172
|
-
file, identifiers most of its same-named siblings use and it does not
|
|
173
|
-
(`components/*/select.py`). They add questions for your agent: does a hunk
|
|
174
|
-
undo a fix it removes, does the change reintroduce something reverted, does
|
|
175
|
-
it follow the guidelines and the sibling pattern. These are pointers, not
|
|
176
|
-
verdicts, and never change status or order. A shallow clone says so: lines
|
|
177
|
-
whose origin lies past its boundary count as unknown.
|
|
57
|
+
Inside your agent CLI, ask in plain words:
|
|
178
58
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
explicitly unestablished. Findings likewise state their scope: duplicated syntax
|
|
183
|
-
is not necessarily duplicated responsibility, and an unread field alone does not
|
|
184
|
-
prove that a real failure was mishandled.
|
|
59
|
+
```text
|
|
60
|
+
Review https://github.com/OWNER/REPO/pull/123 with diffninja
|
|
61
|
+
```
|
|
185
62
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
63
|
+
The agent reads the PR, then gives you a link to the review page (it runs on
|
|
64
|
+
your machine at `127.0.0.1`). On the page:
|
|
65
|
+
|
|
66
|
+
1. Start with **Goal**: the reviewing agent's short, plain-English explanation
|
|
67
|
+
of what the PR is meant to do and its important limits. The full
|
|
68
|
+
**Original PR description** stays one click away, with Markdown formatting.
|
|
69
|
+
The goal is stated intent, not proof the code fulfills it.
|
|
70
|
+
Under it, **How it works** draws the business processes the change touches,
|
|
71
|
+
with its new and changed steps highlighted, and each change in the diff
|
|
72
|
+
says which step or rule it belongs to.
|
|
73
|
+
`j` and `k` move to the next and previous change, and the list on the left
|
|
74
|
+
shows where you are. The agent sets the reading order; the connected page
|
|
75
|
+
does not label changes “Attention”.
|
|
76
|
+
2. Hover a line and press **+** to write a comment, or add the agent's
|
|
77
|
+
suggestions.
|
|
78
|
+
3. Write a summary, choose **Comment**, **Approve**, or **Request changes**.
|
|
79
|
+
4. Press **Check the review** to see exactly what will be sent, then
|
|
80
|
+
**Submit review**.
|
|
81
|
+
|
|
82
|
+
Tip: if your agent is running inside a local clone of the repository, it can
|
|
83
|
+
pass the clone to diffninja. You then also get call-flow diagrams for the
|
|
84
|
+
changed files.
|
|
85
|
+
|
|
86
|
+
You can also review changes that aren't a PR yet:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
Review my changes on this branch against main with diffninja
|
|
90
|
+
```
|
|
198
91
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
match `@Processor` consumers' `job.name` cases or `@Process` methods only on a
|
|
202
|
-
matching queue channel. String enum values can connect member keys to literal
|
|
203
|
-
cases. These are source-derived relations, not runtime calls or proof of
|
|
204
|
-
delivery; dynamic keys, aliases, and unrecognized framework syntax can be absent.
|
|
205
|
-
Constant resolution is snapshot-local, including when extraction is cached.
|
|
92
|
+
The agent gets a report of every changed piece of code, ranked by importance,
|
|
93
|
+
and gives you a link to read the same report in your browser.
|
|
206
94
|
|
|
207
|
-
|
|
208
|
-
non-callable context nodes. Signature, body, and generic type references connect
|
|
209
|
-
them to reviewed methods and other contracts. Relative import bindings take
|
|
210
|
-
precedence; unique module-path suffixes and unimported names are candidate
|
|
211
|
-
matches, not type checking. Unresolved imports do not borrow same-named types.
|
|
212
|
-
Barrel re-exports, nested declarations, and class-field contracts may be absent.
|
|
95
|
+
## Good to know
|
|
213
96
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
already shown in the hunk take precedence over duplicate source. Unseen type
|
|
219
|
-
declarations and dispatch endpoints precede ordinary caller chains, nearest
|
|
220
|
-
first, with resulting-snapshot evidence ahead of prior-snapshot duplicates.
|
|
221
|
-
Unresolved own-call expressions remain verbatim in a whole source body rather
|
|
222
|
-
than repeating unknown-binding boilerplate. At most eight distinct definition
|
|
223
|
-
nodes are kept per hunk, each carried whole, never shortened.
|
|
97
|
+
- The review page closes when you exit your agent CLI.
|
|
98
|
+
- The page and the agent's session contain source code. Treat them like the
|
|
99
|
+
code itself.
|
|
100
|
+
- diffninja never approves, blocks, or merges anything on its own.
|
|
224
101
|
|
|
225
|
-
|
|
226
|
-
it requires a supported lexical or typed-constructor binding. Relative modules
|
|
227
|
-
and simple single-target `paths` aliases from snapshot-local, standalone JSON
|
|
228
|
-
`tsconfig.json` files are supported. JSONC, inherited configurations, package or
|
|
229
|
-
barrel resolution, and complex receivers remain unproven rather than borrowing
|
|
230
|
-
an unrelated same-named function. `referenceProject: "path/to/tsconfig.json"`
|
|
231
|
-
opts into the separate TypeScript diagnostic comparison; absent dependencies,
|
|
232
|
-
unsupported project layouts, and exceeded bounds are reported as not checked.
|
|
102
|
+
## More
|
|
233
103
|
|
|
234
|
-
|
|
104
|
+
- [How the analysis works](docs/how-it-works.md): what diffninja checks and
|
|
105
|
+
how it ranks changes
|
|
106
|
+
- [Manual setup and tool reference](docs/mcp-setup.md): for agent CLIs that
|
|
107
|
+
`setup` doesn't configure
|
|
108
|
+
- [Releasing](docs/npm-release.md): how new versions are published
|
|
235
109
|
|
|
236
|
-
|
|
237
|
-
agent's session. Keep transcripts that contain them private.
|
|
238
|
-
- Connected PR reviews need authenticated `gh`. Nothing else leaves your
|
|
239
|
-
machine: no model is called and no API key is needed.
|
|
240
|
-
|
|
241
|
-
## Dev
|
|
110
|
+
## Development
|
|
242
111
|
|
|
243
112
|
```bash
|
|
244
|
-
npm
|
|
245
|
-
npm run
|
|
246
|
-
npm
|
|
113
|
+
npm install
|
|
114
|
+
npm run build # compile to dist/
|
|
115
|
+
npm run lint
|
|
116
|
+
npm test
|
|
247
117
|
```
|
|
248
118
|
|
|
249
|
-
Run the built server directly with `node dist/review/mcp-cli.js` (it speaks MCP
|
|
250
|
-
over stdio and prints nothing else to stdout).
|
|
251
|
-
|
|
252
|
-
Releases and the npm publishing setup: [docs/npm-release.md](docs/npm-release.md).
|
|
253
|
-
|
|
254
119
|
## Credits
|
|
255
120
|
|
|
256
121
|
The call-flow engine is a fork of
|
|
257
122
|
[calldiff](https://github.com/tanishqkancharla/calldiff) by Tanishq Kancharla
|
|
258
|
-
(MIT, see LICENSE).
|
|
259
|
-
hunk touches.
|
|
123
|
+
(MIT, see LICENSE).
|
|
@@ -22,7 +22,7 @@ import type { ReviewReport } from "./types.js";
|
|
|
22
22
|
* removed, or contains a change, and plain dimmed text for an unchanged call.
|
|
23
23
|
*/
|
|
24
24
|
export declare function renderCallFlows(report: ReviewReport): string;
|
|
25
|
-
export declare const CALL_FLOW_STYLES = "\n.cf {\n display: flex; flex-direction: column; gap: 12px; margin-top: 18px;\n --cf-add: #1a7f37; --cf-add-soft: #dafbe1;\n --cf-del: #cf222e; --cf-del-soft: #ffebe9;\n --cf-chg: #9a6700; --cf-chg-soft: #fff8c5;\n --cf-quiet: var(--ink-soft);\n}\n@media (prefers-color-scheme: dark) {\n .cf {\n --cf-add: #3fb950; --cf-add-soft: rgba(46, 160, 67, 0.15);\n --cf-del: #f85149; --cf-del-soft: rgba(248, 81, 73, 0.12);\n --cf-chg: #d29922; --cf-chg-soft: rgba(187, 128, 9, 0.15);\n }\n}\n/* Mode sections, folded files and the breadcrumb are toggled with the hidden\n attribute, so the module carries that rule itself instead of relying on the page. */\n.cf [hidden] { display: none !important; }\n.cf-head { display: flex; flex-direction: column; gap: 4px; }\n.cf-prov { font-size: 12px; color: var(--ink-soft); }\n.cf-note { font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-absence { padding: 16px 0; }\n.cf-bounds {\n margin: 10px 0 0;\n padding: 2px 0 2px 10px;\n border-left: 2px solid var(--cf-chg);\n color: var(--ink-soft);\n font-size: 12px;\n overflow-wrap: anywhere;\n}\n.cf-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }\n.cf-ready .cf-controls { position: sticky; top: var(--toolbar-height, 0px); z-index: 4; background: var(--bg); padding: 8px 0; }\n.cf, .cf-files, .cf-file-body, .cf-mode { min-width: 0; }\n.cf-modes {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--line);\n border-radius: 6px;\n overflow: hidden;\n background: var(--panel);\n}\n.cf-mode-link {\n display: inline-flex;\n align-items: center;\n padding: 5px 14px;\n font-size: 12px;\n color: var(--ink-soft);\n text-decoration: none;\n border-right: 1px solid var(--line);\n}\n.cf-mode-link:last-child { border-right: 0; }\n.cf-mode-link:hover, .cf-mode-link:focus-visible { color: var(--ink); background: var(--sunken); }\n.js .cf-mode-link[aria-current] { color: var(--ink); background: var(--sunken); font-weight: 600; }\n.cf-jump ul {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n max-height: 22vh;\n overflow-y: auto;\n}\n.cf-jump-link {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n min-width: 0;\n padding: 4px 10px 4px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n font-size: 12px;\n text-decoration: none;\n}\n.cf-jump-link:hover, .cf-jump-link:focus-visible { border-color: var(--cursor); }\n.cf-jump-path { min-width: 0; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.cf-jump-count { color: var(--ink-soft); white-space: nowrap; }\n.cf-crumbs {\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 6px;\n font-size: 12.5px;\n padding-bottom: 3px;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n.cf-crumb {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 34ch;\n font: inherit;\n font-size: 12px;\n padding: 3px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 999px;\n background: var(--panel);\n color: var(--ink);\n cursor: pointer;\n white-space: nowrap;\n}\n.cf-crumb[aria-current] { border-color: var(--cursor); font-weight: 600; }\n.cf-crumb-label { overflow: hidden; text-overflow: ellipsis; }\n.cf-crumb-file {\n flex: 0 0 auto;\n max-width: 22ch;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n color: var(--ink-soft);\n}\n.cf-sep { flex: 0 0 auto; color: var(--ink-soft); }\n.cf-files { display: flex; flex-direction: column; gap: 12px; }\n.cf-file {\n background: var(--panel);\n border: 1px solid var(--line);\n border-left: 3px solid var(--line-strong);\n border-radius: 6px;\n overflow: hidden;\n}\n.cf-file { --cf-color: var(--ink-soft); --cf-fill: var(--sunken); }\n.cf-file-attention { border-left-color: var(--alarm); --cf-color: var(--alarm); --cf-fill: var(--alarm-bg); }\n.cf-file-uncertain { border-left-color: var(--warn); --cf-color: var(--warn); --cf-fill: var(--warn-bg); }\n.cf-file-low { border-left-color: var(--line-strong); }\n.cf-file-passed { border-left-color: var(--teal); --cf-color: var(--teal); --cf-fill: var(--teal-bg); }\n.cf-file-head {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n background: var(--sunken);\n cursor: pointer;\n list-style: none;\n}\n.cf-file-head::-webkit-details-marker { display: none; }\n.cf-file-head::after {\n content: \"\u25B8\";\n margin-left: auto;\n font-size: 11px;\n color: var(--ink-soft);\n transition: transform 0.15s ease;\n}\n.cf-file[open] > .cf-file-head::after { transform: rotate(90deg); }\n.cf-file-head:focus-visible { outline: 2px solid var(--cursor); outline-offset: -2px; }\n.cf-file-path { flex: 1 1 260px; min-width: 0; font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }\n.cf-file-count { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }\n.cf-diff-link { font-size: 12px; white-space: nowrap; }\n.cf-file-body { border-top: 1px solid var(--line); padding: 0 12px 12px; }\n.cf-mode { padding-top: 12px; }\n.cf-noscript { display: block; }\n.cf-noscript .cf-note { margin: 6px 0 0; }\n.cf-mode + .cf-mode { margin-top: 12px; border-top: 1px dashed var(--line); }\n.cf-mode-head {\n margin: 0 0 8px;\n font-size: 11.5px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--ink-soft);\n}\n.cf-ready .cf-mode-head { display: none; }\n.cf-ready .cf-mode + .cf-mode { margin-top: 0; border-top: 0; }\n.cf-tree, .cf-children { list-style: none; margin: 0; padding: 0; }\n.cf-children { margin-left: 15px; padding-left: 10px; border-left: 1px dashed var(--line); }\n.cf-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px; padding: 2px 6px; border-radius: 6px; min-width: 0; }\n.cf-row:hover { background: var(--sunken); }\n.cf-fold > .cf-row { cursor: pointer; list-style: none; }\n.cf-fold > .cf-row::-webkit-details-marker { display: none; }\n.cf-fold > .cf-row::before { content: \"\u25B8\"; font-size: 10px; color: var(--ink-soft); transition: transform 0.15s ease; }\n.cf-fold[open] > .cf-row::before { transform: rotate(90deg); }\n.cf-badge, .cf-chip-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 16px;\n height: 16px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-family: var(--mono);\n font-size: 11px;\n font-weight: 700;\n}\n.cf-badge, .cf-chip-badge { color: var(--cf-node-color); background: var(--cf-node-fill); }\n.cf-label {\n font: 400 12.5px/1.5 var(--mono);\n padding: 0 2px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: none;\n text-align: left;\n /* Capped so a long call never pushes its row onto a second line; the\n location takes what is left and truncates first. */\n flex: 0 1 auto;\n min-width: 0;\n max-width: 62%;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.js .cf-label { cursor: pointer; }\n.js .cf-label:hover, .cf-label:focus-visible { text-decoration: underline; }\n.cf-label:focus-visible { outline: 2px solid var(--cursor); outline-offset: 1px; }\n.cf-loc { flex: 1 1 0; min-width: 0; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-loc-none { font-style: italic; }\n.cf-node, .cf-gnode, .cf-chip { --cf-node-color: var(--cf-chg); --cf-node-fill: var(--cf-chg-soft); --cf-node-weight: 600; --cf-node-decoration: none; }\n.cf-st-added { --cf-node-color: var(--cf-add); --cf-node-fill: var(--cf-add-soft); }\n.cf-st-removed { --cf-node-color: var(--cf-del); --cf-node-fill: var(--cf-del-soft); --cf-node-decoration: line-through; }\n.cf-node.cf-st-same, .cf-gnode.cf-st-same, .cf-chip.cf-st-same { --cf-node-color: var(--ink-soft); --cf-node-fill: var(--sunken); --cf-node-weight: 400; }\n.cf-label { color: var(--cf-node-color); font-weight: var(--cf-node-weight); text-decoration: var(--cf-node-decoration); }\n.cf-node.cf-ancestor > .cf-fold > .cf-row { display: none; }\n.cf-node.cf-ancestor > .cf-fold > .cf-children { margin: 0; padding: 0; border: 0; }\n.cf-svg-wrap {\n max-width: 100%;\n overflow: auto;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n overscroll-behavior: contain;\n}\n.cf-svg { display: block; max-width: none; }\n.cf-ready .cf-svg-wrap { height: clamp(220px, 42vh, 400px); overflow: hidden; touch-action: none; cursor: grab; }\n.cf-diagram-btn, .cf-diagram-close {\n display: inline-flex; align-items: center; gap: 6px; height: 29px; padding: 0 12px;\n border: 1px solid var(--line); border-radius: 6px; background: var(--panel);\n font: 500 12px/1 var(--sans, inherit); color: var(--ink); cursor: pointer;\n}\n.cf-diagram-btn { color: var(--cursor); border-color: var(--cursor); }\n.cf-diagram-btn::after { content: \"\\2197\"; font-size: 12px; }\n.cf-diagram-btn:hover, .cf-diagram-close:hover { background: var(--sunken); }\n.cf-diagram-close { margin-left: auto; }\n.cf-diagram-hint { margin: 0 0 0 auto; font-size: 12px; color: var(--ink-soft); }\n.cf-diagram-hint + .cf-diagram-close { margin-left: 12px; }\n.cf.cf-diagram-open .cf-head { display: none; }\n/* The diagram view fills the browser window, and the graph gets nearly all of its height. */\n.cf.cf-diagram-open {\n position: fixed; inset: 0; z-index: 50; margin: 0; padding: 12px 20px 20px;\n background: var(--bg); overflow: auto; overscroll-behavior: contain;\n}\n.cf.cf-diagram-open .cf-controls { position: sticky; top: -12px; z-index: 5; background: var(--bg); padding: 10px 0; }\n.cf.cf-diagram-open .cf-modes, .cf.cf-diagram-open .cf-diagram-btn { display: none !important; }\n.cf.cf-diagram-open { padding-left: 292px; }\n.cf.cf-diagram-open .cf-svg-wrap { height: calc(100vh - 150px); min-height: 320px; }\n/* One canvas at a time: the entry point picked in the list, in its own file. */\n.cf.cf-diagram-open .cf-jump, .cf.cf-diagram-open .cf-bounds, .cf.cf-diagram-open .cf-omitted { display: none !important; }\n.cf.cf-diagram-open .cf-file:not(.cf-file-current) { display: none; }\n.cf.cf-diagram-open .cf-graph-frame:not(.cf-frame-current) { display: none; }\n.cf.cf-diagram-open .cf-file-body { padding-bottom: 0; }\n.cf-diagram-nav {\n position: fixed; top: 0; bottom: 0; left: 0; width: 276px; overflow-y: auto; z-index: 6;\n padding: 16px 12px; background: var(--sunken); border-right: 1px solid var(--line);\n}\n.cf-diagram-nav-head { margin: 0 0 10px; font-size: 13px; font-weight: 600; color: var(--ink); }\n.cf-diagram-nav-file {\n margin: 14px 0 4px; font: 600 12px var(--mono); color: var(--ink-soft);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.cf-diagram-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }\n.cf-diagram-nav button {\n display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%;\n padding: 6px 8px; border: 0; border-radius: 6px; background: none; color: var(--ink); text-align: left; cursor: pointer;\n}\n.cf-diagram-nav button:hover { background: var(--panel); }\n.cf-diagram-nav button[aria-current] { background: var(--panel); box-shadow: inset 3px 0 0 var(--cursor); }\n.cf-diagram-nav-name { max-width: 100%; font: 600 12px/1.35 var(--mono); overflow-wrap: anywhere; }\n.cf-diagram-nav-count { max-width: 100%; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n@media (max-width: 760px) {\n .cf.cf-diagram-open { padding-left: 12px; padding-top: 12px; }\n .cf-diagram-nav { position: static; width: auto; max-height: 30vh; margin: 0 0 8px; border: 1px solid var(--line); border-radius: 6px; }\n}\n.cf-ready .cf-svg-wrap:focus-visible { outline: 2px solid var(--cursor); outline-offset: 2px; }\n.cf-ready .cf-svg-wrap.cf-dragging { cursor: grabbing; user-select: none; }\n.cf-ready .cf-svg { width: 100%; height: 100%; }\n.cf-graph-frame + .cf-graph-frame { margin-top: 14px; }\n.cf-camera-tools { align-items: center; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }\n.cf-camera-tools button { min-height: 28px; padding: 0 10px; font-size: 12px; }\n.cf-scale { min-width: 4ch; text-align: center; font: 11px var(--mono); color: var(--ink-soft); }\n.cf-omitted { display: none; }\n.cf-ready .cf-omitted { display: block; }\n.cf-node.cf-selected > .cf-row, .cf-node.cf-selected > .cf-fold > .cf-row,\n.cf-chip.cf-selected { outline: 2px solid var(--cursor); outline-offset: 1px; border-radius: 5px; }\n.cf-edge { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode rect { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode:focus-visible { outline: none; }\n.cf-gzoom circle { fill: var(--panel); stroke: var(--line-strong); }\n.cf-gzoom text { fill: var(--ink); font: 14px var(--mono); }\n.cf-gzoom:hover circle, .cf-gzoom:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-gzoom:focus-visible { outline: none; }\n.cf-glabel { font-family: var(--mono); font-size: 12px; fill: var(--ink); }\n.cf-gloc { font-family: var(--mono); font-size: 10px; fill: var(--ink-soft); }\n.cf-gnode.cf-st-same rect { stroke: var(--line); fill: transparent; }\n.cf-gnode.cf-st-same .cf-glabel { fill: var(--ink-soft); }\n.cf-gnode:not(.cf-st-same) rect { stroke: var(--cf-node-color); fill: var(--panel); }\n.cf-gnode:not(.cf-st-same) .cf-glabel { fill: var(--cf-node-color); font-weight: 600; }\n.cf-gnode:hover rect, .cf-gnode:focus-visible rect { stroke: var(--cursor); stroke-width: 2.5; }\n.cf-gnode.cf-selected rect { stroke: var(--cursor); stroke-width: 3; }\n.cf-paths { display: flex; flex-direction: column; gap: 6px; list-style: none; margin: 0; padding: 0; }\n.cf-path {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px 6px;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n padding: 2px 8px 2px 4px;\n border: 1px solid var(--line);\n border-left: 3px solid var(--cf-node-color);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n}\n.cf-chip:hover, .cf-chip:focus-within { border-color: var(--cursor); }\n.cf-chip-zoom {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n}\n.cf-chip-label { min-width: 0; max-width: 36ch; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip-loc { min-width: 0; max-width: 28ch; font-size: 10.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip.cf-st-same .cf-chip-label { color: var(--ink-soft); }\n.cf-chip:not(.cf-st-same) .cf-chip-label { color: var(--cf-node-color); font-weight: 600; }\n.cf-chip .cf-desc { max-width: 28ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-arrow { color: var(--ink-soft); font-size: 12px; }\n/* One description line per call, only when the backend attached one. */\n.cf-desc { font-size: 11.5px; color: var(--ink-soft); font-style: italic; overflow-wrap: anywhere; }\n.cf-row > .cf-desc { flex: 1 0 100%; order: 5; padding-left: 26px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-row > .cf-src-link { order: 4; }\n.cf-depth { align-items: center; gap: 4px; }\n.cf-depth-label { font-size: 12px; color: var(--ink-soft); }\n.cf-depth-btn { padding: 3px 9px; font-size: 12px; }\n.cf-depth-btn[aria-pressed=\"true\"] { border-color: var(--cursor); color: var(--cursor); font-weight: 600; }\n/* Numbered, clickable call order on the graph edges. */\n.cf-edge-num circle { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-edge-num text { font-family: var(--mono); font-size: 10.5px; fill: var(--ink-soft); }\n.cf-edge-num:hover circle, .cf-edge-num:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-edge-num:hover text, .cf-edge-num:focus-visible text { fill: var(--cursor); font-weight: 700; }\n.cf-edge-num:focus-visible { outline: none; }\n.cf-gdesc { font-family: var(--mono); font-size: 10px; font-style: italic; fill: var(--ink-soft); }\n/* Resolved definition of one call: a native disclosure without the script, and\n the body the script clones into its single shared panel. */\n.cf-source { margin: 3px 0 3px 24px; }\n.cf-ready .cf-source { display: none; }\n.cf-src-sum { width: fit-content; cursor: pointer; list-style: none; font-size: 11.5px; color: var(--ink-soft); }\n.cf-src-sum::-webkit-details-marker { display: none; }\n.cf-src-sum::before { content: \"\u25B8\"; margin-right: 6px; font-size: 10px; transition: transform 0.15s ease; display: inline-block; }\n.cf-source[open] > .cf-src-sum::before { transform: rotate(90deg); }\n.cf-src-sum:hover, .cf-src-sum:focus-visible { color: var(--cursor); }\n.cf-src-body { padding: 6px 0 4px; }\n.cf-src-ref { margin: 0 0 5px; font-size: 11.5px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-code {\n margin: 0;\n padding: 6px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n font-family: var(--mono);\n font-size: 11.5px;\n line-height: 1.5;\n overflow-x: auto;\n}\n.cf-src-line { display: block; white-space: pre; }\n.cf-src-no {\n display: inline-block;\n width: 4ch;\n margin-right: 10px;\n text-align: right;\n color: var(--ink-soft);\n user-select: none;\n}\n.cf-src-note { margin: 0; font-size: 11.5px; color: var(--ink-soft); font-style: italic; }\n.cf-src-panel {\n padding: 8px 12px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-src-panel-sum { width: fit-content; cursor: pointer; font-size: 12.5px; font-weight: 600; }\n.cf-src-panel-body { padding-top: 6px; }\n.cf-src-panel-body { max-height: 40vh; overflow: auto; }\n.cf-src-head-line { margin: 0 0 6px; font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-missing { margin: 0; }\n.cf-src-link {\n flex: 0 0 auto;\n font-size: 11px;\n color: var(--ink-soft);\n text-decoration: none;\n white-space: nowrap;\n padding: 0 6px;\n border: 1px solid var(--line);\n border-radius: 999px;\n}\n.cf-src-link:hover { text-decoration: none; }\n.cf-src-link:hover, .cf-src-link:focus-visible { color: var(--cursor); }\n@media (max-width: 680px) {\n .cf-jump ul { max-height: 38vh; }\n .cf-jump-link { width: 100%; }\n .cf-jump-path { max-width: none; flex: 1 1 auto; }\n .cf-file-body { padding: 0 11px 12px; }\n .cf-crumb { max-width: 24ch; }\n .cf-crumb-file { max-width: 12ch; }\n .cf-source { margin-left: 10px; }\n .cf-src-no { width: 3ch; margin-right: 7px; }\n .cf-chip, .cf-chip-zoom { min-width: 0; flex-wrap: wrap; }\n .cf-chip-loc { overflow-wrap: anywhere; }\n}\n";
|
|
25
|
+
export declare const CALL_FLOW_STYLES = "\n.cf {\n display: flex; flex-direction: column; gap: 12px; margin-top: 18px;\n --cf-add: #1a7f37; --cf-add-soft: #dafbe1;\n --cf-del: #cf222e; --cf-del-soft: #ffebe9;\n --cf-chg: #9a6700; --cf-chg-soft: #fff8c5;\n --cf-quiet: var(--ink-soft);\n}\n@media (prefers-color-scheme: dark) {\n .cf {\n --cf-add: #3fb950; --cf-add-soft: rgba(46, 160, 67, 0.15);\n --cf-del: #f85149; --cf-del-soft: rgba(248, 81, 73, 0.12);\n --cf-chg: #d29922; --cf-chg-soft: rgba(187, 128, 9, 0.15);\n }\n}\n/* Mode sections, folded files and the breadcrumb are toggled with the hidden\n attribute, so the module carries that rule itself instead of relying on the page. */\n.cf [hidden] { display: none !important; }\n.cf-head { display: flex; flex-direction: column; gap: 4px; }\n.cf-prov { font-size: 12px; color: var(--ink-soft); }\n.cf-note { font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-absence { padding: 16px 0; }\n.cf-bounds {\n margin: 10px 0 0;\n padding: 2px 0 2px 10px;\n border-left: 2px solid var(--cf-chg);\n color: var(--ink-soft);\n font-size: 12px;\n overflow-wrap: anywhere;\n}\n.cf-controls { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }\n.cf-ready .cf-controls { position: sticky; top: var(--toolbar-height, 0px); z-index: 4; background: var(--bg); padding: 8px 0; }\n.cf, .cf-files, .cf-file-body, .cf-mode { min-width: 0; }\n.cf-modes {\n display: inline-flex;\n align-items: stretch;\n border: 1px solid var(--line);\n border-radius: 6px;\n overflow: hidden;\n background: var(--panel);\n}\n.cf-mode-link {\n display: inline-flex;\n align-items: center;\n padding: 5px 14px;\n font-size: 12px;\n color: var(--ink-soft);\n text-decoration: none;\n border-right: 1px solid var(--line);\n}\n.cf-mode-link:last-child { border-right: 0; }\n.cf-mode-link:hover, .cf-mode-link:focus-visible { color: var(--ink); background: var(--sunken); }\n.js .cf-mode-link[aria-current] { color: var(--ink); background: var(--sunken); font-weight: 600; }\n.cf-jump ul {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n max-height: 22vh;\n overflow-y: auto;\n}\n.cf-jump-link {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n max-width: 100%;\n min-width: 0;\n padding: 4px 10px 4px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n font-size: 12px;\n text-decoration: none;\n}\n.cf-jump-link:hover, .cf-jump-link:focus-visible { border-color: var(--cursor); }\n.cf-jump-path { min-width: 0; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.cf-jump-count { color: var(--ink-soft); white-space: nowrap; }\n.cf-crumbs {\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 6px;\n font-size: 12.5px;\n padding-bottom: 3px;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n.cf-crumb {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex: 0 0 auto;\n max-width: 34ch;\n font: inherit;\n font-size: 12px;\n padding: 3px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 999px;\n background: var(--panel);\n color: var(--ink);\n cursor: pointer;\n white-space: nowrap;\n}\n.cf-crumb[aria-current] { border-color: var(--cursor); font-weight: 600; }\n.cf-crumb-label { overflow: hidden; text-overflow: ellipsis; }\n.cf-crumb-file {\n flex: 0 0 auto;\n max-width: 22ch;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n color: var(--ink-soft);\n}\n.cf-sep { flex: 0 0 auto; color: var(--ink-soft); }\n.cf-files { display: flex; flex-direction: column; gap: 12px; }\n.cf-file {\n background: var(--panel);\n border: 1px solid var(--line);\n border-left: 3px solid var(--line-strong);\n border-radius: 6px;\n overflow: hidden;\n}\n.cf-file { --cf-color: var(--ink-soft); --cf-fill: var(--sunken); }\n.cf-file-attention { border-left-color: var(--alarm); --cf-color: var(--alarm); --cf-fill: var(--alarm-bg); }\n.cf-file-uncertain { border-left-color: var(--warn); --cf-color: var(--warn); --cf-fill: var(--warn-bg); }\n.cf-file-low { border-left-color: var(--line-strong); }\n.cf-file-passed { border-left-color: var(--teal); --cf-color: var(--teal); --cf-fill: var(--teal-bg); }\n.cf-file-head {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n background: var(--sunken);\n cursor: pointer;\n list-style: none;\n}\n.cf-file-head::-webkit-details-marker { display: none; }\n.cf-file-head::after {\n content: \"\u25B8\";\n margin-left: auto;\n font-size: 11px;\n color: var(--ink-soft);\n transition: transform 0.15s ease;\n}\n.cf-file[open] > .cf-file-head::after { transform: rotate(90deg); }\n.cf-file-head:focus-visible { outline: 2px solid var(--cursor); outline-offset: -2px; }\n.cf-file-path { flex: 1 1 260px; min-width: 0; font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }\n.cf-file-count { font-size: 12px; color: var(--ink-soft); white-space: nowrap; }\n.cf-diff-link { font-size: 12px; white-space: nowrap; }\n.cf-file-body { border-top: 1px solid var(--line); padding: 0 12px 12px; }\n.cf-mode { padding-top: 12px; }\n.cf-noscript { display: block; }\n.cf-noscript .cf-note { margin: 6px 0 0; }\n.cf-mode + .cf-mode { margin-top: 12px; border-top: 1px dashed var(--line); }\n.cf-mode-head {\n margin: 0 0 8px;\n font-size: 11.5px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--ink-soft);\n}\n.cf-ready .cf-mode-head { display: none; }\n.cf-ready .cf-mode + .cf-mode { margin-top: 0; border-top: 0; }\n.cf-tree, .cf-children { list-style: none; margin: 0; padding: 0; }\n.cf-children { margin-left: 15px; padding-left: 10px; border-left: 1px dashed var(--line); }\n.cf-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0 8px; padding: 2px 6px; border-radius: 6px; min-width: 0; }\n.cf-row:hover { background: var(--sunken); }\n.cf-fold > .cf-row { cursor: pointer; list-style: none; }\n.cf-fold > .cf-row::-webkit-details-marker { display: none; }\n.cf-fold > .cf-row::before { content: \"\u25B8\"; font-size: 10px; color: var(--ink-soft); transition: transform 0.15s ease; }\n.cf-fold[open] > .cf-row::before { transform: rotate(90deg); }\n.cf-badge, .cf-chip-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n min-width: 16px;\n height: 16px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-family: var(--mono);\n font-size: 11px;\n font-weight: 700;\n}\n.cf-badge, .cf-chip-badge { color: var(--cf-node-color); background: var(--cf-node-fill); }\n.cf-label {\n font: 400 12.5px/1.5 var(--mono);\n padding: 0 2px;\n border: 1px solid transparent;\n border-radius: 4px;\n background: none;\n text-align: left;\n /* Capped so a long call never pushes its row onto a second line; the\n location takes what is left and truncates first. */\n flex: 0 1 auto;\n min-width: 0;\n max-width: 62%;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.js .cf-label { cursor: pointer; }\n.js .cf-label:hover, .cf-label:focus-visible { text-decoration: underline; }\n.cf-label:focus-visible { outline: 2px solid var(--cursor); outline-offset: 1px; }\n.cf-loc { flex: 1 1 0; min-width: 0; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-loc-none { font-style: italic; }\n.cf-node, .cf-gnode, .cf-chip { --cf-node-color: var(--cf-chg); --cf-node-fill: var(--cf-chg-soft); --cf-node-weight: 600; --cf-node-decoration: none; }\n.cf-st-added { --cf-node-color: var(--cf-add); --cf-node-fill: var(--cf-add-soft); }\n.cf-st-removed { --cf-node-color: var(--cf-del); --cf-node-fill: var(--cf-del-soft); --cf-node-decoration: line-through; }\n.cf-node.cf-st-same, .cf-gnode.cf-st-same, .cf-chip.cf-st-same { --cf-node-color: var(--ink-soft); --cf-node-fill: var(--sunken); --cf-node-weight: 400; }\n.cf-label { color: var(--cf-node-color); font-weight: var(--cf-node-weight); text-decoration: var(--cf-node-decoration); }\n.cf-node.cf-ancestor > .cf-fold > .cf-row { display: none; }\n.cf-node.cf-ancestor > .cf-fold > .cf-children { margin: 0; padding: 0; border: 0; }\n.cf-svg-wrap {\n max-width: 100%;\n overflow: auto;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n overscroll-behavior: contain;\n}\n.cf-svg { display: block; max-width: none; }\n.cf-ready .cf-svg-wrap { height: clamp(220px, 42vh, 400px); overflow: hidden; touch-action: none; cursor: grab; }\n.cf-diagram-btn, .cf-diagram-close {\n display: inline-flex; align-items: center; gap: 6px; height: 29px; padding: 0 12px;\n border: 1px solid var(--line); border-radius: 6px; background: var(--panel);\n font: 500 12px/1 var(--sans, inherit); color: var(--ink); cursor: pointer;\n}\n.cf-diagram-btn { color: var(--cursor); border-color: var(--cursor); }\n.cf-diagram-btn::after { content: \"\\2197\"; font-size: 12px; }\n.cf-diagram-btn:hover, .cf-diagram-close:hover { background: var(--sunken); }\n.cf-diagram-close { margin-left: auto; }\n.cf-diagram-hint { margin: 0 0 0 auto; font-size: 12px; color: var(--ink-soft); }\n.cf-diagram-hint + .cf-diagram-close { margin-left: 12px; }\n.cf.cf-diagram-open .cf-head { display: none; }\n/* The diagram view fills the browser window, and the graph gets nearly all of its height. */\n.cf.cf-diagram-open {\n position: fixed; inset: 0; z-index: 50; margin: 0; padding: 12px 20px 20px;\n background: var(--bg); overflow: auto; overscroll-behavior: contain;\n}\n.cf.cf-diagram-open .cf-controls { position: sticky; top: -12px; z-index: 5; background: var(--bg); padding: 10px 0; }\n.cf.cf-diagram-open .cf-modes, .cf.cf-diagram-open .cf-diagram-btn { display: none !important; }\n.cf.cf-diagram-open { padding-left: 292px; }\n.cf.cf-diagram-open .cf-svg-wrap { height: calc(100vh - 150px); min-height: 320px; }\n/* One canvas at a time: the entry point picked in the list, in its own file. */\n.cf.cf-diagram-open .cf-jump, .cf.cf-diagram-open .cf-bounds, .cf.cf-diagram-open .cf-omitted { display: none !important; }\n.cf.cf-diagram-open .cf-file:not(.cf-file-current) { display: none; }\n.cf.cf-diagram-open .cf-graph-frame:not(.cf-frame-current) { display: none; }\n.cf.cf-diagram-open .cf-file-body { padding-bottom: 0; }\n.cf-diagram-nav {\n position: fixed; top: 0; bottom: 0; left: 0; width: 276px; overflow-y: auto; z-index: 6;\n padding: 16px 12px; background: var(--sunken); border-right: 1px solid var(--line);\n}\n.cf-diagram-nav-head { margin: 0 0 10px; font-size: 13px; font-weight: 600; color: var(--ink); }\n.cf-diagram-nav-file {\n margin: 14px 0 4px; font: 600 12px var(--mono); color: var(--ink-soft);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.cf-diagram-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }\n.cf-diagram-nav button {\n display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%;\n padding: 6px 8px; border: 0; border-radius: 6px; background: none; color: var(--ink); text-align: left; cursor: pointer;\n}\n.cf-diagram-nav button:hover { background: var(--panel); }\n.cf-diagram-nav button[aria-current] { background: var(--panel); box-shadow: inset 3px 0 0 var(--cursor); }\n.cf-diagram-nav-name { max-width: 100%; font: 600 12px/1.35 var(--mono); overflow-wrap: anywhere; }\n.cf-diagram-nav-count { max-width: 100%; font-size: 11px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n@media (max-width: 760px) {\n .cf.cf-diagram-open { padding-left: 12px; padding-top: 12px; }\n .cf-diagram-nav { position: static; width: auto; max-height: 30vh; margin: 0 0 8px; border: 1px solid var(--line); border-radius: 6px; }\n}\n.cf-ready .cf-svg-wrap:focus-visible { outline: 2px solid var(--cursor); outline-offset: 2px; }\n.cf-ready .cf-svg-wrap.cf-dragging { cursor: grabbing; user-select: none; }\n.cf-ready .cf-svg { width: 100%; height: 100%; }\n.cf-graph-frame + .cf-graph-frame { margin-top: 14px; }\n.cf-camera-tools { align-items: center; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }\n.cf-camera-tools button { min-height: 28px; padding: 0 10px; font-size: 12px; }\n.cf-scale { min-width: 4ch; text-align: center; font: 11px var(--mono); color: var(--ink-soft); }\n.cf-omitted { display: none; }\n.cf-ready .cf-omitted { display: block; }\n.cf-node.cf-selected > .cf-row, .cf-node.cf-selected > .cf-fold > .cf-row,\n.cf-chip.cf-selected { outline: 2px solid var(--cursor); outline-offset: 1px; border-radius: 5px; }\n.cf-edge { fill: none; stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode rect { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-gnode:focus-visible { outline: none; }\n.cf-gzoom circle { fill: var(--panel); stroke: var(--line-strong); }\n.cf-gzoom text { fill: var(--ink); font: 14px var(--mono); }\n.cf-gzoom:hover circle, .cf-gzoom:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-gzoom:focus-visible { outline: none; }\n.cf-glabel { font-family: var(--mono); font-size: 12px; fill: var(--ink); }\n.cf-gloc { font-family: var(--mono); font-size: 10px; fill: var(--ink-soft); }\n.cf-gnode.cf-st-same rect { stroke: var(--line); fill: transparent; }\n.cf-gnode.cf-st-same .cf-glabel { fill: var(--ink-soft); }\n.cf-gnode:not(.cf-st-same) rect { stroke: var(--cf-node-color); fill: var(--panel); }\n.cf-gnode:not(.cf-st-same) .cf-glabel { fill: var(--cf-node-color); font-weight: 600; }\n.cf-gnode:hover rect, .cf-gnode:focus-visible rect { stroke: var(--cursor); stroke-width: 2.5; }\n.cf-gnode.cf-selected rect { stroke: var(--cursor); stroke-width: 3; }\n.cf-paths { display: flex; flex-direction: column; gap: 6px; list-style: none; margin: 0; padding: 0; }\n.cf-path {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 4px 6px;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n padding: 2px 8px 2px 4px;\n border: 1px solid var(--line);\n border-left: 3px solid var(--cf-node-color);\n border-radius: 6px;\n background: var(--panel);\n color: var(--ink);\n}\n.cf-chip:hover, .cf-chip:focus-within { border-color: var(--cursor); }\n.cf-chip-zoom {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 100%;\n min-width: 0;\n color: inherit;\n text-decoration: none;\n}\n.cf-chip-label { min-width: 0; max-width: 36ch; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip-loc { min-width: 0; max-width: 28ch; font-size: 10.5px; color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-chip.cf-st-same .cf-chip-label { color: var(--ink-soft); }\n.cf-chip:not(.cf-st-same) .cf-chip-label { color: var(--cf-node-color); font-weight: 600; }\n.cf-chip .cf-desc { max-width: 28ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-arrow { color: var(--ink-soft); font-size: 12px; }\n/* One description line per call, only when the backend attached one. */\n.cf-desc { font-size: 11.5px; color: var(--ink-soft); font-style: italic; overflow-wrap: anywhere; }\n.cf-business { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 10px; }\n.cf-business-note { font-size: 13px; color: var(--ink-soft); }\n.cf-plumbing-toggle { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-soft); cursor: pointer; width: fit-content; }\n.cf:has(.cf-show-plumbing:not(:checked)) .cf-node.cf-plumbing { display: none; }\n.cf-purpose { font-family: var(--sans); font-size: 13.5px; font-weight: 550; color: var(--ink); overflow-wrap: anywhere; }\n.cf-row > .cf-purpose { flex: 1 0 100%; order: -1; padding-left: 26px; }\n.cf-fold > .cf-row > .cf-purpose { padding-left: 16px; }\n.cf-node.cf-explained > .cf-row .cf-label, .cf-node.cf-explained > .cf-fold > .cf-row .cf-label { font-size: 11.5px; }\n.cf-chip .cf-purpose { font-size: 12.5px; max-width: 34ch; }\n.cf-gpurpose { font-family: var(--sans); font-size: 12px; font-weight: 600; fill: var(--ink); }\n.cf-diagram-nav-name.cf-diagram-nav-purpose { font-family: var(--sans); white-space: normal; }\n.cf-row > .cf-desc { flex: 1 0 100%; order: 5; padding-left: 26px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.cf-row > .cf-src-link { order: 4; }\n.cf-depth { align-items: center; gap: 4px; }\n.cf-depth-label { font-size: 12px; color: var(--ink-soft); }\n.cf-depth-btn { padding: 3px 9px; font-size: 12px; }\n.cf-depth-btn[aria-pressed=\"true\"] { border-color: var(--cursor); color: var(--cursor); font-weight: 600; }\n/* Numbered, clickable call order on the graph edges. */\n.cf-edge-num circle { fill: var(--panel); stroke: var(--line-strong); stroke-width: 1.5; }\n.cf-edge-num text { font-family: var(--mono); font-size: 10.5px; fill: var(--ink-soft); }\n.cf-edge-num:hover circle, .cf-edge-num:focus-visible circle { stroke: var(--cursor); stroke-width: 2; }\n.cf-edge-num:hover text, .cf-edge-num:focus-visible text { fill: var(--cursor); font-weight: 700; }\n.cf-edge-num:focus-visible { outline: none; }\n.cf-gdesc { font-family: var(--mono); font-size: 10px; font-style: italic; fill: var(--ink-soft); }\n/* Resolved definition of one call: a native disclosure without the script, and\n the body the script clones into its single shared panel. */\n.cf-source { margin: 3px 0 3px 24px; }\n.cf-ready .cf-source { display: none; }\n.cf-src-sum { width: fit-content; cursor: pointer; list-style: none; font-size: 11.5px; color: var(--ink-soft); }\n.cf-src-sum::-webkit-details-marker { display: none; }\n.cf-src-sum::before { content: \"\u25B8\"; margin-right: 6px; font-size: 10px; transition: transform 0.15s ease; display: inline-block; }\n.cf-source[open] > .cf-src-sum::before { transform: rotate(90deg); }\n.cf-src-sum:hover, .cf-src-sum:focus-visible { color: var(--cursor); }\n.cf-src-body { padding: 6px 0 4px; }\n.cf-src-ref { margin: 0 0 5px; font-size: 11.5px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-code {\n margin: 0;\n padding: 6px 9px;\n border: 1px solid var(--line);\n border-radius: 6px;\n background: var(--sunken);\n font-family: var(--mono);\n font-size: 11.5px;\n line-height: 1.5;\n overflow-x: auto;\n}\n.cf-src-line { display: block; white-space: pre; }\n.cf-src-no {\n display: inline-block;\n width: 4ch;\n margin-right: 10px;\n text-align: right;\n color: var(--ink-soft);\n user-select: none;\n}\n.cf-src-note { margin: 0; font-size: 11.5px; color: var(--ink-soft); font-style: italic; }\n.cf-src-panel {\n padding: 8px 12px 10px;\n border: 1px solid var(--line-strong);\n border-radius: 6px;\n background: var(--panel);\n}\n.cf-src-panel-sum { width: fit-content; cursor: pointer; font-size: 12.5px; font-weight: 600; }\n.cf-src-panel-body { padding-top: 6px; }\n.cf-src-panel-body { max-height: 40vh; overflow: auto; }\n.cf-src-head-line { margin: 0 0 6px; font-size: 12px; color: var(--ink-soft); overflow-wrap: anywhere; }\n.cf-src-missing { margin: 0; }\n.cf-src-link {\n flex: 0 0 auto;\n font-size: 11px;\n color: var(--ink-soft);\n text-decoration: none;\n white-space: nowrap;\n padding: 0 6px;\n border: 1px solid var(--line);\n border-radius: 999px;\n}\n.cf-src-link:hover { text-decoration: none; }\n.cf-src-link:hover, .cf-src-link:focus-visible { color: var(--cursor); }\n@media (max-width: 680px) {\n .cf-jump ul { max-height: 38vh; }\n .cf-jump-link { width: 100%; }\n .cf-jump-path { max-width: none; flex: 1 1 auto; }\n .cf-file-body { padding: 0 11px 12px; }\n .cf-crumb { max-width: 24ch; }\n .cf-crumb-file { max-width: 12ch; }\n .cf-source { margin-left: 10px; }\n .cf-src-no { width: 3ch; margin-right: 7px; }\n .cf-chip, .cf-chip-zoom { min-width: 0; flex-wrap: wrap; }\n .cf-chip-loc { overflow-wrap: anywhere; }\n}\n";
|
|
26
26
|
/**
|
|
27
27
|
* Progressive enhancement for the rendered call flow. Nothing here is
|
|
28
28
|
* interpolated: the visited trail, mode, depth and the source text all come from
|