@capagents/forgeqe 0.0.0-stage → 1.0.1
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/HOW_TO_USE.md +1639 -0
- package/README.md +338 -2
- package/bin/forgeqe.d.ts +3 -0
- package/bin/forgeqe.js +188 -0
- package/docs/ARCHITECTURE.md +408 -0
- package/docs/security-data-flow.html +1870 -0
- package/docs/user-guide.html +2094 -0
- package/evals/domain/access-role-change.yaml +37 -0
- package/evals/domain/banking-payment-transfer.yaml +41 -0
- package/evals/domain/gdpr-account-erasure.yaml +30 -0
- package/evals/domain/insurance-claim-fnol.yaml +30 -0
- package/evals/domain/insurance-policy-renewal.yaml +37 -0
- package/evals/domain/lending-loan-application.yaml +36 -0
- package/forgeqe-architecture.html +718 -0
- package/forgeqe.example.yaml +203 -0
- package/knowledge/README.md +18 -0
- package/knowledge/business-rules.md +10 -0
- package/knowledge/compliance.md +16 -0
- package/knowledge/environments.md +5 -0
- package/knowledge/glossary.md +5 -0
- package/knowledge/product.md +16 -0
- package/knowledge/roles.md +9 -0
- package/knowledge/test-data.md +7 -0
- package/llms.example.json +51 -0
- package/package.json +63 -4
- package/prompts/README.md +34 -0
- package/prompts/api-scenarios.md +21 -0
- package/prompts/api-tests.md +39 -0
- package/prompts/brain.md +166 -0
- package/prompts/browser-capture.md +7 -0
- package/prompts/browser-case-report.md +7 -0
- package/prompts/browser-checks.md +14 -0
- package/prompts/browser-efficiency.md +7 -0
- package/prompts/browser-error-recovery.md +15 -0
- package/prompts/browser-extra.md +5 -0
- package/prompts/browser-goal-verification.md +13 -0
- package/prompts/browser-skill-select.md +6 -0
- package/prompts/domain-rule.md +13 -0
- package/prompts/eval-judge.md +19 -0
- package/prompts/explore-charters.md +27 -0
- package/prompts/find-tests.md +28 -0
- package/prompts/heal.md +40 -0
- package/prompts/pr-review.md +29 -0
- package/prompts/skill-rule.md +6 -0
- package/prompts/test-agent.md +34 -0
- package/prompts/test-reviewer.md +30 -0
- package/prompts/test-rules.md +28 -0
- package/py/forgeqe_engine/__init__.py +3 -0
- package/py/forgeqe_engine/__main__.py +3 -0
- package/py/forgeqe_engine/brand.py +453 -0
- package/py/forgeqe_engine/brand_assets.py +6 -0
- package/py/forgeqe_engine/browser.py +196 -0
- package/py/forgeqe_engine/capture.py +538 -0
- package/py/forgeqe_engine/checks.py +98 -0
- package/py/forgeqe_engine/cli.py +188 -0
- package/py/forgeqe_engine/diagnostics.py +609 -0
- package/py/forgeqe_engine/llm.py +195 -0
- package/py/forgeqe_engine/logins.py +251 -0
- package/py/forgeqe_engine/loops.py +224 -0
- package/py/forgeqe_engine/memory.py +1004 -0
- package/py/forgeqe_engine/network.py +244 -0
- package/py/forgeqe_engine/prompts.py +20 -0
- package/py/forgeqe_engine/replay.py +240 -0
- package/py/forgeqe_engine/report.py +173 -0
- package/py/forgeqe_engine/service.py +1335 -0
- package/py/forgeqe_engine/settings.py +93 -0
- package/py/forgeqe_engine/skills.py +88 -0
- package/py/forgeqe_engine/summary.py +239 -0
- package/py/forgeqe_engine/urls.py +81 -0
- package/py/forgeqe_engine/workflow_yaml.py +689 -0
- package/skills/dotnet-nunit-playwright/SKILL.md +34 -0
- package/skills/dynamics-365/SKILL.md +21 -0
- package/skills/playwright-typescript/SKILL.md +35 -0
- package/skills/writing-skills/SKILL.md +30 -0
- package/src/api/plan.ts +246 -0
- package/src/api/run.ts +204 -0
- package/src/api/scenario.ts +502 -0
- package/src/api/schema.ts +170 -0
- package/src/api/spec.ts +624 -0
- package/src/api/store.ts +232 -0
- package/src/api/traffic.ts +281 -0
- package/src/api/types.ts +163 -0
- package/src/api/write.ts +272 -0
- package/src/auth/signin.ts +494 -0
- package/src/auth/store.ts +170 -0
- package/src/brain/index.ts +189 -0
- package/src/brain/prompt.ts +19 -0
- package/src/capabilities/docs.ts +101 -0
- package/src/capabilities/guide.ts +455 -0
- package/src/capabilities/registry.ts +343 -0
- package/src/capabilities/render.ts +106 -0
- package/src/capabilities/status.ts +137 -0
- package/src/card.ts +11 -0
- package/src/cases/ado.ts +325 -0
- package/src/cases/api.ts +117 -0
- package/src/cases/bugs.ts +223 -0
- package/src/cases/confluence.ts +111 -0
- package/src/cases/context.ts +223 -0
- package/src/cases/dashboards.ts +137 -0
- package/src/cases/failure.ts +97 -0
- package/src/cases/files.ts +295 -0
- package/src/cases/goal.ts +53 -0
- package/src/cases/http.ts +111 -0
- package/src/cases/index.ts +131 -0
- package/src/cases/jira.ts +122 -0
- package/src/cases/qtest.ts +108 -0
- package/src/cases/quality.ts +190 -0
- package/src/cases/rag.ts +100 -0
- package/src/cases/readiness.ts +284 -0
- package/src/cases/save.ts +149 -0
- package/src/cases/sprints.ts +148 -0
- package/src/cases/testrail.ts +176 -0
- package/src/cases/types.ts +107 -0
- package/src/cases/wiki.ts +105 -0
- package/src/cases/xray.ts +206 -0
- package/src/cases/zephyr.ts +153 -0
- package/src/childEnv.ts +86 -0
- package/src/cli/args.ts +114 -0
- package/src/cli/help.ts +167 -0
- package/src/cli/index.ts +1142 -0
- package/src/cli/terminal.ts +187 -0
- package/src/code/areas.ts +337 -0
- package/src/code/attention.ts +214 -0
- package/src/code/find.ts +540 -0
- package/src/code/handoff.ts +541 -0
- package/src/code/history.ts +170 -0
- package/src/code/impact.ts +231 -0
- package/src/code/index.ts +34 -0
- package/src/code/livebrowser.ts +67 -0
- package/src/code/loop.ts +603 -0
- package/src/code/notes.ts +75 -0
- package/src/code/profile.ts +226 -0
- package/src/code/prompt.ts +303 -0
- package/src/code/prreview.ts +649 -0
- package/src/code/prtests.ts +103 -0
- package/src/code/replay.ts +772 -0
- package/src/code/review.ts +202 -0
- package/src/code/runtime.ts +781 -0
- package/src/code/slices.ts +159 -0
- package/src/code/testrun.ts +187 -0
- package/src/code/traceability.ts +100 -0
- package/src/code/worktree.ts +153 -0
- package/src/commands.ts +101 -0
- package/src/config.ts +970 -0
- package/src/detail.ts +65 -0
- package/src/domain.ts +19 -0
- package/src/engine/bridge.ts +359 -0
- package/src/engine/mock.ts +102 -0
- package/src/engine/setup.ts +298 -0
- package/src/entra.ts +154 -0
- package/src/env.ts +24 -0
- package/src/evals/domain.ts +338 -0
- package/src/explore/charters.ts +151 -0
- package/src/explore/run.ts +280 -0
- package/src/explore/story.ts +106 -0
- package/src/guard.ts +51 -0
- package/src/heal/loop.ts +357 -0
- package/src/heal/prompt.ts +143 -0
- package/src/heal/report.ts +95 -0
- package/src/index.ts +12 -0
- package/src/init.ts +102 -0
- package/src/knowledge.ts +114 -0
- package/src/legacyEnv.ts +9 -0
- package/src/llm.ts +61 -0
- package/src/locale.ts +25 -0
- package/src/logins.ts +148 -0
- package/src/managed.ts +101 -0
- package/src/mcp/apitools.ts +335 -0
- package/src/mcp/captools.ts +25 -0
- package/src/mcp/casetools.ts +223 -0
- package/src/mcp/commands.ts +421 -0
- package/src/mcp/exploretools.ts +129 -0
- package/src/mcp/external.ts +172 -0
- package/src/mcp/git.ts +78 -0
- package/src/mcp/notifytools.ts +49 -0
- package/src/mcp/prtools.ts +261 -0
- package/src/mcp/qualitytools.ts +154 -0
- package/src/mcp/repotests.ts +286 -0
- package/src/mcp/server.ts +234 -0
- package/src/mcp/tools.ts +810 -0
- package/src/memory.ts +250 -0
- package/src/money.ts +39 -0
- package/src/monitor/alerts.ts +94 -0
- package/src/monitor/background.ts +68 -0
- package/src/monitor/run.ts +263 -0
- package/src/monitor/state.ts +105 -0
- package/src/notify/events.ts +144 -0
- package/src/notify/notice.ts +177 -0
- package/src/prices.ts +175 -0
- package/src/print.ts +54 -0
- package/src/privacy.ts +35 -0
- package/src/prompts.ts +165 -0
- package/src/quality/budgets.ts +249 -0
- package/src/quality/history.ts +149 -0
- package/src/quality/visual.ts +201 -0
- package/src/report/data.ts +475 -0
- package/src/report/html.ts +106 -0
- package/src/report/template.html +84 -0
- package/src/run.ts +243 -0
- package/src/sessionLog.ts +216 -0
- package/src/sessionLogins.ts +94 -0
- package/src/skills/registry.ts +217 -0
- package/src/skills/scaffold.ts +65 -0
- package/src/skills/select.ts +37 -0
- package/src/skills/view.ts +44 -0
- package/src/suite/report.ts +135 -0
- package/src/suite/run.ts +303 -0
- package/src/summary.ts +188 -0
- package/src/tools.ts +130 -0
- package/src/tui/App.tsx +983 -0
- package/src/tui/clipboard.ts +36 -0
- package/src/tui/components.tsx +263 -0
- package/src/tui/dialogs.ts +65 -0
- package/src/tui/main.tsx +20 -0
- package/src/tui/text.ts +28 -0
- package/src/tui/theme.ts +104 -0
- package/src/tui/transcript.tsx +344 -0
- package/src/types.ts +146 -0
- package/src/update.ts +311 -0
- package/src/usage.ts +158 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
import { docSections, resolveDoc, type DocSection } from "./docs.ts"
|
|
5
|
+
import { CAPABILITIES, OUT_OF_SCOPE, type Area, type Capability, type DocRef } from "./registry.ts"
|
|
6
|
+
import { describeRequirement } from "./status.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* docs/user-guide.html: how to use ForgeQE and every capability in detail, generated from the capability registry and
|
|
10
|
+
* HOW_TO_USE.md so it never drifts from either. Its look (theme tokens, logo, masthead, rail) is the one
|
|
11
|
+
* docs/security-data-flow.html sets; the tokens and logo are read from that page, so both share one theme.
|
|
12
|
+
* Regenerate with bun scripts/gen-guide.ts; tests/guide.test.ts fails while the file is stale.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const root = path.resolve(import.meta.dir, "..", "..")
|
|
16
|
+
export const GUIDE_FILE = path.join(root, "docs", "user-guide.html")
|
|
17
|
+
const THEME_SOURCE = path.join(root, "docs", "security-data-flow.html")
|
|
18
|
+
|
|
19
|
+
/** Chapters that are not a capability area: which HOW_TO_USE sections they hold, in order. */
|
|
20
|
+
const START: DocRef[] = ["What you need to run", "Quick start", "1. Install", "2. Scaffold config"].map((heading) => ({ doc: "HOW_TO_USE.md", heading }))
|
|
21
|
+
const USING: DocRef[] = ["3. Shell", "4. Without the shell"].map((heading) => ({ doc: "HOW_TO_USE.md", heading }))
|
|
22
|
+
const REFERENCE: DocRef[] = ["10. MCP", "12. Engine tuning", "13. Logs for debugging", "14. Env overrides", "15. Commands cheat sheet"].map((heading) => ({ doc: "HOW_TO_USE.md", heading }))
|
|
23
|
+
/** Left out on purpose: the Agent map is the registry's own table, which the Overview chapter replaces. */
|
|
24
|
+
export const GUIDE_EXCLUDED = ["HOW_TO_USE.md#agent-map"]
|
|
25
|
+
|
|
26
|
+
const AREA_NOTE: Record<Area, string> = {
|
|
27
|
+
Browser: "Goals carried out in a real browser, and the logins they use",
|
|
28
|
+
"Test cases": "Run, check, write, and explore manual test cases",
|
|
29
|
+
"Automated tests": "Write, heal, find, run, and stabilise tests in your repository",
|
|
30
|
+
"Pull requests": "Reviews, impact, and the tests a change can break",
|
|
31
|
+
API: "Contracts and end-to-end scenarios, without a browser",
|
|
32
|
+
Quality: "Accessibility, page performance, and visual changes",
|
|
33
|
+
Release: "Requirement coverage and a release verdict",
|
|
34
|
+
Operations: "Monitoring and CI pipelines",
|
|
35
|
+
Systems: "Azure DevOps, Jira, and Confluence",
|
|
36
|
+
Setup: "Configuration, skills, and help",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const esc = (s: string) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """)
|
|
40
|
+
const slug = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
|
|
41
|
+
|
|
42
|
+
/** The theme and logo the documentation pages share, from the page that defines them. */
|
|
43
|
+
function sharedLook() {
|
|
44
|
+
const page = readFileSync(THEME_SOURCE, "utf8")
|
|
45
|
+
const tokens = page.match(/\/\* =+ tokens =+ \*\/[\s\S]*?(?=\/\* =+ base =+ \*\/)/)?.[0]
|
|
46
|
+
const favicon = page.match(/<link rel="icon"[^>]*>/)?.[0]
|
|
47
|
+
const logo = page.match(/const MARK = h\("img", \{ src: "(data:image\/png;base64,[A-Za-z0-9+/=]+)"/)?.[1]
|
|
48
|
+
if (!tokens || !favicon || !logo) throw new Error(`${THEME_SOURCE} no longer has its tokens block, favicon, or MARK logo where the guide reads them.`)
|
|
49
|
+
return { tokens: tokens.trim(), favicon, logo }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Which section renders where: the first place that names it; later places link to it. */
|
|
53
|
+
type Placement = { rendered: Set<string>; claimed: Set<string> }
|
|
54
|
+
|
|
55
|
+
function ids(refs: DocRef[]): string[] {
|
|
56
|
+
return refs.map((r) => {
|
|
57
|
+
const s = resolveDoc(r)
|
|
58
|
+
if (!s) throw new Error(`The guide names "${r.heading}" in ${r.doc}, which has no such heading.`)
|
|
59
|
+
return s.id
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sectionAnchor = (s: Pick<DocSection, "id">) => `doc-${s.id.replace(/\.md#/, "-").toLowerCase()}`
|
|
64
|
+
|
|
65
|
+
/** HOW_TO_USE Markdown as HTML: headings one level down, wide tables scrollable, links pointed at this guide or the docs. */
|
|
66
|
+
function markdown(text: string, sections: DocSection[], placement: Placement): string {
|
|
67
|
+
let html = Bun.markdown.html(text)
|
|
68
|
+
html = html.replace(/<(\/?)h([1-5])>/g, (_, close, n) => `<${close}h${Math.min(6, Number(n) + 1)}>`)
|
|
69
|
+
html = html.replace(/<table>/g, '<div class="tbl"><table>').replace(/<\/table>/g, "</table></div>")
|
|
70
|
+
return html.replace(/href="([^"]+)"/g, (whole, href: string) => {
|
|
71
|
+
if (/^(https?:|mailto:)/.test(href)) return `href="${href}" rel="noopener"`
|
|
72
|
+
const [file, hash] = href.startsWith("#") ? ["HOW_TO_USE.md", href.slice(1)] : href.split("#")
|
|
73
|
+
const target = sections.find((s) => s.id === `${file}#${hash}`)
|
|
74
|
+
if (target && placement.claimed.has(target.id)) return `href="#${sectionAnchor(target)}"`
|
|
75
|
+
// Anything else is a file in the package: links are relative to its root, this page is in docs/.
|
|
76
|
+
return `href="../${esc(href)}"`
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** One HOW_TO_USE section with its text, then the subsections no chapter or capability shows elsewhere. */
|
|
81
|
+
function docBlock(id: string, sections: DocSection[], placement: Placement, level: 3 | 4): string {
|
|
82
|
+
const s = sections.find((x) => x.id === id)!
|
|
83
|
+
if (placement.rendered.has(id)) {
|
|
84
|
+
return `<p class="see">See <a href="#${sectionAnchor(s)}">${esc(s.heading)}</a>${s.doc === "README.md" ? " (from the README)" : ""}.</p>`
|
|
85
|
+
}
|
|
86
|
+
placement.rendered.add(id)
|
|
87
|
+
const children = sections.filter((c) => c.parent === id && !placement.claimed.has(c.id) && !GUIDE_EXCLUDED.includes(c.id))
|
|
88
|
+
for (const c of children) placement.rendered.add(c.id)
|
|
89
|
+
return [
|
|
90
|
+
`<section class="doc" id="${sectionAnchor(s)}">`,
|
|
91
|
+
`<h${level}>${esc(s.heading.replace(/^\d+\.\s+/, ""))}</h${level}>`,
|
|
92
|
+
`<div class="prose">${markdown(s.text, sections, placement)}</div>`,
|
|
93
|
+
...children.map((c) => `<section class="doc sub" id="${sectionAnchor(c)}"><h${level + 1}>${esc(c.heading)}</h${level + 1}><div class="prose">${markdown(c.text, sections, placement)}</div></section>`),
|
|
94
|
+
`</section>`,
|
|
95
|
+
].join("\n")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function feature(c: Capability, sections: DocSection[], placement: Placement): string {
|
|
99
|
+
const needs = c.requires.map((r) => {
|
|
100
|
+
const optional = "optional" in r && r.optional
|
|
101
|
+
const kind = r.kind === "input" ? "input" : optional ? "opt" : "req"
|
|
102
|
+
const label = r.kind === "input" ? "You give" : optional ? "Optional" : "Required"
|
|
103
|
+
return `<li><span class="need ${kind}">${label}</span><span>${esc(describeRequirement(r).replace(/^From your request: /, ""))}</span></li>`
|
|
104
|
+
})
|
|
105
|
+
return [
|
|
106
|
+
`<article class="spec feat" id="cap-${c.id}">`,
|
|
107
|
+
`<div class="hd"><span class="mono">${esc(c.area)}</span><h3>${esc(c.title)}</h3></div>`,
|
|
108
|
+
`<p class="ds">${esc(c.summary)}</p>`,
|
|
109
|
+
`<div class="facts">`,
|
|
110
|
+
`<div class="fact"><span class="mono">Ask it</span><ul class="asks">${c.examples.map((e) => `<li>${esc(e)}</li>`).join("")}</ul></div>`,
|
|
111
|
+
`<div class="fact"><span class="mono">What it needs</span>${needs.length ? `<ul class="needs">${needs.join("")}</ul>` : `<p class="muted">Nothing beyond ForgeQE itself.</p>`}</div>`,
|
|
112
|
+
c.limits?.length ? `<div class="fact"><span class="mono">Limits</span><ul class="plainlist">${c.limits.map((l) => `<li>${esc(l)}</li>`).join("")}</ul></div>` : "",
|
|
113
|
+
`<div class="fact"><span class="mono">Tools</span><p class="tools">${c.tools.map((t) => `<code>${esc(t)}</code>`).join(" ")}</p></div>`,
|
|
114
|
+
`</div>`,
|
|
115
|
+
`<div class="detail"><span class="mono">In detail</span>`,
|
|
116
|
+
...ids(c.docs).map((id) => docBlock(id, sections, placement, 4)),
|
|
117
|
+
`</div>`,
|
|
118
|
+
`</article>`,
|
|
119
|
+
]
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join("\n")
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
type Chapter = { id: string; title: string; note: string; body: string }
|
|
125
|
+
|
|
126
|
+
function chapter(n: number, c: Chapter): string {
|
|
127
|
+
return [
|
|
128
|
+
`<section class="chapter" id="${c.id}" aria-labelledby="${c.id}-h">`,
|
|
129
|
+
`<header class="chead"><span class="mono">${String(n).padStart(2, "0")}</span><h2 id="${c.id}-h">${esc(c.title)}</h2><p>${esc(c.note)}</p></header>`,
|
|
130
|
+
c.body,
|
|
131
|
+
`</section>`,
|
|
132
|
+
].join("\n")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function intro(): string {
|
|
136
|
+
const text = readFileSync(path.join(root, "HOW_TO_USE.md"), "utf8")
|
|
137
|
+
const start = text.indexOf("\n", text.indexOf("# How to use ForgeQE")) + 1
|
|
138
|
+
return text.slice(start, text.indexOf("\n## ", start)).replace(/\n---\s*$/, "").trim()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function renderGuide(): string {
|
|
142
|
+
const look = sharedLook()
|
|
143
|
+
const sections = docSections()
|
|
144
|
+
const areas = [...new Set(CAPABILITIES.map((c) => c.area))]
|
|
145
|
+
const placement: Placement = {
|
|
146
|
+
rendered: new Set(),
|
|
147
|
+
claimed: new Set([...ids(START), ...ids(USING), ...ids(REFERENCE), ...CAPABILITIES.flatMap((c) => ids(c.docs))]),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const overview = [
|
|
151
|
+
`<div class="lead prose">${markdown(intro(), sections, placement)}</div>`,
|
|
152
|
+
`<h3 class="band-title">Every feature</h3>`,
|
|
153
|
+
...areas.map((a) => {
|
|
154
|
+
const caps = CAPABILITIES.filter((c) => c.area === a)
|
|
155
|
+
return `<div class="area"><a class="area-name" href="#area-${slug(a)}"><span class="mono">${esc(a)}</span></a><ul class="tiles">${caps
|
|
156
|
+
.map((c) => `<li><a class="tile" href="#cap-${c.id}"><b>${esc(c.title)}</b><span>${esc(c.examples[0]!)}</span></a></li>`)
|
|
157
|
+
.join("")}</ul></div>`
|
|
158
|
+
}),
|
|
159
|
+
`<h3 class="band-title">Not supported</h3>`,
|
|
160
|
+
`<ul class="plainlist">${OUT_OF_SCOPE.map((o) => `<li><b>${esc(o.what)}</b>${o.instead ? `. Instead: ${esc(o.instead)}.` : ""}</li>`).join("")}</ul>`,
|
|
161
|
+
].join("\n")
|
|
162
|
+
|
|
163
|
+
const chapters: Chapter[] = [
|
|
164
|
+
{ id: "overview", title: "Overview", note: "What ForgeQE is, and everything it can do", body: overview },
|
|
165
|
+
{ id: "get-started", title: "Get started", note: "Install, configure a model, and sign in to your tools", body: ids(START).map((id) => docBlock(id, sections, placement, 3)).join("\n") },
|
|
166
|
+
{ id: "using", title: "Using ForgeQE", note: "The shell, its commands, and running without it", body: ids(USING).map((id) => docBlock(id, sections, placement, 3)).join("\n") },
|
|
167
|
+
...areas.map((a) => ({
|
|
168
|
+
id: `area-${slug(a)}`,
|
|
169
|
+
title: a,
|
|
170
|
+
note: AREA_NOTE[a],
|
|
171
|
+
body: CAPABILITIES.filter((c) => c.area === a)
|
|
172
|
+
.map((c) => feature(c, sections, placement))
|
|
173
|
+
.join("\n"),
|
|
174
|
+
})),
|
|
175
|
+
{ id: "reference", title: "Reference", note: "MCP, tuning, logs, environment variables, and every command", body: ids(REFERENCE).map((id) => docBlock(id, sections, placement, 3)).join("\n") },
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const rail = chapters
|
|
179
|
+
.map((c, i) => `<li><a href="#${c.id}"><span class="n">${String(i + 1).padStart(2, "0")}</span><span class="t">${esc(c.title)}</span><span class="d">${esc(c.note)}</span></a></li>`)
|
|
180
|
+
.join("\n")
|
|
181
|
+
|
|
182
|
+
return `<!DOCTYPE html>
|
|
183
|
+
<!-- Generated by scripts/gen-guide.ts from src/capabilities/registry.ts and HOW_TO_USE.md. Do not edit: change those and run bun scripts/gen-guide.ts. -->
|
|
184
|
+
<html lang="en">
|
|
185
|
+
<head>
|
|
186
|
+
<meta charset="utf-8">
|
|
187
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
188
|
+
<title>ForgeQE user guide</title>
|
|
189
|
+
<script>try{var t=localStorage.getItem("wp-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}</script>
|
|
190
|
+
<meta name="description" content="How to use ForgeQE: install, configure, and every feature in detail, with what it needs and how to ask for it.">
|
|
191
|
+
${look.favicon}
|
|
192
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
193
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
194
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Montserrat:ital,wght@0,400;0,500;0,600;0,700;0,800;1,700;1,800&display=swap" rel="stylesheet">
|
|
195
|
+
<style>
|
|
196
|
+
${look.tokens}
|
|
197
|
+
${GUIDE_CSS}
|
|
198
|
+
</style>
|
|
199
|
+
</head>
|
|
200
|
+
<body>
|
|
201
|
+
<a class="skip" href="#panel">Skip to the guide</a>
|
|
202
|
+
<div id="app">
|
|
203
|
+
<header class="mast">
|
|
204
|
+
<div class="mark"><img src="${look.logo}" alt=""><b>ForgeQE</b></div>
|
|
205
|
+
<span class="sep" aria-hidden="true"></span>
|
|
206
|
+
<div class="ttl"><h1>User guide</h1><span class="sub">How to use ForgeQE, and every feature in detail</span></div>
|
|
207
|
+
<div class="right"><button class="theme" type="button" aria-label="Switch theme" title="Switch theme">${SUN}${MOON}</button></div>
|
|
208
|
+
</header>
|
|
209
|
+
<nav class="rail" aria-label="Chapters">
|
|
210
|
+
<div class="cap mono">Contents</div>
|
|
211
|
+
<ol class="chapters">
|
|
212
|
+
${rail}
|
|
213
|
+
</ol>
|
|
214
|
+
</nav>
|
|
215
|
+
<main class="panel" id="panel" tabindex="-1">
|
|
216
|
+
<div class="page">
|
|
217
|
+
${chapters.map((c, i) => chapter(i + 1, c)).join("\n")}
|
|
218
|
+
</div>
|
|
219
|
+
</main>
|
|
220
|
+
</div>
|
|
221
|
+
<script>
|
|
222
|
+
${GUIDE_JS}
|
|
223
|
+
</script>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
226
|
+
`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const SUN = `<svg class="sun" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="3" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3 3l1.4 1.4M11.6 11.6 13 13M3 13l1.4-1.4M11.6 4.4 13 3" stroke="currentColor" stroke-width="1.4"/></svg>`
|
|
230
|
+
const MOON = `<svg class="moon" viewBox="0 0 16 16" aria-hidden="true"><path d="M13 10.2A5.5 5.5 0 0 1 5.8 3a5.5 5.5 0 1 0 7.2 7.2Z" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>`
|
|
231
|
+
|
|
232
|
+
/** The shared shell (masthead, rail, panel, chapter head, card) as docs/security-data-flow.html writes it, plus the guide's reading styles. */
|
|
233
|
+
const GUIDE_CSS = `
|
|
234
|
+
/* ============ base ============ */
|
|
235
|
+
*{box-sizing:border-box}
|
|
236
|
+
html,body{height:100%}
|
|
237
|
+
html{-webkit-text-size-adjust:100%}
|
|
238
|
+
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:12.75px;line-height:1.5;-webkit-font-smoothing:antialiased}
|
|
239
|
+
button{font:inherit;color:inherit}
|
|
240
|
+
:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
|
|
241
|
+
code{font-family:var(--codefont);font-size:.88em;background:var(--code);padding:0 4px;border:1px solid var(--hair);border-radius:2px;white-space:nowrap}
|
|
242
|
+
p{margin:0}
|
|
243
|
+
h2,h3{margin:0}
|
|
244
|
+
a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--rule);text-underline-offset:2px}
|
|
245
|
+
a:hover{text-decoration-color:var(--ink)}
|
|
246
|
+
.muted{color:var(--muted)}
|
|
247
|
+
.mono{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;font-weight:500}
|
|
248
|
+
.skip{position:absolute;left:-9999px;top:8px;z-index:20;background:var(--card);color:var(--ink);padding:6px 10px;border:1px solid var(--ink)}
|
|
249
|
+
.skip:focus{left:8px}
|
|
250
|
+
|
|
251
|
+
/* ============ shell ============ */
|
|
252
|
+
#app{height:100vh;height:100dvh;display:grid;grid-template-columns:216px minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}
|
|
253
|
+
.mast{grid-column:1/-1;display:flex;align-items:center;gap:16px;height:52px;padding:0 20px;background:var(--brand);color:var(--brand-ink)}
|
|
254
|
+
.mark{display:flex;align-items:center;gap:9px;flex:none}
|
|
255
|
+
.mark img{display:block;width:30px;height:30px;padding:2px;background:var(--brand-ink);border-radius:7px}
|
|
256
|
+
.mark b{font-family:var(--sans);font-weight:700;font-size:14px;letter-spacing:-.01em}
|
|
257
|
+
.mast .sep{width:1px;height:22px;background:color-mix(in srgb,var(--brand-ink) 40%,transparent);flex:none}
|
|
258
|
+
.mast .ttl{display:flex;align-items:baseline;gap:12px;min-width:0}
|
|
259
|
+
.mast h1{font-family:var(--display);font-weight:700;font-size:16px;line-height:1;margin:0;white-space:nowrap;letter-spacing:-.005em}
|
|
260
|
+
.mast .sub{color:var(--brand-soft);font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
261
|
+
.mast .right{margin-left:auto;display:flex;align-items:center;gap:14px;flex:none}
|
|
262
|
+
.theme{display:grid;place-items:center;width:30px;height:30px;border:1px solid color-mix(in srgb,var(--brand-ink) 50%,transparent);background:transparent;cursor:pointer;border-radius:0;color:var(--brand-ink)}
|
|
263
|
+
.theme:hover{background:var(--brand-ink);color:var(--brand);border-color:var(--brand-ink)}
|
|
264
|
+
.mast :focus-visible{outline-color:var(--brand-ink)}
|
|
265
|
+
.theme svg{width:14px;height:14px}
|
|
266
|
+
.theme .sun{display:none}
|
|
267
|
+
:root[data-theme="dark"] .theme .sun{display:block}
|
|
268
|
+
:root[data-theme="dark"] .theme .moon{display:none}
|
|
269
|
+
@media (prefers-color-scheme:dark){
|
|
270
|
+
:root:not([data-theme="light"]) .theme .sun{display:block}
|
|
271
|
+
:root:not([data-theme="light"]) .theme .moon{display:none}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* chapter rail */
|
|
275
|
+
.rail{grid-row:2;border-right:1px solid var(--hair);background:var(--band);display:flex;flex-direction:column;min-height:0;overflow:auto}
|
|
276
|
+
.rail .cap{padding:16px 18px 8px;color:var(--muted)}
|
|
277
|
+
.chapters{list-style:none;margin:0;display:flex;flex-direction:column;padding:0 0 12px}
|
|
278
|
+
.chapters a{position:relative;display:grid;grid-template-columns:28px minmax(0,1fr);gap:0 6px;text-decoration:none;border-top:1px solid var(--hair);padding:11px 16px 11px 18px;color:var(--muted)}
|
|
279
|
+
.chapters li:last-child a{border-bottom:1px solid var(--hair)}
|
|
280
|
+
.chapters .n{font-family:var(--mono);font-size:10.5px;font-weight:600;padding-top:2px;color:var(--muted)}
|
|
281
|
+
.chapters .t{font-weight:600;font-size:12.5px;color:var(--ink2);line-height:1.3}
|
|
282
|
+
.chapters .d{grid-column:2;font-size:11px;line-height:1.4;margin-top:2px}
|
|
283
|
+
.chapters a::before{content:"";position:absolute;left:0;top:-1px;bottom:-1px;width:3px;background:var(--ink);transform:scaleY(0);transform-origin:top;transition:transform .25s ease}
|
|
284
|
+
.chapters a:hover{background:var(--card)}
|
|
285
|
+
.chapters a:hover .t{color:var(--ink)}
|
|
286
|
+
.chapters a[aria-current]{background:var(--card);color:var(--ink2)}
|
|
287
|
+
.chapters a[aria-current] .t,.chapters a[aria-current] .n{color:var(--ink)}
|
|
288
|
+
.chapters a[aria-current]::before{transform:scaleY(1)}
|
|
289
|
+
.chapters a:focus-visible{outline-offset:-2px}
|
|
290
|
+
|
|
291
|
+
/* content */
|
|
292
|
+
.panel{grid-row:2;min-width:0;min-height:0;overflow:auto;padding:14px 22px 18px;scroll-behavior:smooth}
|
|
293
|
+
.page{max-width:920px;margin:0 auto;display:flex;flex-direction:column;gap:36px;padding-bottom:40vh}
|
|
294
|
+
.chapter{display:flex;flex-direction:column;gap:16px;scroll-margin-top:8px}
|
|
295
|
+
.chead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;padding-bottom:9px;border-bottom:1px solid var(--hair)}
|
|
296
|
+
.chead .mono{color:var(--muted)}
|
|
297
|
+
.chead h2{font-family:var(--display);font-weight:700;font-size:20px;line-height:1.1;letter-spacing:-.01em}
|
|
298
|
+
.chead p{color:var(--muted);font-size:12px;flex:1 1 280px}
|
|
299
|
+
|
|
300
|
+
/* overview */
|
|
301
|
+
.lead{font-size:13.5px;line-height:1.6;color:var(--ink2)}
|
|
302
|
+
.band-title{font-family:var(--display);font-weight:700;font-size:15.5px;line-height:1.15;margin-top:6px}
|
|
303
|
+
.area{display:grid;grid-template-columns:140px minmax(0,1fr);gap:8px 16px;border-top:1px solid var(--hair);padding-top:10px}
|
|
304
|
+
.area-name{text-decoration:none;color:var(--org);padding-top:2px}
|
|
305
|
+
.area-name:hover{text-decoration:underline;text-underline-offset:3px}
|
|
306
|
+
.tiles{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px}
|
|
307
|
+
.tile{display:flex;flex-direction:column;gap:2px;height:100%;background:var(--card);border:1px solid var(--hair);padding:8px 10px;text-decoration:none;color:var(--ink)}
|
|
308
|
+
.tile b{font-size:12.5px;font-weight:600}
|
|
309
|
+
.tile span{color:var(--muted);font-size:11.5px}
|
|
310
|
+
.tile:hover{border-color:var(--ink)}
|
|
311
|
+
|
|
312
|
+
/* feature card */
|
|
313
|
+
.spec{background:var(--card);border:1px solid var(--hair);display:flex;flex-direction:column;scroll-margin-top:8px}
|
|
314
|
+
.spec>.hd{display:flex;align-items:baseline;gap:10px;padding:10px 14px 8px;border-bottom:1px solid var(--hair)}
|
|
315
|
+
.spec>.hd .mono{color:var(--org)}
|
|
316
|
+
.spec>.hd h3{font-family:var(--display);font-weight:700;font-size:15.5px;line-height:1.15}
|
|
317
|
+
.spec>.ds{padding:8px 14px 10px;color:var(--muted);font-size:12px}
|
|
318
|
+
.facts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 22px;padding:10px 14px 12px;border-top:1px solid var(--hair);background:var(--canvas)}
|
|
319
|
+
.fact{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
320
|
+
.fact>.mono{color:var(--muted)}
|
|
321
|
+
.asks,.needs,.plainlist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
|
|
322
|
+
.asks li{border-left:3px solid var(--ink);padding:2px 0 2px 8px;font-size:12.5px;color:var(--ink)}
|
|
323
|
+
.needs li{display:grid;grid-template-columns:86px minmax(0,1fr);gap:8px;align-items:baseline;font-size:12.25px}
|
|
324
|
+
.need{justify-self:start;display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:9.5px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;padding:1px 6px 1px 5px;border:1px solid currentColor;border-radius:2px;line-height:1.5;white-space:nowrap;color:var(--ink2);background:var(--card)}
|
|
325
|
+
.need::before{content:"";width:5px;height:5px;background:currentColor;flex:none}
|
|
326
|
+
.need.opt{border-style:dashed;color:var(--muted)}
|
|
327
|
+
.need.opt::before{background:none;border:1px solid currentColor;width:4px;height:4px}
|
|
328
|
+
.need.input{color:var(--muted);background:var(--band)}
|
|
329
|
+
.plainlist li{font-size:12.25px}
|
|
330
|
+
.tools{display:flex;flex-wrap:wrap;gap:6px}
|
|
331
|
+
.detail{padding:12px 14px 14px;border-top:1px solid var(--hair);display:flex;flex-direction:column;gap:14px}
|
|
332
|
+
.detail>.mono{color:var(--muted)}
|
|
333
|
+
|
|
334
|
+
/* documentation text */
|
|
335
|
+
.doc{display:flex;flex-direction:column;gap:8px;scroll-margin-top:8px}
|
|
336
|
+
.doc>:is(h3,h4){font-family:var(--display);font-weight:700;font-size:15.5px;line-height:1.15;margin:0}
|
|
337
|
+
.doc.sub{margin-top:8px;padding-top:10px;border-top:1px dashed var(--hair)}
|
|
338
|
+
.doc.sub>:is(h4,h5){font-family:var(--display);font-weight:700;font-size:13.5px;margin:0}
|
|
339
|
+
.chapter>.doc+.doc{padding-top:18px;border-top:1px solid var(--hair)}
|
|
340
|
+
.see{color:var(--muted);font-size:12.25px}
|
|
341
|
+
.prose{font-size:13.5px;line-height:1.6;color:var(--ink2)}
|
|
342
|
+
.prose :where(p,ul,ol,pre,blockquote){margin:0}
|
|
343
|
+
.prose>*+*{margin-top:10px}
|
|
344
|
+
.prose ul,.prose ol{padding-left:20px}
|
|
345
|
+
.prose li+li{margin-top:4px}
|
|
346
|
+
.prose strong{color:var(--ink)}
|
|
347
|
+
.prose h5,.prose h6{font-family:var(--display);font-weight:700;font-size:13.5px;color:var(--ink);margin:16px 0 0}
|
|
348
|
+
.prose code{white-space:normal;overflow-wrap:anywhere}
|
|
349
|
+
.prose pre{margin:0;padding:10px 12px;overflow-x:auto;background:var(--code);border:1px solid var(--hair);font-family:var(--codefont);font-size:11.5px;line-height:1.6}
|
|
350
|
+
.prose pre code{background:none;border:0;padding:0;font-size:inherit;white-space:pre;overflow-wrap:normal}
|
|
351
|
+
.prose hr{border:0;border-top:1px solid var(--hair)}
|
|
352
|
+
.prose blockquote{margin:0;padding-left:12px;border-left:3px solid var(--rule);color:var(--muted)}
|
|
353
|
+
.tbl{overflow-x:auto;border:1px solid var(--hair)}
|
|
354
|
+
.tbl table{border-collapse:collapse;width:100%;font-size:12.25px;line-height:1.5}
|
|
355
|
+
.tbl th,.tbl td{text-align:left;vertical-align:top;padding:6px 10px;border-bottom:1px solid var(--hair)}
|
|
356
|
+
.tbl th{background:var(--band);color:var(--ink);font-weight:600}
|
|
357
|
+
.tbl tr:last-child td{border-bottom:0}
|
|
358
|
+
|
|
359
|
+
/* ============ responsive shell ============ */
|
|
360
|
+
@media (max-width:1023px){
|
|
361
|
+
#app{grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto minmax(0,1fr)}
|
|
362
|
+
.rail{grid-row:2;border-right:0;border-bottom:1px solid var(--hair);overflow:visible}
|
|
363
|
+
.rail .cap{display:none}
|
|
364
|
+
.chapters{flex-direction:row;overflow-x:auto;scrollbar-width:none;padding:0}
|
|
365
|
+
.chapters::-webkit-scrollbar{display:none}
|
|
366
|
+
.chapters li{flex:none}
|
|
367
|
+
.chapters a{grid-template-columns:auto auto;gap:6px;border-top:0;border-right:1px solid var(--hair);padding:10px 14px}
|
|
368
|
+
.chapters li:last-child a{border-bottom:0}
|
|
369
|
+
.chapters .d{display:none}
|
|
370
|
+
.chapters a::before{left:0;right:0;top:auto;bottom:0;width:auto;height:3px;transform:scaleX(0);transform-origin:left}
|
|
371
|
+
.chapters a[aria-current]::before{transform:scaleX(1)}
|
|
372
|
+
.panel{grid-row:3;padding:12px 16px 18px}
|
|
373
|
+
.mast .sub{display:none}
|
|
374
|
+
.facts{grid-template-columns:minmax(0,1fr)}
|
|
375
|
+
.area{grid-template-columns:minmax(0,1fr)}
|
|
376
|
+
}
|
|
377
|
+
@media (max-width:640px){
|
|
378
|
+
.mast{padding:0 12px;gap:10px;height:48px}
|
|
379
|
+
.mast .sep,.mark b{display:none}
|
|
380
|
+
.mast h1{font-size:18px}
|
|
381
|
+
.panel{padding:12px 16px 18px}
|
|
382
|
+
.chead h2{font-size:21px}
|
|
383
|
+
}
|
|
384
|
+
@media (prefers-reduced-motion:reduce){.panel{scroll-behavior:auto}.chapters a::before{transition:none}}
|
|
385
|
+
|
|
386
|
+
/* ============ print ============ */
|
|
387
|
+
@media print{
|
|
388
|
+
#app{display:block;height:auto}
|
|
389
|
+
.rail,.theme,.skip{display:none}
|
|
390
|
+
.panel{overflow:visible;padding:0}
|
|
391
|
+
.page{max-width:none;padding-bottom:0}
|
|
392
|
+
.mast{background:none;color:var(--ink);border-bottom:1px solid var(--ink)}
|
|
393
|
+
.mast .sub{color:var(--muted)}
|
|
394
|
+
.spec,.doc{break-inside:auto}
|
|
395
|
+
.chapter{break-before:page}
|
|
396
|
+
.chapter:first-child{break-before:auto}
|
|
397
|
+
}
|
|
398
|
+
`
|
|
399
|
+
|
|
400
|
+
/** The theme switch (stored as the other documentation pages store it) and the rail following the chapter in view. */
|
|
401
|
+
const GUIDE_JS = `(function () {
|
|
402
|
+
var root = document.documentElement;
|
|
403
|
+
var dark = function () {
|
|
404
|
+
var t = root.getAttribute("data-theme");
|
|
405
|
+
if (t) return t === "dark";
|
|
406
|
+
return window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches;
|
|
407
|
+
};
|
|
408
|
+
var button = document.querySelector(".theme");
|
|
409
|
+
var label = function () { var l = dark() ? "Switch to light theme" : "Switch to dark theme"; button.setAttribute("aria-label", l); button.title = l; };
|
|
410
|
+
label();
|
|
411
|
+
button.addEventListener("click", function () {
|
|
412
|
+
var next = dark() ? "light" : "dark";
|
|
413
|
+
root.setAttribute("data-theme", next);
|
|
414
|
+
try { localStorage.setItem("wp-theme", next); } catch (e) {}
|
|
415
|
+
label();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
var panel = document.getElementById("panel");
|
|
419
|
+
var links = Array.prototype.slice.call(document.querySelectorAll(".chapters a"));
|
|
420
|
+
var byId = {};
|
|
421
|
+
links.forEach(function (a) { byId[a.getAttribute("href").slice(1)] = a; });
|
|
422
|
+
var mark = function (id) {
|
|
423
|
+
links.forEach(function (a) { if (a === byId[id]) a.setAttribute("aria-current", "location"); else a.removeAttribute("aria-current"); });
|
|
424
|
+
var a = byId[id];
|
|
425
|
+
if (a && a.scrollIntoView && window.matchMedia("(max-width:1023px)").matches) a.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
426
|
+
};
|
|
427
|
+
var chapters = Array.prototype.slice.call(document.querySelectorAll(".chapter"));
|
|
428
|
+
var current = function () {
|
|
429
|
+
var top = panel.getBoundingClientRect().top + 24, id = chapters[0].id;
|
|
430
|
+
chapters.forEach(function (c) { if (c.getBoundingClientRect().top <= top) id = c.id; });
|
|
431
|
+
return id;
|
|
432
|
+
};
|
|
433
|
+
var ticking = false;
|
|
434
|
+
panel.addEventListener("scroll", function () {
|
|
435
|
+
if (ticking) return;
|
|
436
|
+
ticking = true;
|
|
437
|
+
requestAnimationFrame(function () { ticking = false; mark(current()); });
|
|
438
|
+
}, { passive: true });
|
|
439
|
+
mark(location.hash && byId[location.hash.slice(1)] ? location.hash.slice(1) : current());
|
|
440
|
+
// The browser jumps to a link's section before the fonts arrive, and the text reflows when they do: jump again
|
|
441
|
+
// after each font loads and once the page has, until the reader scrolls themselves.
|
|
442
|
+
var target = location.hash && document.getElementById(location.hash.slice(1));
|
|
443
|
+
if (target) {
|
|
444
|
+
var moved = false;
|
|
445
|
+
var stop = function () { moved = true; };
|
|
446
|
+
["wheel", "touchstart", "keydown", "mousedown"].forEach(function (e) { window.addEventListener(e, stop, { once: true, passive: true }); });
|
|
447
|
+
var settle = function () {
|
|
448
|
+
if (moved) return;
|
|
449
|
+
target.scrollIntoView({ block: "start", behavior: "instant" });
|
|
450
|
+
mark(current());
|
|
451
|
+
};
|
|
452
|
+
window.addEventListener("load", settle);
|
|
453
|
+
if (document.fonts) document.fonts.addEventListener("loadingdone", settle);
|
|
454
|
+
}
|
|
455
|
+
})();`
|