diff --git a/CHANGELOG.md b/CHANGELOG.md
index 620139a5..e98dbe7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
All notable changes to Context are documented here.
+## 0.7.91 - 2026-11-02
+
+- The HTML review report has labelled previous and next buttons (“Previous −” and “Next +”, 上一页 − and 下一页 + in Chinese) at the top right, left of “Copy revision notes”, and the same `-` and `+` keys. They step through the articles under review in navigation order. From the home page, `+` opens the first article and `−` is disabled; `−` on the first article returns home, and `+` is disabled on the last article. The keys are ignored while typing revision notes or when combined with Ctrl, Cmd or Alt, so browser zoom keeps working.
+
## 0.7.90 - 2026-10-28
- A `context update --pending` decision can now revise existing pages whose citations did not change. The update lists them in `scope_pages`; they need no decision, but when an addition or a shared change affects one, include it with an `instruction`. Previously only pages with changed citations could be submitted, so such a page was rejected as not a candidate.
diff --git a/bun.lock b/bun.lock
index c72a487f..a5c3ae2e 100644
--- a/bun.lock
+++ b/bun.lock
@@ -18,7 +18,7 @@
},
"packages/context": {
"name": "@c4a/context",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"yaml": "^2.5.1",
"zod": "^3.23.8",
@@ -26,7 +26,7 @@
},
"packages/context-cli": {
"name": "@c4a/context-cli",
- "version": "0.7.90",
+ "version": "0.7.91",
"bin": {
"context": "dist/cli.js",
},
@@ -76,7 +76,7 @@
},
"packages/core": {
"name": "@c4a/core",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"picomatch": "^4.0.4",
"yaml": "^2.4.5",
@@ -88,7 +88,7 @@
},
"packages/dev-cli": {
"name": "@c4a/dev-cli",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/context": "workspace:*",
"@c4a/core": "workspace:*",
@@ -100,7 +100,7 @@
},
"packages/extract": {
"name": "@c4a/extract",
- "version": "0.7.90",
+ "version": "0.7.91",
"bin": {
"c4a-extract-code": "./dist/bin/c4a-extract-code.js",
},
@@ -115,7 +115,7 @@
},
"packages/extract-contract": {
"name": "@c4a/extract-contract",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"graphql": "^16.14.2",
@@ -125,7 +125,7 @@
},
"packages/extract-go": {
"name": "@c4a/extract-go",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"@c4a/extract": "workspace:*",
@@ -135,7 +135,7 @@
},
"packages/extract-mdx": {
"name": "@c4a/extract-mdx",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"remark-mdx": "^3.1.1",
@@ -147,7 +147,7 @@
},
"packages/extract-proto": {
"name": "@c4a/extract-proto",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"zod": "^3.23.8",
@@ -155,7 +155,7 @@
},
"packages/extract-rush": {
"name": "@c4a/extract-rush",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"typescript": "^5.5.4",
@@ -165,7 +165,7 @@
},
"packages/extract-sql": {
"name": "@c4a/extract-sql",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"node-sql-parser": "^5.4.0",
@@ -174,7 +174,7 @@
},
"packages/extract-style": {
"name": "@c4a/extract-style",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"postcss": "^8.5.26",
@@ -186,7 +186,7 @@
},
"packages/extract-thrift": {
"name": "@c4a/extract-thrift",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"zod": "^3.23.8",
@@ -194,7 +194,7 @@
},
"packages/extract-ts": {
"name": "@c4a/extract-ts",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"@c4a/core": "workspace:*",
"@c4a/extract": "workspace:*",
@@ -205,7 +205,7 @@
},
"packages/tui": {
"name": "@c4a/tui",
- "version": "0.7.90",
+ "version": "0.7.91",
"dependencies": {
"ink": "^5.0.0",
"react": "^18.3.1",
diff --git a/package.json b/package.json
index 44163890..ad9eb788 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "context",
- "version": "0.7.90",
+ "version": "0.7.91",
"packageManager": "bun@1.3.9",
"repository": {
"type": "git",
diff --git a/packages/context-cli/context-workflow/provider.yaml b/packages/context-cli/context-workflow/provider.yaml
index 363ae6a6..688d5af0 100644
--- a/packages/context-cli/context-workflow/provider.yaml
+++ b/packages/context-cli/context-workflow/provider.yaml
@@ -1,6 +1,6 @@
schema: agent-graph.provider.v1
id: c4a/context
-version: 0.7.90
+version: 0.7.91
name: Context workflow
description: Internal work contract for Context knowledge workspaces.
graphs:
diff --git a/packages/context-cli/package.json b/packages/context-cli/package.json
index db6177c7..2e336842 100644
--- a/packages/context-cli/package.json
+++ b/packages/context-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/context-cli",
"description": "Local runtime and Agent integration for traceable knowledge production",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/context-cli/src/__tests__/reviewHtml.integration.test.ts b/packages/context-cli/src/__tests__/reviewHtml.integration.test.ts
index 76f6fffe..eb7e3266 100644
--- a/packages/context-cli/src/__tests__/reviewHtml.integration.test.ts
+++ b/packages/context-cli/src/__tests__/reviewHtml.integration.test.ts
@@ -134,3 +134,38 @@ test("approved pages in changed navigation remain readable without review contro
expect(await reviewSiteBaselineHash(root, [])).not.toBe(baseline);
} finally { await rm(root, { recursive: true, force: true }); }
}, 45000);
+
+test("pager steps through articles in reading order with buttons and +/- keys", async () => {
+ const root = await prepareRevisionKnowledge([]);
+ try {
+ const report = await writeReviewHtml({ projectRoot: root, all: true });
+ const browser = openReport(await readFile(report.path, "utf8"));
+ const evaluate = (code: string) => runInContext(code, browser.runtime);
+ const order = evaluate("readingOrder().map(page => page.id)") as string[];
+ expect(order.length).toBe(evaluate("candidates.length"));
+ expect(new Set(order).size).toBe(order.length);
+ expect(browser.get("next-page").textContent).toBe("Next +");
+ expect(browser.get("previous-page").disabled).toBe(true);
+ expect(browser.get("next-page").disabled).toBe(false);
+ expect(evaluate("pagerKey({ key: '+', target: { tagName: 'BODY' } })")).toBe(true);
+ expect(evaluate("selected")).toBe(order[0]);
+ expect(browser.get("previous-page").disabled).toBe(false);
+ for (const id of order.slice(1)) {
+ evaluate("stepPage(1)");
+ expect(evaluate("selected")).toBe(id);
+ }
+ expect(browser.get("next-page").disabled).toBe(true);
+ expect(evaluate("pagerKey({ key: '+', ctrlKey: true, target: { tagName: 'BODY' } })")).toBe(false);
+ expect(evaluate("pagerKey({ key: '-', target: { tagName: 'TEXTAREA' } })")).toBe(false);
+ for (const id of order.slice(0, -1).reverse()) {
+ evaluate("pagerKey({ key: '-', target: { tagName: 'BODY' } })");
+ expect(evaluate("selected")).toBe(id);
+ }
+ evaluate("stepPage(-1)");
+ expect(evaluate("view")).toBe("home");
+ expect(browser.get("previous-page").disabled).toBe(true);
+ evaluate("view = 'revisions'; render()");
+ expect(browser.get("previous-page").disabled).toBe(true);
+ expect(browser.get("next-page").disabled).toBe(false);
+ } finally { await rm(root, { recursive: true, force: true }); }
+}, 45000);
diff --git a/packages/context-cli/src/project/indexerBaseContracts.ts b/packages/context-cli/src/project/indexerBaseContracts.ts
index 2e5a4fbd..32d36c35 100644
--- a/packages/context-cli/src/project/indexerBaseContracts.ts
+++ b/packages/context-cli/src/project/indexerBaseContracts.ts
@@ -21,7 +21,7 @@ import { bundledMarkdownReaderQuestionContracts } from
"./indexerBaseMarkdownAuthoringCatalog.js";
const BASE_CONTRACT_VERSION = "1.1.0";
-export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.90";
+export const BUNDLED_INDEXER_PARSER_PACKAGE_VERSION = "0.7.91";
const BUNDLED_PARSER_REQUIREMENTS = buildIndexerParserCapabilityRequirements(
BUNDLED_INDEXER_PARSER_PACKAGE_VERSION,
);
diff --git a/packages/context-cli/src/project/reviewHtml.ts b/packages/context-cli/src/project/reviewHtml.ts
index 7887d7f2..d969847f 100644
--- a/packages/context-cli/src/project/reviewHtml.ts
+++ b/packages/context-cli/src/project/reviewHtml.ts
@@ -45,6 +45,7 @@ function renderReviewHtml(model: ReviewSiteModel, diagramScript: string): string
+
diff --git a/packages/context-cli/src/project/reviewSiteClient.ts b/packages/context-cli/src/project/reviewSiteClient.ts
index 7227487a..91174ab2 100644
--- a/packages/context-cli/src/project/reviewSiteClient.ts
+++ b/packages/context-cli/src/project/reviewSiteClient.ts
@@ -18,6 +18,8 @@ const words = {
previous: ['查看旧文本', 'Previous text'], removed: ['移除或替换的旧内容', 'Removed or replaced content'],
removedNav: ['移除的目录条目', 'Removed navigation entries'], unplaced: ['待落位文章', 'Unplaced articles'],
sources: ['来源引用', 'Sources'], feedbackTag: ['修订', 'Notes'],
+ previousPage: ['上一页 −', 'Previous −'], nextPage: ['下一页 +', 'Next +'],
+ previousHint: ['上一页(快捷键 -)', 'Previous article (key -)'], nextHint: ['下一页(快捷键 +)', 'Next article (key +)'],
};
let language = 'zh';
const t = key => words[key]?.[language === 'zh' ? 0 : 1] || key;
@@ -101,6 +103,58 @@ function rootFor(pageId) {
return node?.key || null;
}
+/** Candidate articles in navigation order, then any the navigation does not place. */
+function readingOrder() {
+ const ordered = [];
+ const seen = new Set();
+ function walk(parent, visited = new Set()) {
+ for (const node of DATA.nodes.filter(item => (item.parent || null) === parent).sort((a, b) => a.order - b.order)) {
+ if (visited.has(node.key)) continue;
+ visited.add(node.key);
+ const page = pagesById.get(node.page);
+ if (page?.candidate_id && !seen.has(page.id)) { seen.add(page.id); ordered.push(page); }
+ walk(node.key, visited);
+ }
+ }
+ walk(null);
+ return [...ordered, ...candidates.filter(page => !seen.has(page.id))];
+}
+
+/** Home and the revision list come before the first article; previous from the first article returns home. */
+function pagerTargets() {
+ const order = readingOrder();
+ const index = view === 'article' ? order.findIndex(page => page.id === selected) : -1;
+ return { previous: index >= 0 ? (index > 0 ? order[index - 1].id : 'home') : null, next: order[index + 1]?.id || null };
+}
+
+function stepPage(direction) {
+ const target = pagerTargets()[direction < 0 ? 'previous' : 'next'];
+ if (target === 'home') showHome();
+ else if (target) showPage(target);
+}
+
+function updatePager() {
+ const targets = pagerTargets();
+ $('previous-page').disabled = !targets.previous;
+ $('next-page').disabled = !targets.next;
+ for (const [id, label, hint] of [['previous-page', 'previousPage', 'previousHint'], ['next-page', 'nextPage', 'nextHint']]) {
+ $(id).textContent = t(label);
+ $(id).title = t(hint);
+ }
+}
+
+function pagerKey(event) {
+ if (event.defaultPrevented || event.ctrlKey || event.metaKey || event.altKey || event.isComposing) return false;
+ const target = event.target;
+ if (target?.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target?.tagName)) return false;
+ if ($('copy-dialog').open) return false;
+ const direction = ['+', '='].includes(event.key) || event.code === 'NumpadAdd' ? 1 : ['-', '_'].includes(event.key) || event.code === 'NumpadSubtract' ? -1 : 0;
+ if (!direction) return false;
+ event.preventDefault?.();
+ stepPage(direction);
+ return true;
+}
+
function updateCounts() {
const revised = revisedPages().length;
$('counts').innerHTML = \`\${countText(t('read'), read.size)}/\`
@@ -190,6 +244,7 @@ function render() {
$('article').classList.toggle('review-new-page', view === 'article' && page?.change === 'new');
renderNav();
updateCounts();
+ updatePager();
if (view === 'home') renderHome();
else if (view === 'revisions') renderRevisions();
else if (page) {
@@ -318,7 +373,11 @@ $('clear-note').onclick = () => {
$('revision-note').dispatchEvent(new Event('input', { bubbles: true }));
$('revision-note').focus();
};
-$('home').onclick = () => { selected = null; active = null; view = 'home'; history.replaceState(null, '', location.pathname + location.search); render(); window.scrollTo(0, 0); };
+function showHome() { selected = null; active = null; view = 'home'; history.replaceState(null, '', location.pathname + location.search); render(); window.scrollTo(0, 0); }
+$('home').onclick = showHome;
+$('previous-page').onclick = () => stepPage(-1);
+$('next-page').onclick = () => stepPage(1);
+document.addEventListener('keydown', pagerKey);
$('theme').onclick = () => { document.body.classList.toggle('dark'); persist(); render(); };
$('language').onclick = () => { language = language === 'zh' ? 'en' : 'zh'; persist(); render(); };
$('copy-notes').onclick = copyNotes;
diff --git a/packages/context-cli/src/project/reviewSiteStyles.ts b/packages/context-cli/src/project/reviewSiteStyles.ts
index e4bf65d8..84ea69a1 100644
--- a/packages/context-cli/src/project/reviewSiteStyles.ts
+++ b/packages/context-cli/src/project/reviewSiteStyles.ts
@@ -31,6 +31,7 @@ header nav { scrollbar-width: none; }
header nav::-webkit-scrollbar { display: none; }
header nav button { padding: 0 11px; }
.tools { flex-shrink: 0; gap: 16px; }
+.pager { display: flex; flex-shrink: 0; gap: 6px; }
.reading-progress { min-width: 225px; }
#counts { display: flex; align-items: center; justify-content: space-between; gap: 8px; white-space: nowrap; font-size: 12px; color: var(--muted); }
#counts b { font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
diff --git a/packages/context/package.json b/packages/context/package.json
index f42735d1..dd346033 100644
--- a/packages/context/package.json
+++ b/packages/context/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/context",
"description": "Declarative SDK for Context knowledge sources, workflows, review, and package outputs",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/core/package.json b/packages/core/package.json
index 45f8f311..4f43af38 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/core",
"description": "Shared extraction types, schemas, and utilities for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/dev-cli/package.json b/packages/dev-cli/package.json
index 573e9251..9ffc2cdb 100644
--- a/packages/dev-cli/package.json
+++ b/packages/dev-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/dev-cli",
"description": "Developer menu for Context build, verification, link, and release preparation",
- "version": "0.7.90",
+ "version": "0.7.91",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/packages/extract-contract/package.json b/packages/extract-contract/package.json
index abd468cd..7edaceee 100644
--- a/packages/extract-contract/package.json
+++ b/packages/extract-contract/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-contract",
"description": "OpenAPI and GraphQL contract catalog adapter for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-go/package.json b/packages/extract-go/package.json
index 259766f3..97dfb677 100644
--- a/packages/extract-go/package.json
+++ b/packages/extract-go/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-go",
"description": "Go extraction plugin and structural index for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-mdx/package.json b/packages/extract-mdx/package.json
index 847417d9..0739b34e 100644
--- a/packages/extract-mdx/package.json
+++ b/packages/extract-mdx/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-mdx",
"description": "MDX component and example catalog bridge for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-proto/package.json b/packages/extract-proto/package.json
index 225f8c4a..0ceda99e 100644
--- a/packages/extract-proto/package.json
+++ b/packages/extract-proto/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-proto",
"description": "Protocol Buffers IDL catalog parser for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-rush/package.json b/packages/extract-rush/package.json
index 9df5c5cf..01374e8c 100644
--- a/packages/extract-rush/package.json
+++ b/packages/extract-rush/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-rush",
"description": "Rush workspace structural index for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-sql/package.json b/packages/extract-sql/package.json
index 8d75f2a2..5bcd7a4d 100644
--- a/packages/extract-sql/package.json
+++ b/packages/extract-sql/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-sql",
"description": "Dialect-bound lightweight SQL evidence adapter for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-style/package.json b/packages/extract-style/package.json
index 551efe30..dccd6cbf 100644
--- a/packages/extract-style/package.json
+++ b/packages/extract-style/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-style",
"description": "Lightweight CSS and SCSS evidence adapter for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-thrift/package.json b/packages/extract-thrift/package.json
index 98bde51d..029aabb1 100644
--- a/packages/extract-thrift/package.json
+++ b/packages/extract-thrift/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-thrift",
"description": "Apache Thrift IDL catalog parser for Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract-ts/package.json b/packages/extract-ts/package.json
index 48db2bb5..79b3e321 100644
--- a/packages/extract-ts/package.json
+++ b/packages/extract-ts/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract-ts",
"description": "TypeScript and JavaScript extraction plugin for the Context ExtractionResult v2 contract",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/extract/package.json b/packages/extract/package.json
index 2fe693f4..ef4387eb 100644
--- a/packages/extract/package.json
+++ b/packages/extract/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/extract",
"description": "Language-plugin framework and repository runner for Context code evidence",
- "version": "0.7.90",
+ "version": "0.7.91",
"type": "module",
"license": "MIT",
"engines": {
diff --git a/packages/tui/package.json b/packages/tui/package.json
index d7e850fb..177621a2 100644
--- a/packages/tui/package.json
+++ b/packages/tui/package.json
@@ -1,7 +1,7 @@
{
"name": "@c4a/tui",
"description": "Shared terminal UI components (Ink + React) for Context development tools",
- "version": "0.7.90",
+ "version": "0.7.91",
"private": true,
"type": "module",
"license": "MIT",
diff --git a/plugins/context/repo-install/claude/.claude-plugin/plugin.json b/plugins/context/repo-install/claude/.claude-plugin/plugin.json
index f0648b48..0d057f0e 100644
--- a/plugins/context/repo-install/claude/.claude-plugin/plugin.json
+++ b/plugins/context/repo-install/claude/.claude-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "c4a",
"description": "Start or continue a project-local knowledge workspace through one graph-routed entry.",
- "version": "0.7.90",
+ "version": "0.7.91",
"author": {
"name": "c4a"
},
diff --git a/plugins/context/repo-install/codex/.codex-plugin/plugin.json b/plugins/context/repo-install/codex/.codex-plugin/plugin.json
index abcefbf5..ff940b0c 100644
--- a/plugins/context/repo-install/codex/.codex-plugin/plugin.json
+++ b/plugins/context/repo-install/codex/.codex-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "c4a",
- "version": "0.7.90",
+ "version": "0.7.91",
"description": "Start or continue a project-local knowledge workspace through one graph-routed entry.",
"author": { "name": "c4a" },
"homepage": "https://github.com/context4ai/c4a",
@@ -10,7 +10,7 @@
"skills": "./skills/",
"interface": {
"displayName": "C4A Context",
- "shortDescription": "Initialize and advance a local, source-linked project knowledge workspace.\nv0.7.90",
+ "shortDescription": "Initialize and advance a local, source-linked project knowledge workspace.\nv0.7.91",
"longDescription": "Create a Context workspace and use agent-guided next steps to register sources, run extraction, review candidates, build package outputs, and verify health without silently mutating source repositories.",
"developerName": "c4a",
"category": "Productivity",
diff --git a/plugins/context/repo-install/cursor/.cursor-plugin/plugin.json b/plugins/context/repo-install/cursor/.cursor-plugin/plugin.json
index c0404be3..549a07ed 100644
--- a/plugins/context/repo-install/cursor/.cursor-plugin/plugin.json
+++ b/plugins/context/repo-install/cursor/.cursor-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "c4a",
"displayName": "C4A Context",
- "version": "0.7.90",
+ "version": "0.7.91",
"description": "Start or continue a project-local knowledge workspace through one graph-routed entry.",
"author": {
"name": "Context4AI",