@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-press/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Scaffolder for open-press — AI-first fixed-layout document workspaces.",
6
6
  "license": "MIT",
@@ -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
- process.stdout.write(` ─ ${m.path}\n`);
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
- const p = path.join(root, "docs", "migrations", `${v}.md`);
112
- if (existsSync(p)) {
113
- results.push({ version: v, path: path.relative(root, p) });
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(/\/openpress\/media\/([^"')\s>]+)/g)) {
236
- matches.add(match[1]);
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-press/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "open-press core — runtime primitives, CLI, and render pipeline for AI-first fixed-layout documents.",
6
6
  "license": "MIT",
@@ -1,21 +1,35 @@
1
1
  ## Introduction
2
2
 
3
- This document is a model and starting point for an academic paper drafted in open-press. The structure follows the IEEE conference template conventions where they make sense for screen-first reading: numbered sections, italic sub-sections, an abstract block, and a numbered references list. The two-column body of IEEEtran is not yet supported — single-column will land as the v0.8 paged.js migration arrives.
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
- Use this pack for **drafting, iteration, and preprint distribution**. When the paper is ready for venue submission, export the prose into the publisher's required LaTeX class (`IEEEtran`, `acmart`, etc.). The pack does not replace LaTeX for camera-ready submission; it replaces the awkward iteration loop *before* submission.
6
+ ### Background and Motivation
6
7
 
7
- ### Where to start
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
- Each chapter directory under `document/chapters/` is one paper section:
13
+ ### Problem Statement
10
14
 
11
- - `01-introduction/` motivate the work, state the contribution, summarise the structure.
12
- - `02-methods/` describe what you did, with enough detail to reproduce.
13
- - `03-results-and-discussion/` present results with figures and tables; interpret what they mean.
14
- - `04-acknowledgment/` acknowledge funding, collaborators, reviewers.
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
- Replace each chapter's MDX content with your own. The skeleton text below each `##` heading is illustrative — overwrite it.
20
+ ### Contribution
18
21
 
19
- ### Abstract and Index Terms
22
+ This demo paper includes the following contributions:
20
23
 
21
- Edit the abstract and index terms inside `document/index.tsx` (the `cover` JSX export). They render on the title page above the body. Keep the abstract under 250 words and avoid abbreviations, symbols, or math in it.
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
- Describe the materials, instruments, datasets, models, or procedures you used. Anyone familiar with the field should be able to reproduce your work from this section.
3
+ This section describes a hypothetical experimental design suitable for an applied
4
+ machine-learning paper.
4
5
 
5
- ### Apparatus and Materials
6
+ ### Data and Problem Setup
6
7
 
7
- Specify hardware, software versions, datasets, and any pre-processing. Cite reused work using `[N]` numeric references (e.g. "we trained the encoder following the procedure of [3]").
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
- ### Procedure
13
+ ### Core Pipeline
10
14
 
11
- Describe the experimental procedure as steps a reader could follow:
15
+ The pipeline has three stages:
12
16
 
13
- 1. State the input data and pre-processing.
14
- 2. State the model / algorithm / instrument settings.
15
- 3. State the evaluation metric(s) and any held-out splits.
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
- ### Notation
21
+ For this draft, preprocessing includes clipping extreme values and aligning all
22
+ continuous fields to SI-style unit notation.
19
23
 
20
- Define notation early. Italicise Roman symbols for quantities and variables, but not Greek symbols. Use a long dash rather than a hyphen for a minus sign. Number equations consecutively:
24
+ ### Equations
21
25
 
22
- $$a + b = \gamma \tag{1}$$
26
+ The main objective is written as:
23
27
 
24
- Refer to equations by `(1)`, not `Eq. (1)` or `equation (1)`, except at the start of a sentence: "Equation (1) shows ...".
28
+ $$
29
+ \mathcal{L}(\theta) = \sum_{i=1}^{N}\left(\hat{y}_{i}(\theta)-y_{i}\right)^2
30
+ $$
25
31
 
26
- ### Reporting Units
32
+ where $\theta$ are trainable parameters and the objective is minimized on the
33
+ training subset.
27
34
 
28
- - Use SI (MKS) as primary units; English units only as secondary (in parentheses).
29
- - Don't mix complete spellings and abbreviations: "Wb/m²" or "webers per square meter", not "webers/m²".
30
- - Use a zero before decimal points: `0.25`, not `.25`.
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
- Present the findings of your study, then interpret what they mean for the research question stated in the introduction.
3
+ ### Benchmark Results
4
4
 
5
- ### Quantitative Results
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
- Use a table to summarise results across conditions. Caption above the table.
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
- <TableCaption>Sample comparison of conditions.</TableCaption>
14
+ The proposed model retains a similar variance profile while improving score
15
+ stability in low-sample conditions.
10
16
 
11
- | Condition | Metric A | Metric B | Notes |
12
- | --- | --- | --- | --- |
13
- | Baseline | 0.71 | 0.65 | Prior work [3] |
14
- | Ours (v1) | 0.78 | 0.69 | This work |
15
- | Ours (v2) | **0.83** | **0.72** | Best |
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
- Cite figures and tables in the text the first time they appear: "Fig. 1 shows the architecture", "Table I summarises the conditions". Use `Fig.` even at the start of a sentence; spell out `Figure` only when it is the subject.
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
- ### Qualitative Discussion
32
+ ### Sensitivity Study
20
33
 
21
- Discuss what the results mean. State the limitations honestly — if the experiment doesn't cover a case you'd like to claim, say so. Avoid the word "essentially" when you mean "approximately" or "effectively".
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
- ### Threats to Validity
38
+ ### Discussion
24
39
 
25
- Briefly list what could invalidate the results: small sample size, single dataset, environmental drift, evaluator bias, etc. Reviewers will look for this section; pre-empt it.
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
- ### Future Work
44
+ ### Limitations
28
45
 
29
- Indicate where the work goes next. Keep this to one short paragraph — over-claiming future scope hurts more than it helps in review.
46
+ This section reports synthetic results only; real-world deployment would require
47
+ cross-domain validation, privacy checks, and a stronger external benchmark suite.