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",