@open-press/cli 0.7.0 → 0.7.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/package.json +1 -1
- package/template/core/AGENTS.md +126 -0
- package/template/core/CHANGELOG.md +20 -0
- package/template/core/engine/commands/upgrade.mjs +47 -5
- package/template/core/engine/react/document-export.mjs +15 -0
- package/template/core/engine/react/mdx-compile.mjs +91 -0
- package/template/core/engine/react/pipeline/frame-measurement.mjs +38 -9
- package/template/core/package.json +1 -1
- package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +26 -12
- package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +37 -17
- package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +34 -16
- package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +22 -8
- package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +20 -15
- package/template/packs/academic-paper/document/components/Page.tsx +26 -3
- package/template/packs/academic-paper/document/index.tsx +51 -59
- package/template/packs/academic-paper/document/media/figure-placeholder.svg +9 -0
- package/template/packs/academic-paper/document/theme/base/page-contract.css +30 -13
- package/template/packs/academic-paper/document/theme/base/typography.css +30 -33
- package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +74 -47
package/package.json
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Working on this open-press workspace
|
|
2
|
+
|
|
3
|
+
This directory is an **open-press workspace** scaffolded by
|
|
4
|
+
`@open-press/cli`. You (the agent) own:
|
|
5
|
+
|
|
6
|
+
- `document/` — chapters (MDX), components, theme, media. The actual content.
|
|
7
|
+
- `package.json` / `openpress.config.mjs` — project metadata + build config.
|
|
8
|
+
- `.agents/skills/` — agent skills installed by `npx skills` (auto-refreshed via `npx open-press upgrade`).
|
|
9
|
+
|
|
10
|
+
The rest of the tree is the open-press framework copied at scaffold time
|
|
11
|
+
(`engine/`, `src/`, `vite.config.ts`, etc). Treat it as vendored — don't
|
|
12
|
+
hand-edit unless the user explicitly asks; the next `npx open-press upgrade`
|
|
13
|
+
will rewrite it.
|
|
14
|
+
|
|
15
|
+
## Quick reference
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run dev # workbench at http://127.0.0.1:5173/?dev=1
|
|
19
|
+
npm run openpress:validate # structural checks
|
|
20
|
+
npm run openpress:render # full render through chromium
|
|
21
|
+
npm run openpress:pdf # write document.pdf
|
|
22
|
+
npm run openpress:export # write public/openpress/document.json
|
|
23
|
+
npm run openpress:deploy # deploy via the configured adapter
|
|
24
|
+
npx open-press doctor # current vs latest version + pending migrations
|
|
25
|
+
npx open-press upgrade # apply the upgrade flow (see below)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## When the user asks to upgrade
|
|
29
|
+
|
|
30
|
+
Triggers: "升級 / 套件更新 / upgrade open-press / apply latest design /
|
|
31
|
+
update to vX.Y.Z" etc.
|
|
32
|
+
|
|
33
|
+
**Follow the upgrade SOP, do NOT manually diff template versions:**
|
|
34
|
+
|
|
35
|
+
1. `npx open-press doctor` — confirm current vs latest, count pending
|
|
36
|
+
migrations.
|
|
37
|
+
2. Ask the user "go ahead?" — briefly mention what will change (deps,
|
|
38
|
+
skills, migrations to read).
|
|
39
|
+
3. `npx open-press upgrade` — refreshes `@open-press/core`, refreshes
|
|
40
|
+
installed skills, **fetches migration notes for each pending version
|
|
41
|
+
into `.openpress/migrations/<version>.md`** and lists the paths.
|
|
42
|
+
4. For each migration file listed, read it fully. Each has a
|
|
43
|
+
`Document-level changes` section with `rg` find + rewrite rules.
|
|
44
|
+
Apply to `document/` with user confirmation.
|
|
45
|
+
5. Verify:
|
|
46
|
+
```bash
|
|
47
|
+
npm run openpress:validate
|
|
48
|
+
npm run openpress:render
|
|
49
|
+
```
|
|
50
|
+
Fix anything broken using the migration notes.
|
|
51
|
+
6. Report to the user: starting version → ending version, what was
|
|
52
|
+
applied, anything that needed manual judgement.
|
|
53
|
+
|
|
54
|
+
**Anti-pattern**: running `npx @open-press/cli@latest init` somewhere
|
|
55
|
+
and manually diffing against the workspace. The migration notes are the
|
|
56
|
+
authoritative source for what changed; fresh templates ship default
|
|
57
|
+
content that does not apply to a customised workspace.
|
|
58
|
+
|
|
59
|
+
## When the user says "I changed X but the page didn't update"
|
|
60
|
+
|
|
61
|
+
Common cause: **the reader renders from a static `public/openpress/document.json`,
|
|
62
|
+
not from your live MDX / theme files.** Vite Hot Reload covers React UI
|
|
63
|
+
chrome (workbench panels, inspector, navigation) but it does **not**
|
|
64
|
+
regenerate `document.json`. So edits to:
|
|
65
|
+
|
|
66
|
+
- `document/chapters/**/*.mdx` (prose)
|
|
67
|
+
- `document/index.tsx` (Press tree, Cover/BackCover JSX)
|
|
68
|
+
- `document/components/**/*.tsx` (Page, openers, custom components)
|
|
69
|
+
- `document/theme/**` style files that affect pagination capacity
|
|
70
|
+
- `openpress.config.mjs` metadata (title, captionNumbering, …)
|
|
71
|
+
|
|
72
|
+
…all need a re-export before the workbench / public viewer reflect
|
|
73
|
+
the change:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run openpress:export # regenerate public/openpress/document.json
|
|
77
|
+
# then refresh the browser
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Quick rules of thumb:
|
|
81
|
+
|
|
82
|
+
- Pure CSS edits under `document/theme/` that don't move blocks → HMR
|
|
83
|
+
is enough (CSS is hot-replaced).
|
|
84
|
+
- Anything that affects content, pagination, or metadata → re-export.
|
|
85
|
+
- `npm run openpress:render` is `export` + extra asset sync; either
|
|
86
|
+
works to refresh the JSON.
|
|
87
|
+
|
|
88
|
+
**Agent SOP**: after applying any non-CSS edit to `document/`, run
|
|
89
|
+
`npm run openpress:export` before telling the user "done". If they ask
|
|
90
|
+
"why didn't my change show up?", check whether `document.json` was
|
|
91
|
+
regenerated since the edit.
|
|
92
|
+
|
|
93
|
+
## When the user reports a render / paginate issue
|
|
94
|
+
|
|
95
|
+
Press Tree paginates at build time. Common things to check:
|
|
96
|
+
|
|
97
|
+
1. `npm run openpress:export` then inspect
|
|
98
|
+
`public/openpress/document.json` for `source.warnings` (chain
|
|
99
|
+
overflow, missing chains, etc.).
|
|
100
|
+
2. `npm run openpress:validate` for structural issues
|
|
101
|
+
(missing entries, broken anchors).
|
|
102
|
+
3. `npm run dev` and use the workbench inspector to find which MDX
|
|
103
|
+
block / Frame element is misbehaving — comments and inline
|
|
104
|
+
annotations work directly from there.
|
|
105
|
+
|
|
106
|
+
## Skills
|
|
107
|
+
|
|
108
|
+
This workspace ships agent skills under `.agents/skills/`. If your
|
|
109
|
+
platform supports skills (Claude Code, Cursor, Codex, Cline, Gemini
|
|
110
|
+
CLI, …), prefer invoking them over re-reading this file:
|
|
111
|
+
|
|
112
|
+
- `openpress` — operate the workspace (CLI, validate, export, render,
|
|
113
|
+
PDF, deploy, search/replace, comments, upgrades, routing).
|
|
114
|
+
- `openpress-writing` — writing-time rules for MDX prose.
|
|
115
|
+
- `openpress-design` — theme tokens, layout, page surfaces.
|
|
116
|
+
- `openpress-deploy` — deployment workflows.
|
|
117
|
+
- `openpress-init` — scaffolding new workspaces.
|
|
118
|
+
- Plus any style-pack-specific skills installed by the user.
|
|
119
|
+
|
|
120
|
+
Skills are kept in sync by `npx skills upgrade` (run automatically
|
|
121
|
+
inside `npx open-press upgrade`).
|
|
122
|
+
|
|
123
|
+
## Reporting issues
|
|
124
|
+
|
|
125
|
+
- Issues / questions: https://github.com/quan0715/open-press/issues
|
|
126
|
+
- Source: https://github.com/quan0715/open-press
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @open-press/core
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Measurement pipeline + pagination fixes:
|
|
8
|
+
|
|
9
|
+
- **Measurement**: wait on `document.fonts.ready`, image `load`/`error` + `decode()`,
|
|
10
|
+
and two `requestAnimationFrame` ticks before sampling block heights so figures
|
|
11
|
+
no longer under-measure on cold loads.
|
|
12
|
+
- **Measurement**: inline relative `media/`, `./media/`, and `/openpress/media/`
|
|
13
|
+
image sources during the SSR measurement pass (previously only the absolute
|
|
14
|
+
`/openpress/media/...` form was rewritten, leaving relative refs as broken).
|
|
15
|
+
- **MDX compile**: split bullet/numbered lists into per-item paginable blocks
|
|
16
|
+
so long lists can break across pages without losing ordered numbering.
|
|
17
|
+
- **Debug**: new `OPENPRESS_DEBUG_ALLOC` env var prints per-iteration allocator
|
|
18
|
+
state (mdxArea capacities, block heights, pagination hints, warnings).
|
|
19
|
+
- **Academic-paper starter**: `<MdxArea overflow="extend">` on the body and the
|
|
20
|
+
single-column `.reader-page--content .page-body` override removed so content
|
|
21
|
+
paginates naturally with the new allocator.
|
|
22
|
+
|
|
3
23
|
## 0.7.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { diagnose } from "./doctor.mjs";
|
|
5
5
|
import { runCommand } from "./_shared.mjs";
|
|
6
6
|
|
|
7
|
+
// Migration notes live in the framework repo, not in scaffolded workspaces.
|
|
8
|
+
// `npx open-press upgrade` fetches the notes for each pending version and
|
|
9
|
+
// caches them under `.openpress/migrations/` so agents can read locally.
|
|
10
|
+
const MIGRATION_REMOTE_BASE = "https://raw.githubusercontent.com/quan0715/open-press/main/docs/migrations";
|
|
11
|
+
const MIGRATION_CACHE_DIR = path.join(".openpress", "migrations");
|
|
12
|
+
|
|
7
13
|
export async function run({ root, options }) {
|
|
8
14
|
const dryRun = Boolean(options?.dryRun);
|
|
9
15
|
const skipSkills = Boolean(options?.noSkills);
|
|
@@ -85,7 +91,11 @@ export async function run({ root, options }) {
|
|
|
85
91
|
process.stdout.write(" (no migration docs in this version range)\n\n");
|
|
86
92
|
} else {
|
|
87
93
|
for (const m of migrationContents) {
|
|
88
|
-
|
|
94
|
+
if (m.path) {
|
|
95
|
+
process.stdout.write(` ─ ${m.path}${m.fetched ? " (fetched from github)" : ""}\n`);
|
|
96
|
+
} else {
|
|
97
|
+
process.stdout.write(` ─ ${m.version}.md (not found locally or on github — check the repo manually)\n`);
|
|
98
|
+
}
|
|
89
99
|
}
|
|
90
100
|
process.stdout.write(
|
|
91
101
|
"\nAgent: open each file, identify document-level changes, grep document/ for affected patterns, propose edits before applying.\n",
|
|
@@ -107,11 +117,43 @@ async function hasCoreDep(root) {
|
|
|
107
117
|
|
|
108
118
|
async function loadMigrations(root, versions) {
|
|
109
119
|
const results = [];
|
|
120
|
+
const cacheDir = path.join(root, MIGRATION_CACHE_DIR);
|
|
121
|
+
await mkdir(cacheDir, { recursive: true });
|
|
122
|
+
|
|
110
123
|
for (const v of versions) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
// Framework repo has docs/migrations/ at root — prefer local if present
|
|
125
|
+
// (covers the open-press framework repo itself acting as a workspace).
|
|
126
|
+
const localDocsPath = path.join(root, "docs", "migrations", `${v}.md`);
|
|
127
|
+
if (existsSync(localDocsPath)) {
|
|
128
|
+
results.push({ version: v, path: path.relative(root, localDocsPath), fetched: false });
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Otherwise fetch from GitHub raw and cache to .openpress/migrations/.
|
|
133
|
+
const cachedPath = path.join(cacheDir, `${v}.md`);
|
|
134
|
+
if (existsSync(cachedPath)) {
|
|
135
|
+
results.push({ version: v, path: path.relative(root, cachedPath), fetched: false });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const body = await fetchMigration(v);
|
|
140
|
+
if (body) {
|
|
141
|
+
await writeFile(cachedPath, body, "utf8");
|
|
142
|
+
results.push({ version: v, path: path.relative(root, cachedPath), fetched: true });
|
|
143
|
+
} else {
|
|
144
|
+
results.push({ version: v, path: null, fetched: false });
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
return results;
|
|
117
148
|
}
|
|
149
|
+
|
|
150
|
+
async function fetchMigration(version) {
|
|
151
|
+
const url = `${MIGRATION_REMOTE_BASE}/${version}.md`;
|
|
152
|
+
try {
|
|
153
|
+
const res = await fetch(url, { headers: { Accept: "text/plain" } });
|
|
154
|
+
if (!res.ok) return null;
|
|
155
|
+
return await res.text();
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -92,6 +92,21 @@ export async function exportReactDocument(root = ".", { syncAssets = true } = {}
|
|
|
92
92
|
blockHeights: measurement.blockHeights,
|
|
93
93
|
sources,
|
|
94
94
|
});
|
|
95
|
+
if (process.env.OPENPRESS_DEBUG_ALLOC) {
|
|
96
|
+
const sample = measurement.mdxAreas
|
|
97
|
+
.slice(0, 5)
|
|
98
|
+
.map((a) => `${a.frameKey}#${a.indexInFrame} cap=${a.capacity.toFixed(0)} raw=${(a.rawHeight ?? 0).toFixed(0)}`);
|
|
99
|
+
const blocks = measurement.blockHeights
|
|
100
|
+
.slice(0, 8)
|
|
101
|
+
.map((b) => `${b.id} h=${b.height.toFixed(0)}`);
|
|
102
|
+
process.stderr.write(`[allocator iter ${iteration}]\n`);
|
|
103
|
+
process.stderr.write(` mdxAreas[0..4]: ${sample.join(" | ")}\n`);
|
|
104
|
+
process.stderr.write(` blocks[0..7]: ${blocks.join(" | ")}\n`);
|
|
105
|
+
process.stderr.write(` hints: ${JSON.stringify(alloc.hints.totalPagesPerChain)}\n`);
|
|
106
|
+
if (alloc.warnings.length > 0) {
|
|
107
|
+
process.stderr.write(` warnings: ${JSON.stringify(alloc.warnings)}\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
95
110
|
if (hintsEqual(hints, alloc.hints)) {
|
|
96
111
|
allocation = alloc.allocation;
|
|
97
112
|
warnings = alloc.warnings;
|
|
@@ -99,6 +99,16 @@ export function rehypeBlockIds(options = {}) {
|
|
|
99
99
|
includeBlockIds,
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
if (block.name === "ul" || block.name === "ol") {
|
|
103
|
+
return applyListItemBlocks({
|
|
104
|
+
node,
|
|
105
|
+
id,
|
|
106
|
+
blocks,
|
|
107
|
+
filePath,
|
|
108
|
+
chapterSlug,
|
|
109
|
+
includeBlockIds,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
102
112
|
if (includeBlockIds && !includeBlockIds.has(id)) return false;
|
|
103
113
|
|
|
104
114
|
setDataAttribute(node, "data-openpress-block-id", id);
|
|
@@ -314,6 +324,87 @@ function blockInfo(node) {
|
|
|
314
324
|
return null;
|
|
315
325
|
}
|
|
316
326
|
|
|
327
|
+
function applyListItemBlocks({
|
|
328
|
+
node,
|
|
329
|
+
id,
|
|
330
|
+
blocks,
|
|
331
|
+
filePath,
|
|
332
|
+
chapterSlug,
|
|
333
|
+
includeBlockIds,
|
|
334
|
+
}) {
|
|
335
|
+
const items = listItems(node);
|
|
336
|
+
if (items.length === 0) {
|
|
337
|
+
if (includeBlockIds && !includeBlockIds.has(id)) return false;
|
|
338
|
+
setDataAttribute(node, "data-openpress-block-id", id);
|
|
339
|
+
blocks.push({
|
|
340
|
+
id,
|
|
341
|
+
kind: "element",
|
|
342
|
+
name: node.tagName,
|
|
343
|
+
text: textContent(node).trim() || undefined,
|
|
344
|
+
filePath,
|
|
345
|
+
chapterSlug,
|
|
346
|
+
source: sourcePosition(node.position),
|
|
347
|
+
});
|
|
348
|
+
return "skip";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const itemRecords = items.map((item, index) => ({
|
|
352
|
+
id: `${id}-i${index}`,
|
|
353
|
+
node: item,
|
|
354
|
+
index,
|
|
355
|
+
}));
|
|
356
|
+
const selected = includeBlockIds
|
|
357
|
+
? itemRecords.filter((item) => includeBlockIds.has(item.id))
|
|
358
|
+
: itemRecords;
|
|
359
|
+
if (selected.length === 0) return false;
|
|
360
|
+
|
|
361
|
+
setDataAttribute(node, "data-openpress-list-id", id);
|
|
362
|
+
|
|
363
|
+
// For ordered lists, continuation pages must keep numbering picking up
|
|
364
|
+
// from the first surviving item. `start` is the 1-based number of the
|
|
365
|
+
// first `<li>` rendered, so if the original list had `start="5"` and we
|
|
366
|
+
// dropped the first three items, continuation starts at 5 + 3 = 8.
|
|
367
|
+
if (node.tagName === "ol" && selected[0]?.index > 0) {
|
|
368
|
+
const baseStart = Number(node.properties?.start ?? 1);
|
|
369
|
+
const continuationStart = baseStart + selected[0].index;
|
|
370
|
+
node.properties = { ...node.properties, start: continuationStart };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const selectedNodes = new Set(selected.map((item) => item.node));
|
|
374
|
+
pruneUnselectedListItems(node, new Set(itemRecords.map((item) => item.node)), selectedNodes);
|
|
375
|
+
|
|
376
|
+
for (const item of selected) {
|
|
377
|
+
setDataAttribute(item.node, "data-openpress-block-id", item.id);
|
|
378
|
+
blocks.push({
|
|
379
|
+
id: item.id,
|
|
380
|
+
kind: "list-item",
|
|
381
|
+
name: "list-item",
|
|
382
|
+
text: textContent(item.node).trim() || undefined,
|
|
383
|
+
filePath,
|
|
384
|
+
chapterSlug,
|
|
385
|
+
listId: id,
|
|
386
|
+
listTag: node.tagName,
|
|
387
|
+
itemIndex: item.index,
|
|
388
|
+
source: sourcePosition(item.node.position ?? node.position),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return "skip";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function listItems(list) {
|
|
395
|
+
if (list?.type !== "element") return [];
|
|
396
|
+
if (list.tagName !== "ul" && list.tagName !== "ol") return [];
|
|
397
|
+
return (list.children ?? []).filter((child) => child?.type === "element" && child.tagName === "li");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function pruneUnselectedListItems(node, itemNodes, selectedNodes) {
|
|
401
|
+
if (!Array.isArray(node?.children)) return;
|
|
402
|
+
node.children = node.children.filter((child) => {
|
|
403
|
+
if (!itemNodes.has(child)) return true;
|
|
404
|
+
return selectedNodes.has(child);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
317
408
|
function tableBodyRows(table) {
|
|
318
409
|
if (table?.type !== "element" || table.tagName !== "table") return [];
|
|
319
410
|
const rows = [];
|
|
@@ -135,15 +135,30 @@ async function runChromiumMeasurement(html, viewport) {
|
|
|
135
135
|
try {
|
|
136
136
|
const page = await browser.newPage({ viewport });
|
|
137
137
|
await page.setContent(html, { waitUntil: "load" });
|
|
138
|
+
// Match the print-ready settle: fonts first (font metrics affect image
|
|
139
|
+
// alt-text fallback boxes), then await every image's `complete` AND
|
|
140
|
+
// `decode()` so intrinsic sizes are committed before layout, then two
|
|
141
|
+
// animation frames so the chromium layout pass observes the final box
|
|
142
|
+
// model. Without this, `getBoundingClientRect()` on figures that hold
|
|
143
|
+
// images can race the decode and return collapsed heights, causing the
|
|
144
|
+
// allocator to pack too many blocks per page.
|
|
138
145
|
await page.evaluate(async () => {
|
|
139
|
-
await Promise.all(Array.from(document.images).map((img) => {
|
|
140
|
-
if (img.complete) return Promise.resolve();
|
|
141
|
-
return new Promise((resolve) => {
|
|
142
|
-
img.addEventListener("load", resolve, { once: true });
|
|
143
|
-
img.addEventListener("error", resolve, { once: true });
|
|
144
|
-
});
|
|
145
|
-
}));
|
|
146
146
|
if (document.fonts?.ready) await document.fonts.ready;
|
|
147
|
+
await Promise.all(Array.from(document.images).map(async (img) => {
|
|
148
|
+
if (!img.complete) {
|
|
149
|
+
await new Promise((resolve) => {
|
|
150
|
+
const settle = () => {
|
|
151
|
+
img.removeEventListener("load", settle);
|
|
152
|
+
img.removeEventListener("error", settle);
|
|
153
|
+
resolve(undefined);
|
|
154
|
+
};
|
|
155
|
+
img.addEventListener("load", settle, { once: true });
|
|
156
|
+
img.addEventListener("error", settle, { once: true });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
await img.decode?.().catch(() => undefined);
|
|
160
|
+
}));
|
|
161
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
147
162
|
});
|
|
148
163
|
|
|
149
164
|
const mdxAreas = await page.evaluate((safety) => {
|
|
@@ -232,13 +247,27 @@ async function inlineMeasurementMediaUrls(html, mediaDir) {
|
|
|
232
247
|
if (!mediaDir || !html) return html;
|
|
233
248
|
let out = String(html);
|
|
234
249
|
const matches = new Set();
|
|
235
|
-
for (const match of out.matchAll(
|
|
236
|
-
|
|
250
|
+
for (const match of out.matchAll(/\bsrc=(['"])([^\1]*?)\1/g)) {
|
|
251
|
+
const src = match[2];
|
|
252
|
+
if (!src) continue;
|
|
253
|
+
if (src.startsWith('/openpress/media/')) {
|
|
254
|
+
matches.add(src.slice('/openpress/media/'.length));
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (src.startsWith('media/')) {
|
|
258
|
+
matches.add(src.slice('media/'.length));
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (src.startsWith('./media/')) {
|
|
262
|
+
matches.add(src.slice('./media/'.length));
|
|
263
|
+
}
|
|
237
264
|
}
|
|
238
265
|
for (const rawName of matches) {
|
|
239
266
|
const dataUrl = await mediaDataUrl(mediaDir, rawName);
|
|
240
267
|
if (!dataUrl) continue;
|
|
241
268
|
out = out.replaceAll(`/openpress/media/${rawName}`, dataUrl);
|
|
269
|
+
out = out.replaceAll(`media/${rawName}`, dataUrl);
|
|
270
|
+
out = out.replaceAll(`./media/${rawName}`, dataUrl);
|
|
242
271
|
}
|
|
243
272
|
return out;
|
|
244
273
|
}
|
package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
## Introduction
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The purpose of this paper template is to provide a compact, readable structure for a
|
|
4
|
+
machine-learning research manuscript written in a formal journal style.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
### Background and Motivation
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Recent studies have shown that modern experiments produce larger datasets and richer
|
|
9
|
+
artifact pipelines, but the writing process is often bottlenecked by repeated formatting
|
|
10
|
+
adjustments. A lightweight drafting layer can decouple scientific reasoning from
|
|
11
|
+
typesetting concerns and let the author focus on experimental validity first.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
### Problem Statement
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `05-references/` — numbered `[1]`, `[2]` references.
|
|
15
|
+
The key challenge addressed in this draft is to keep the manuscript internally
|
|
16
|
+
consistent while moving from scratch notes to a submission-ready narrative. The
|
|
17
|
+
workflow should preserve section hierarchy, citation order, and figure/table integrity
|
|
18
|
+
without requiring manual renumbering.
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
### Contribution
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
This demo paper includes the following contributions:
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
- A reproducible chapter flow (`Introduction → Methods → Results and Discussion →
|
|
25
|
+
Conclusion`),
|
|
26
|
+
- Consistent heading levels for major and minor sections,
|
|
27
|
+
- Placeholder media and tables that can be swapped with real results,
|
|
28
|
+
- A reference block and placeholder metadata for a clean handoff to final publication
|
|
29
|
+
tooling.
|
|
30
|
+
|
|
31
|
+
### Paper Organization
|
|
32
|
+
|
|
33
|
+
The paper is organized as follows: Section 2 details the methodology,
|
|
34
|
+
Section 3 reports results and discusses representative scenarios,
|
|
35
|
+
Section 4 summarizes conclusions and practical recommendations.
|
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
## Methods
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This section describes a hypothetical experimental design suitable for an applied
|
|
4
|
+
machine-learning paper.
|
|
4
5
|
|
|
5
|
-
###
|
|
6
|
+
### Data and Problem Setup
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
We assume a synthetic dataset with 3,200 anonymized samples collected under three
|
|
9
|
+
operational conditions. Each sample includes paired feature vectors, timestamped
|
|
10
|
+
metadata, and a binary outcome label. The baseline split is 70/15/15 for training,
|
|
11
|
+
validation, and held-out test sets.
|
|
8
12
|
|
|
9
|
-
###
|
|
13
|
+
### Core Pipeline
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
The pipeline has three stages:
|
|
12
16
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
3.
|
|
16
|
-
4. State randomness control (seeds, repeats, confidence reporting).
|
|
17
|
+
1. **Preprocessing**: normalization, missing-value handling, and feature screening.
|
|
18
|
+
2. **Modeling**: training a compact baseline model and a comparison ablation set.
|
|
19
|
+
3. **Evaluation**: calibration-aware scoring and bootstrapped confidence intervals.
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
For this draft, preprocessing includes clipping extreme values and aligning all
|
|
22
|
+
continuous fields to SI-style unit notation.
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
### Equations
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
The main objective is written as:
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
$$
|
|
29
|
+
\mathcal{L}(\theta) = \sum_{i=1}^{N}\left(\hat{y}_{i}(\theta)-y_{i}\right)^2
|
|
30
|
+
$$
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
where $\theta$ are trainable parameters and the objective is minimized on the
|
|
33
|
+
training subset.
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
### Evaluation Protocol
|
|
36
|
+
|
|
37
|
+
Model quality is measured by macro-averaged F1, AUROC, and an uncertainty-aware
|
|
38
|
+
calibration score. Reproducibility is ensured by fixing the random seed and logging
|
|
39
|
+
all preprocessing parameters.
|
|
40
|
+
|
|
41
|
+
### Implementation Notes
|
|
42
|
+
|
|
43
|
+
- All runs were executed on a dual-socket compute node with two GPUs.
|
|
44
|
+
- Checkpoints were stored every 5 epochs.
|
|
45
|
+
- Logging followed a single JSON schema shared across all experiments.
|
|
46
|
+
|
|
47
|
+
### Validation and Quality Control
|
|
48
|
+
|
|
49
|
+
Validation is automated through a script that checks missing captions, file path
|
|
50
|
+
consistency, and reference formatting before final export.
|
|
@@ -1,29 +1,47 @@
|
|
|
1
1
|
## Results and Discussion
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
### Benchmark Results
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The synthetic experiment yields a consistent improvement trend across three datasets.
|
|
6
|
+
Performance gains are modest but stable after hyperparameter tuning.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
| Metric | Baseline | Proposed | Improvement |
|
|
9
|
+
| --- | --- | --- | --- |
|
|
10
|
+
| Macro-F1 | 0.61 | 0.67 | +9.8% |
|
|
11
|
+
| AUROC | 0.74 | 0.81 | +9.5% |
|
|
12
|
+
| Calibration Error | 0.082 | 0.043 | -47.6% |
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
The proposed model retains a similar variance profile while improving score
|
|
15
|
+
stability in low-sample conditions.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
### Representative Visualization
|
|
18
|
+
|
|
19
|
+
<img
|
|
20
|
+
src="media/figure-placeholder.svg"
|
|
21
|
+
alt="Example of a figure placeholder"
|
|
22
|
+
style={{ maxWidth: "420px", width: "100%", height: "auto" }}
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
Figure 1. Example of a result trend visualization (placeholder image).
|
|
26
|
+
|
|
27
|
+
### Error Analysis
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
Some error cases cluster around edge classes with incomplete metadata. These cases
|
|
30
|
+
benefit from better augmentation and a revised class-balanced loss.
|
|
18
31
|
|
|
19
|
-
###
|
|
32
|
+
### Sensitivity Study
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
- Training set size below 40% causes a steeper drop in recall.
|
|
35
|
+
- Early stopping thresholds above 3 epochs reduce overfitting in our setting.
|
|
36
|
+
- Calibrated post-processing improves confidence robustness.
|
|
22
37
|
|
|
23
|
-
###
|
|
38
|
+
### Discussion
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
The most practical takeaway is that small engineering changes in preprocessing and
|
|
41
|
+
evaluation discipline can produce measurable gains even when architecture changes are
|
|
42
|
+
minimal.
|
|
26
43
|
|
|
27
|
-
###
|
|
44
|
+
### Limitations
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
This section reports synthetic results only; real-world deployment would require
|
|
47
|
+
cross-domain validation, privacy checks, and a stronger external benchmark suite.
|