Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 15 additions & 15 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "context",
"version": "0.7.90",
"version": "0.7.91",
"packageManager": "bun@1.3.9",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/context-workflow/provider.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
2 changes: 1 addition & 1 deletion packages/context-cli/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
35 changes: 35 additions & 0 deletions packages/context-cli/src/__tests__/reviewHtml.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
2 changes: 1 addition & 1 deletion packages/context-cli/src/project/indexerBaseContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
1 change: 1 addition & 0 deletions packages/context-cli/src/project/reviewHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ function renderReviewHtml(model: ReviewSiteModel, diagramScript: string): string
<div id="counts" aria-label="阅读进度"></div>
<div class="read-track" role="progressbar" aria-label="本轮阅读进度" aria-valuemin="0" aria-valuenow="0"><span id="read-fill"></span></div>
</div>
<div class="pager" role="group" aria-label="文章翻页"><button class="btn" id="previous-page" title="上一页(快捷键 -)" disabled>上一页 −</button><button class="btn" id="next-page" title="下一页(快捷键 +)">下一页 +</button></div>
<button class="btn primary" id="copy-notes">复制修订意见</button>
</div>
<button class="btn" id="theme" aria-label="切换深色主题" title="切换深色主题">◐</button>
Expand Down
61 changes: 60 additions & 1 deletion packages/context-cli/src/project/reviewSiteClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = \`<span>\${countText(t('read'), read.size)}</span><span class="separator">/</span>\`
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/context-cli/src/project/reviewSiteStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
2 changes: 1 addition & 1 deletion packages/context/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/dev-cli/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-contract/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-go/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-mdx/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-proto/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/extract-rush/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
Loading
Loading