@compn3rd/pi-web-code-review 0.0.0-stage → 0.1.2
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/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +30 -2
- package/dist/browser/index.js +3277 -0
- package/dist/protocol.js +46 -0
- package/dist/server.js +75 -0
- package/docs/compatibility.md +59 -0
- package/docs/composer-design.md +54 -0
- package/docs/development.md +30 -0
- package/docs/screenshots/review-after.png +0 -0
- package/docs/screenshots/review-before.png +0 -0
- package/docs/usage.md +96 -0
- package/package.json +60 -6
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function isRevision(value) {
|
|
2
|
+
return typeof value === "string" && /^[a-z0-9_][a-z0-9_./~^@{}-]{0,255}$/iu.test(value) && !value.includes("..");
|
|
3
|
+
}
|
|
4
|
+
export function isCommitId(value) {
|
|
5
|
+
return typeof value === "string" && /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(value);
|
|
6
|
+
}
|
|
7
|
+
export function isComparison(value) {
|
|
8
|
+
return record(value) && isRevision(value["baseRef"]) && isRevision(value["headRef"])
|
|
9
|
+
&& (value["mode"] === "merge-base" || value["mode"] === "direct")
|
|
10
|
+
&& isCommitId(value["base"]) && isCommitId(value["head"]);
|
|
11
|
+
}
|
|
12
|
+
export function parseComparison(value) {
|
|
13
|
+
if (!isComparison(value))
|
|
14
|
+
throw new Error("Invalid Git comparison");
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
export function comparisonKey(comparison) {
|
|
18
|
+
return comparison === undefined ? "" : JSON.stringify([comparison.baseRef, comparison.headRef, comparison.mode, comparison.base, comparison.head]);
|
|
19
|
+
}
|
|
20
|
+
export const MAX_TEXT_LENGTH = 200_000;
|
|
21
|
+
export const MAX_FILES = 2_000;
|
|
22
|
+
export function isGitSource(value) {
|
|
23
|
+
return value === "git-staged" || value === "git-unstaged" || value === "git-compare";
|
|
24
|
+
}
|
|
25
|
+
/** Relative, unambiguous paths. Git callers must also disable pathspec magic with --literal-pathspecs. */
|
|
26
|
+
export function isWorkspacePath(value) {
|
|
27
|
+
return typeof value === "string" && value.length > 0 && value.length <= 4096
|
|
28
|
+
&& !/^[a-z]:/iu.test(value) && !value.startsWith("/") && !value.includes("\\")
|
|
29
|
+
// eslint-disable-next-line no-control-regex -- Never pass controls to Git or persist ambiguous labels.
|
|
30
|
+
&& !/[\u0000-\u001f\u007f]/u.test(value)
|
|
31
|
+
&& value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
32
|
+
}
|
|
33
|
+
export function record(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
export function parsePaths(value) {
|
|
37
|
+
if (!Array.isArray(value) || value.length > MAX_FILES || !value.every(isWorkspacePath)) {
|
|
38
|
+
throw new Error("Invalid or oversized Git file list");
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export function parseDiff(value) {
|
|
43
|
+
if (typeof value !== "string" || value.length > MAX_TEXT_LENGTH)
|
|
44
|
+
throw new Error("Invalid or oversized Git diff");
|
|
45
|
+
return value;
|
|
46
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { isCommitId, isGitSource, isRevision, isWorkspacePath, MAX_FILES, MAX_TEXT_LENGTH, parseComparison, record } from "./protocol.js";
|
|
2
|
+
// Do not inherit the repository/index of the process hosting the daemon.
|
|
3
|
+
const gitEnvironment = [
|
|
4
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES", "GIT_COMMON_DIR", "GIT_DIR", "GIT_INDEX_FILE",
|
|
5
|
+
"GIT_OBJECT_DIRECTORY", "GIT_PREFIX", "GIT_QUARANTINE_PATH", "GIT_WORK_TREE",
|
|
6
|
+
];
|
|
7
|
+
export async function requestGit(execFile, request) {
|
|
8
|
+
const { input, operation, workspace, signal } = request;
|
|
9
|
+
if (!record(input) || !isGitSource(input["source"]))
|
|
10
|
+
throw new Error("Invalid Git review source");
|
|
11
|
+
if (operation !== "changes" && operation !== "diff" && operation !== "compare")
|
|
12
|
+
throw new Error("Unknown review operation");
|
|
13
|
+
const run = async (args) => {
|
|
14
|
+
const result = await execFile({ file: "git", args: ["--literal-pathspecs", "-C", workspace.path, ...args], cwd: workspace.path, env: { GIT_OPTIONAL_LOCKS: "0" }, unsetEnv: gitEnvironment, signal, timeoutMs: 10_000 });
|
|
15
|
+
if (result.exitCode !== 0 || result.signal !== null)
|
|
16
|
+
throw new Error(`Git review failed: ${result.stderr.trim() || `exit ${String(result.exitCode)}`}`);
|
|
17
|
+
if (result.stdoutTruncated || result.stderrTruncated)
|
|
18
|
+
throw new Error("Git output exceeds the host limit; narrow the review");
|
|
19
|
+
return result.stdout;
|
|
20
|
+
};
|
|
21
|
+
if (operation === "compare") {
|
|
22
|
+
if (input["source"] !== "git-compare" || !isRevision(input["baseRef"]) || !isRevision(input["headRef"])
|
|
23
|
+
|| (input["mode"] !== "merge-base" && input["mode"] !== "direct"))
|
|
24
|
+
throw new Error("Invalid comparison revisions or mode");
|
|
25
|
+
const baseRef = input["baseRef"];
|
|
26
|
+
const headRef = input["headRef"];
|
|
27
|
+
const mode = input["mode"];
|
|
28
|
+
const resolve = async (ref) => {
|
|
29
|
+
const commit = (await run(["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`])).trim();
|
|
30
|
+
if (!isCommitId(commit))
|
|
31
|
+
throw new Error("Git returned an invalid commit ID");
|
|
32
|
+
return commit;
|
|
33
|
+
};
|
|
34
|
+
const baseTip = await resolve(baseRef);
|
|
35
|
+
const head = await resolve(headRef);
|
|
36
|
+
const base = mode === "direct" ? baseTip : (await run(["merge-base", baseTip, head])).trim();
|
|
37
|
+
if (!isCommitId(base))
|
|
38
|
+
throw new Error("Git returned an invalid merge base");
|
|
39
|
+
return { baseRef, headRef, mode, base, head };
|
|
40
|
+
}
|
|
41
|
+
const comparison = input["source"] === "git-compare" ? parseComparison(input["comparison"]) : undefined;
|
|
42
|
+
const path = input["path"];
|
|
43
|
+
if (operation === "diff" && !isWorkspacePath(path))
|
|
44
|
+
throw new Error("Invalid workspace-relative review path");
|
|
45
|
+
const args = ["diff", "--no-ext-diff", "--no-textconv", "--no-renames", "--no-color", "--submodule=short", "--relative"];
|
|
46
|
+
if (input["source"] === "git-staged")
|
|
47
|
+
args.push("--cached");
|
|
48
|
+
if (operation === "changes")
|
|
49
|
+
args.push("--name-only", "-z");
|
|
50
|
+
else
|
|
51
|
+
args.push("--unified=3");
|
|
52
|
+
if (comparison !== undefined)
|
|
53
|
+
args.push(comparison.base, comparison.head);
|
|
54
|
+
args.push("--");
|
|
55
|
+
if (operation === "diff" && typeof path === "string")
|
|
56
|
+
args.push(path);
|
|
57
|
+
const output = await run(args);
|
|
58
|
+
if (operation === "changes") {
|
|
59
|
+
const paths = output.split("\0").filter((entry) => entry !== "");
|
|
60
|
+
if (paths.length > MAX_FILES)
|
|
61
|
+
throw new Error("Too many changed files; narrow the review");
|
|
62
|
+
if (!paths.every(isWorkspacePath))
|
|
63
|
+
throw new Error("Git returned a filename this review plugin cannot safely address");
|
|
64
|
+
return paths;
|
|
65
|
+
}
|
|
66
|
+
if (output.length > MAX_TEXT_LENGTH)
|
|
67
|
+
throw new Error("Diff is too large to review inline");
|
|
68
|
+
return output;
|
|
69
|
+
}
|
|
70
|
+
const plugin = {
|
|
71
|
+
apiVersion: 3,
|
|
72
|
+
name: "Code Review",
|
|
73
|
+
activate: ({ execFile }) => ({ peer: { request: (request) => requestGit(execFile, request) } }),
|
|
74
|
+
};
|
|
75
|
+
export default plugin;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Latest-release compatibility check
|
|
2
|
+
|
|
3
|
+
## Target
|
|
4
|
+
|
|
5
|
+
Checked npm's latest published `@jmfederico/pi-web`: **1.202610.3**, source revision `29f0ace6d955a458a947bb0f03673fa069afb8da`.
|
|
6
|
+
|
|
7
|
+
References:
|
|
8
|
+
|
|
9
|
+
- [Release changelog](https://github.com/jmfederico/pi-web/blob/29f0ace6d955a458a947bb0f03673fa069afb8da/CHANGELOG.md)
|
|
10
|
+
- [Version-matched plugin guide](https://github.com/jmfederico/pi-web/blob/29f0ace6d955a458a947bb0f03673fa069afb8da/docs/plugins.md)
|
|
11
|
+
- Public browser/server declarations in the installed npm package, resolved through `@jmfederico/pi-web/plugin-api` and `@jmfederico/pi-web/server-plugin-api`.
|
|
12
|
+
|
|
13
|
+
## Integration findings
|
|
14
|
+
|
|
15
|
+
No production API migration was required for this release:
|
|
16
|
+
|
|
17
|
+
- Composer chips shipped in **1.202610.2**. `PluginPromptChip`, optional `setChip`/`removeChip`, silent programmatic withdrawal, and `onRemove("user" | "submitted")` match the plugin's implementation. Submission means server acceptance, not agent completion; failed sends retain chips.
|
|
18
|
+
- Browser API **v4** and server API **v3** are unchanged. Workspace file helpers, invalidation, package peers, and bounded `execFile` with `unsetEnv` and operation signals remain supported.
|
|
19
|
+
- Removed panel aliases do not affect this plugin: it declares no `routeAliases`/`navigationAliases` and opens its panel with the runtime-qualified contribution ID.
|
|
20
|
+
- Full, case-sensitive session IDs are now required. The plugin already uses the full `selectedSession.id`; it does not generate abbreviated session links.
|
|
21
|
+
- New selection subscriptions, application panels, metadata, and message actions are optional additions, not required adaptations for the existing Review panel.
|
|
22
|
+
- PI WEB's newer Pi SDK dependencies do not require a companion-extension migration here: this plugin uses no Pi SDK imports or companion extension.
|
|
23
|
+
- The standalone browser/server manifest follows the published example, keeping server code outside `browserRoot`. Node's declared minimum remains `>=22.19.0`.
|
|
24
|
+
|
|
25
|
+
The supported dependency range is `>=1.202610.3`; the lockfile records a concrete resolved version. Compatibility was checked against `1.202610.3`, not every later version. One test access was made null-safe because panel `state` is optional in the public contract. No production API migration was needed. The subsequent `0.1.1` patch adds the Review icon, lint cleanup, and branch/commit comparisons described in the [changelog](../CHANGELOG.md). Revision comparison uses only the existing scoped package-peer API and read-only Git runner; no additional host API is required.
|
|
26
|
+
|
|
27
|
+
## UI patch 0.1.2
|
|
28
|
+
|
|
29
|
+
The redesigned panel uses the same host APIs, storage format, and server operations as 0.1.1. Standalone Edge screenshots show the old and new components with identical sample content ([before](screenshots/review-before.png), [after](screenshots/review-after.png)). Browser rendering was checked separately from the DOM tests; this does not establish live-host behavior or narrow-panel acceptance.
|
|
30
|
+
|
|
31
|
+
## Validation
|
|
32
|
+
|
|
33
|
+
After `npm ci --ignore-scripts`:
|
|
34
|
+
|
|
35
|
+
| Check | Result |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `npm run typecheck` | Passed |
|
|
38
|
+
| `npm run lint` | Passed |
|
|
39
|
+
| `npm test` | Passed: 6 files, 84 tests |
|
|
40
|
+
| `npm run build` | Passed |
|
|
41
|
+
| `npm run check:artifacts` | Passed |
|
|
42
|
+
| `npm pack` and smoke check of extracted package | Passed |
|
|
43
|
+
| `npm run verify` | Passed |
|
|
44
|
+
| Live-host send/acceptance, remote machines, real browser storage failures | Not exercised |
|
|
45
|
+
|
|
46
|
+
The artifact harness activates the built browser/server entries with mocked host contexts and a DOM/IndexedDB harness. Passing it does not establish live host registration or end-to-end send behavior.
|
|
47
|
+
|
|
48
|
+
### Lint cleanup
|
|
49
|
+
|
|
50
|
+
All 38 original lint findings are resolved. Async disposal and callback-mutated ownership checks are preserved through live-state reads. Storage rejects with Error instances (retaining non-Error exceptions as causes), and tests use runtime-checked panel fixtures, real event/request objects, and explicit mock spies. The deliberately unbound IndexedDB method has a local lint exemption because its receiver is explicitly supplied with `.call`.
|
|
51
|
+
|
|
52
|
+
## Remaining release work
|
|
53
|
+
|
|
54
|
+
1. Automated verification and packed smoke checks pass after lint cleanup; storage/ownership guards remain in place.
|
|
55
|
+
2. Validate comparison controls and saved comparison comments on a live host, including moving branches and upgrading all older tabs. Complete the separate live-host checklist in [the release gate](composer-design.md#release-gate), especially successful/failed/chips-only sends, steer/follow-up, replacement during Send, selection changes, two-tab acknowledgements, and remote scoping. The newest host also adds pending-message controls; confirm locally queued messages do not clear saved feedback before server admission.
|
|
56
|
+
3. The declared host range is `>=1.202610.3`. Only `1.202610.3` was checked here; later versions are allowed but have not been independently verified.
|
|
57
|
+
4. Publication is enabled for the `0.1.2` patch release as `@compn3rd/pi-web-code-review`. The remaining validation is follow-up work; discovered defects will be addressed in patch releases.
|
|
58
|
+
|
|
59
|
+
Automated verification did not install the plugin into a live PI WEB instance or restart a daemon. Live-host testing remains separate from package publication.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Composer-chip design and release gate
|
|
2
|
+
|
|
3
|
+
## Target and status
|
|
4
|
+
|
|
5
|
+
Target the public API on `jmfederico/pi-web` main at `830dc179`, including composer chips introduced in `02d24ec6`. The source imports `PluginPromptChip` directly from `@jmfederico/pi-web/plugin-api`; it does not recreate the missing API through a local shim or access private host code.
|
|
6
|
+
|
|
7
|
+
The development dependency accepts PI WEB `>=1.202610.3`; compatibility was checked against published `1.202610.3`. Composer chips shipped in `1.202610.2`; the `1.202610.3` public declarations match this design. No speculative version, Git dependency, or local shim is used. Typechecking, all 84 tests, builds, and unpacked/packed artifact smoke checks pass. Full automated verification passes. See [compatibility results](compatibility.md).
|
|
8
|
+
|
|
9
|
+
The `0.1.2` patch release is enabled for publication. Live-host checks remain follow-up work rather than publication gates. Live-host runtime testing and installation are still deferred; the checks above validate declarations and standalone artifacts, not end-to-end send admission in a running host.
|
|
10
|
+
|
|
11
|
+
Reference: [upstream composer documentation](https://github.com/jmfederico/pi-web/blob/830dc179/docs/plugins.md#attach-text-context-to-a-message).
|
|
12
|
+
|
|
13
|
+
## Ownership
|
|
14
|
+
|
|
15
|
+
- **ReviewStore** owns durable comments in browser IndexedDB transactions (with one-time, backup-retaining localStorage migration), keyed by machine/project/workspace. A comment is not deleted merely because it was attached to a conversation.
|
|
16
|
+
- **ReviewChips** owns staging and callback bookkeeping for the plugin activation, independent of whether the Review panel is mounted. It retains the exact conversation-bound prompt facade used for each attachment, never a live-selection redirect.
|
|
17
|
+
- **PI WEB** owns composer chips, send admission, failed-send retention, and callbacks. Staging happens only from the user's **Attach review to composer** action, never from render.
|
|
18
|
+
- **ReviewPanel** owns file loading and unsaved edits. It delegates composer lifecycle decisions instead of reaching into the host DOM or sending messages itself.
|
|
19
|
+
|
|
20
|
+
## One snapshot per workspace/conversation
|
|
21
|
+
|
|
22
|
+
Attach creates one labeled `Review (N)` chip containing the sorted Markdown for all saved comments in the workspace. The plugin-local chip id is stable for that workspace; the host additionally scopes it by plugin, machine, and conversation. Reattaching replaces the same chip rather than adding duplicates. Individual comments remain editable/removable in the Review panel.
|
|
23
|
+
|
|
24
|
+
A ready, unarchived conversation in the panel's workspace is required. The facade is already bound to that conversation: no navigation, selection-service shim, cursor manipulation, or clipboard fallback is needed. Hosts without `setChip`/`removeChip` produce an explicit unsupported-host error rather than reverting to the interim insertion implementation.
|
|
25
|
+
|
|
26
|
+
## Lifecycle
|
|
27
|
+
|
|
28
|
+
| Event | Intended behavior |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| Attach | Read current saved comments; stage a detached snapshot; retain durable data. |
|
|
31
|
+
| Panel closes or selection changes | Host retains the chip in its original conversation; the activation retains its callback. |
|
|
32
|
+
| User removes the composer chip | Detach only; keep saved comments for editing or reattachment. |
|
|
33
|
+
| Send fails or is queued only locally | No submitted callback; chip and saved comments remain available. |
|
|
34
|
+
| Server accepts the prompt | `onRemove("submitted")` clears only comments whose stored fields still equal the submitted snapshot. Later edits and new comments survive. |
|
|
35
|
+
| A chip is replaced while Send is in flight | An older callback must not retire the replacement or clear the saved feedback for that newer attachment. |
|
|
36
|
+
| Save or remove a comment in the Review panel | Withdraw this workspace's staged copies before mutation. Attach the updated review explicitly. If withdrawal fails, block the mutation and show the error. |
|
|
37
|
+
| Another tab changes saved review data | Withdraw this activation's copies, even while the panel is closed, and ask for reattachment. A request already in flight may still be accepted; its callback compares against current storage. |
|
|
38
|
+
| Clearing submitted data fails | Preserve saved data, report the problem in the panel, and throw for the host's attributed callback logging. The user checks the conversation before reattaching. |
|
|
39
|
+
| Browser reload | Host chips are gone; saved comments remain. Open the target conversation and attach again with a fresh facade/callback. |
|
|
40
|
+
| Plugin disposal | Close the notification channel/database and attempt silent chip withdrawal; suppress obsolete asynchronous UI work. Already-started durable writes may complete. |
|
|
41
|
+
|
|
42
|
+
Acceptance means prompt admission by the server, not that the assistant completed its response. There is no automatic resend or inference from the disappearance of prompt text. A submitted callback always uses its captured workspace and snapshot, even after navigation. Attachments in different conversations are independent snapshots; intentionally attaching to multiple conversations can send the feedback more than once.
|
|
43
|
+
|
|
44
|
+
## Release gate
|
|
45
|
+
|
|
46
|
+
Current progress against published PI WEB `1.202610.3`:
|
|
47
|
+
|
|
48
|
+
1. Confirm its public declarations include `PluginPromptChip` and the documented `setChip`/`removeChip` lifecycle. Declare the supported minimum as `>=1.202610.3` and retain a concrete resolved version in the lockfile.
|
|
49
|
+
2. Re-read the published contract and reconcile any differences from the recorded main revision. Do not assume main's design is frozen.
|
|
50
|
+
3. Run `npm run verify` at the package root, followed by an actual packed-package smoke check. All 82 unit/DOM cases have passed, as have unpacked and packed artifact smoke checks. All 38 lint findings have been resolved and full `verify` now passes. The dev-only `fake-indexeddb` factory exercises the actual adapter on two connections, transaction aborts, migration precedence/empty markers, and open retry/late-success cleanup. Dependencies were installed with `--ignore-scripts`; no host installation or daemon restart was performed.
|
|
51
|
+
4. In a separate test installation, exercise successful and failed sends, chips-only messages, steer/follow-up, selection changes, repeat attachment during an in-flight send, local edits/deletes, simultaneous two-tab saves/acknowledgements, failed/blocked/corrupt migration, retained legacy backups, persistent storage/acknowledgement warnings across remounts, async navigation/disposal, LAN HTTP without Web Locks, BroadcastChannel failure, Git with `diff.submodule=diff`, reload/restaging, disabled-plugin cleanup, and remote-machine scoping. Confirm the server-accepted callback clears only the correct saved feedback. Do not restart the developer's active session daemon as part of this check.
|
|
52
|
+
5. Publication is enabled for version `0.1.2` as `@compn3rd/pi-web-code-review`, with PI WEB `>=1.202610.3` supported. Keep outstanding validation documented and address discovered defects in patch releases.
|
|
53
|
+
|
|
54
|
+
There is no scheduled release watcher or automatic installation/restart associated with this gate.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Developing the standalone plugin
|
|
2
|
+
|
|
3
|
+
All package source, tests, and configuration live at this repository's root. The package is independent of PI WEB's bundled plugins, test command, and npm release; no PI WEB source checkout is required.
|
|
4
|
+
|
|
5
|
+
This repository starts from the plugin snapshot in [CompN3rd/pi-web at `d0c7a212`](https://github.com/CompN3rd/pi-web/tree/d0c7a212/plugins/code-review). Earlier development history remains there; PI WEB host code is not included here.
|
|
6
|
+
|
|
7
|
+
## Verification status
|
|
8
|
+
|
|
9
|
+
The development dependency accepts PI WEB `>=1.202610.3`, which includes the public composer-chip API. The lockfile records the concrete version used for reproducible development. There are no local replacement declarations. Typechecking, all 84 tests, the build, and unpacked/packed artifact smoke checks pass. Full `verify` passes; live-host testing is still pending. See [compatibility results](compatibility.md).
|
|
10
|
+
|
|
11
|
+
The `0.1.2` patch release is enabled for publication with live-host testing tracked as follow-up work in the [validation checklist](composer-design.md#release-gate). Reproduce validation with:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm ci --ignore-scripts
|
|
15
|
+
npm run verify
|
|
16
|
+
npm pack --dry-run
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`verify` runs strict typechecking, typed ESLint, Vitest, the standalone build, and browser/server artifact smoke checks. `npm run check:artifacts` consumes already-built output without rebuilding; `node scripts/check-artifacts.mjs /path/to/extracted/package` checks a packed copy using this checkout's DOM harness.
|
|
20
|
+
|
|
21
|
+
Prepared cases cover actual adapter serialization on two IndexedDB connections, aborted-write durability, one-time migration and empty migration markers, legacy-backup retention, corrupt/failed migration, failed-open retry and late blocked-open cleanup, captured chip ownership, stale async reads, disposal, user removal versus server acceptance, replacement during Send, unchanged-snapshot deletion, cross-tab withdrawal/badge refresh, persistent storage/acknowledgement warnings across remounts, and Git's `--submodule=short` override. `fake-indexeddb` is dev-only and injected through the native `IDBFactory` boundary; DOM tests retain the per-file happy-dom harness. These tests and the existing source-coordinate, Markdown, Git, and storage cases have passed against the released declarations. Mock/DOM coverage does not replace live-host acceptance testing.
|
|
22
|
+
|
|
23
|
+
## Boundaries
|
|
24
|
+
|
|
25
|
+
- The frontend stores workspace-scoped comments and delegates staging/acknowledgement to `ReviewChips`. Only public `prompt.setChip`/`prompt.removeChip` methods are used for handoff; prompt insertion and the provisional selection-service shim have been removed.
|
|
26
|
+
- The backend accepts only read-only `compare`, `changes`, and `diff` operations, runs argv-based Git through the host's bounded executor with the request signal, and uses the host-resolved workspace. Literal pathspecs, disabled external diff/textconv drivers, forced short submodule diffs, and output/path bounds keep this surface narrow.
|
|
27
|
+
- The Markdown renderer is adapted from PI WEB's MIT-licensed Captain's Log example. It renders known lexer tokens through Lit text bindings, not unsafe HTML.
|
|
28
|
+
- Browser dependencies are bundled under `dist/browser`; server JavaScript remains outside that public directory. The whole plugin can be built separately from the host after the dependency gate is satisfied.
|
|
29
|
+
|
|
30
|
+
Version `0.1.2` is packaged for public npm distribution as `@compn3rd/pi-web-code-review`. Release publication is separate from host installation and activation. Do not publish the PI WEB host to ship this plugin.
|
|
Binary file
|
|
Binary file
|
package/docs/usage.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Code Review workflow
|
|
2
|
+
|
|
3
|
+
Version **0.1.2** requires **PI WEB >=1.202610.3**. Automated tests, builds, and artifact smoke checks passed against `1.202610.3`; live-host testing remains outstanding. See [compatibility results](compatibility.md) for the validation limits.
|
|
4
|
+
|
|
5
|
+
## Install the standalone package
|
|
6
|
+
|
|
7
|
+
Install `npm:@compn3rd/pi-web-code-review` through **Settings → Pi packages** on the target machine. If migrating from the unscoped package, disable/remove that plugin first; do not enable both copies. The plugin ID and storage keys are unchanged. Enable the scoped plugin and restart safely as described below. To build and install locally:
|
|
8
|
+
|
|
9
|
+
Build from this repository's root:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm ci --ignore-scripts
|
|
13
|
+
npm run build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`--ignore-scripts` avoids native installation scripts from the development-only PI WEB dependency. This plugin's build does not use those native modules. The browser's Lit and Markdown dependencies are bundled; the server entry uses only the host-provided Git runner. Installed users need the built package, not its development dependencies.
|
|
17
|
+
|
|
18
|
+
Link this directory into the target machine's plugin directory. On Linux/macOS, while still at the repository root:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
mkdir -p "${PI_WEB_DATA_DIR:-$HOME/.pi-web}/plugins"
|
|
22
|
+
ln -s "$PWD" "${PI_WEB_DATA_DIR:-$HOME/.pi-web}/plugins/code-review"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On Windows, run PowerShell from the built plugin directory:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
$data = if ($env:PI_WEB_DATA_DIR) { $env:PI_WEB_DATA_DIR } else { Join-Path $env:USERPROFILE '.pi-web' }
|
|
29
|
+
New-Item -ItemType Directory -Force (Join-Path $data 'plugins')
|
|
30
|
+
New-Item -ItemType Junction -Path (Join-Path $data 'plugins/code-review') -Target (Get-Location).Path
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Alternatively, copy the package's `package.json`, `dist`, `LICENSE`, `README.md`, and `docs` into that directory. For distribution, `npm pack` creates a self-contained tarball; extract it as the plugin directory, retaining `package.json` at its root. Do not copy `node_modules`.
|
|
34
|
+
|
|
35
|
+
Enable **Code Review** in **Settings → PI WEB plugins**. Because the package includes a server entry, **manually restart the target session daemon when safe**, then reload the browser. Restarting interrupts active sessions and terminals; do it from outside the sessions that daemon hosts. The plugin does not restart anything itself. Browser reload or Pi's `/reload` alone is insufficient after installing or updating this paired package.
|
|
36
|
+
|
|
37
|
+
For remote workspaces, install and enable the package on each target machine. Git runs on the panel's machine, never on the gateway as a fallback. Files and package-peer requests use the host's scoped helpers, including nested deployments.
|
|
38
|
+
|
|
39
|
+
## Review files and changes
|
|
40
|
+
|
|
41
|
+
1. Select a workspace and open the **Review** tab (or **Open Code Review** in the action palette).
|
|
42
|
+
2. Choose **Files**, **Git unstaged**, **Git staged**, or **Compare revisions**. Files includes untracked files. Browse directories or enter a workspace-relative file path. For committed work across multiple commits, use the comparison controls below.
|
|
43
|
+
3. Click a line number to open and focus the comment editor directly below it inside the viewer. Shift-click extends its range on the same side; on touch screens, edit **Start line** and **End line** instead. Deleted diff lines use old-side coordinates; context and additions use new-side coordinates. Ranges cannot span omitted diff context.
|
|
44
|
+
4. Enter Markdown and choose **Save comment**. Ctrl/Cmd+Enter also saves; ordinary Enter inserts a newline.
|
|
45
|
+
5. Review the saved list. **Edit comment** reloads its source before allowing a save; **Remove comment** deletes that comment.
|
|
46
|
+
|
|
47
|
+
Comments render safe Markdown: raw HTML is displayed as text, images do not load, and links are limited to HTTP/HTTPS. Binary, truncated, very large sources (over 200,000 characters or 5,000 lines), and directories/change lists with over 2,000 entries are rejected rather than displaying incomplete selectable coordinates. Git must be installed. Git views show tracked changes; inspect untracked files in Files. Submodules are shown as Git's gitlink diff, not recursively expanded into another repository's file changes.
|
|
48
|
+
|
|
49
|
+
Saved comments are local to this browser origin, keyed by machine, project, and workspace. They survive reloads and session changes; they are not synchronized across browsers or machines. IndexedDB transactions serialize concurrent saves and acknowledgements across tabs, so independent comments are not lost through competing read/modify/write operations. IndexedDB does not require HTTPS or Web Locks; LAN HTTP remains supported where the browser permits origin storage. Storage errors are shown and an unsuccessful save leaves the editor text available. Corrupt or unrecognized storage is not overwritten automatically. Feedback warnings remain separate from source-loading errors, survive panel/workspace remounts within the activation, and stay visible until explicitly dismissed.
|
|
50
|
+
|
|
51
|
+
### Review accumulated branch or commit changes
|
|
52
|
+
|
|
53
|
+
Select **Compare revisions**, enter **Base revision** and **Target revision**, then choose a comparison mode and click **Compare**:
|
|
54
|
+
|
|
55
|
+
| Goal | Base | Target | Mode |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| Current branch's work since diverging from main | `main` (or `origin/main`) | `HEAD` | Branch changes since merge base (`A...B`) |
|
|
58
|
+
| Another branch's accumulated work | `main` | `feature/my-branch` | Branch changes since merge base (`A...B`) |
|
|
59
|
+
| Last three commits together | `HEAD~3` | `HEAD` | Between revisions (`A..B`) |
|
|
60
|
+
| Changes since a particular commit or tag | Commit ID or tag | `HEAD` | Between revisions (`A..B`) |
|
|
61
|
+
|
|
62
|
+
Merge-base mode compares the common ancestor of base and target with the target, excluding changes made only on main after divergence. Between-revisions mode compares the two commit trees directly. A `main` branch is not assumed to exist: enter `master`, a remote-tracking branch, or another available revision as needed. Refs must exist locally; this feature does not fetch, checkout, stage, or modify Git data. Revisions are single branch/tag/commit expressions (including `HEAD~3` or `HEAD^`), not combined `A..B` strings, paths, or Git options.
|
|
63
|
+
|
|
64
|
+
Only **committed** changes appear. Dirty index/worktree and untracked changes remain available in the other source modes. Click a changed file to review its complete accumulated diff, then comment as usual.
|
|
65
|
+
|
|
66
|
+
The list and all opened files use the same resolved commit IDs, even if a branch moves meanwhile. **Compare** or **Refresh** resolves the inputs again and resets the file selection; choose a file in the new list. Saved comments retain the original endpoints, comparison mode, and snapshot hash. **Edit comment** reopens those frozen endpoints, not today's branch tips. A different comparison never silently re-anchors an existing comment, even if its text diff is identical. Feedback Markdown includes the revision labels and abbreviated commit IDs.
|
|
67
|
+
|
|
68
|
+
### Storage upgrade and recovery
|
|
69
|
+
|
|
70
|
+
Reload **all old plugin tabs before using the upgraded plugin**. Comparison comments introduce a new source type; older plugin versions cannot read a workspace record containing them and report unrecognized storage without overwriting it. Upgrade all tabs before saving comparison comments; copy/export feedback before downgrading. On first access to each workspace, the plugin imports its previous `pi-web-code-review:v1:` localStorage record into IndexedDB in one transaction. The record itself marks migration complete, including when its comment list is empty after removal or acknowledgement. Existing IndexedDB data always wins. Legacy localStorage is retained unchanged as a backup, not an active store after migration. Old plugin tabs still write only localStorage; their later edits cannot safely be merged automatically and will not appear in the upgraded plugin. Copy any such feedback before reloading those tabs.
|
|
71
|
+
|
|
72
|
+
If migration is corrupt, blocked, or fails, the panel reports it and retains the original data for recovery; it does not substitute an empty successful save. Close older tabs if an upgrade is blocked, then reopen Review to retry. Browser storage clearing deletes active comments and may also delete the backup; clearing only IndexedDB can reimport the retained legacy snapshot. Export/copy needed feedback before clearing storage.
|
|
73
|
+
|
|
74
|
+
Cross-tab chip withdrawal and live badges use BroadcastChannel notifications after durable commits. A notification failure cannot undo a saved comment. If the browser blocks BroadcastChannel, transaction safety remains, but other tabs may display stale feedback/chips until refreshed; reattach a fresh snapshot before sending.
|
|
75
|
+
|
|
76
|
+
Switching away from the tab preserves the current editor within this activation. Switching workspace, reloading the browser, or disabling the plugin discards **unsaved** edits. Save before doing so.
|
|
77
|
+
|
|
78
|
+
Refreshing or host file invalidation reloads the current snapshot (in comparison mode, it re-resolves the revision inputs and resets the file selection). A changed file does not silently delete feedback: saved comments remain in the list, are flagged as stale when that source is viewed, and are not attached to its new lines. Copy any unfinished text before cancelling a stale edit. Create a new comment against the current snapshot instead of automatically moving an old anchor. Sources you have not reopened have not been checked for staleness; verify coordinates before handing off.
|
|
79
|
+
|
|
80
|
+
## Attach feedback to the composer
|
|
81
|
+
|
|
82
|
+
Choose a ready, unarchived conversation in this workspace, then select **Attach review to composer** in the panel header. The header shows saved-comment and unsaved-draft counts; save your draft first if you want it included. One removable **Review (N)** chip contains a snapshot of all saved comments. Repeating the action replaces that conversation's review chip, not the text at its cursor. You can inspect the payload under **Feedback Markdown**.
|
|
83
|
+
|
|
84
|
+
Send the chip with a message or by itself. Failed sends keep it and the saved comments for retry. Server acceptance clears only unchanged comments from the submitted snapshot; later edits and new comments survive. Acceptance does not mean the assistant has finished answering. Removing the composer chip only detaches it—saved comments remain available.
|
|
85
|
+
|
|
86
|
+
Saving edits or removing comments withdraws this workspace's staged feedback; attach again when ready. Changes from another browser tab also withdraw this activation's copies. If withdrawal fails, remove the stale chip manually before sending. If clearing submitted comments fails, inspect the accepted conversation before reattaching to avoid duplicate feedback. This acceptance warning remains visible alongside later action or storage errors until you explicitly dismiss it, including after switching workspaces or reopening Review within the same activation.
|
|
87
|
+
|
|
88
|
+
Chips survive closing the panel or navigating away, but not reloading the page. Saved comments survive reloads; open the intended conversation and attach again. Different conversations can hold independent snapshots, so attach to multiple conversations only if you intend to send the feedback more than once.
|
|
89
|
+
|
|
90
|
+
## Differences from the former core UI
|
|
91
|
+
|
|
92
|
+
This is an independent Review tab, not an extension of the bundled Files/Git tabs: main does not expose inline decoration hooks for those panels. Composer integration uses main's public chip API, including removal and server-accepted callbacks. There are no private DOM hooks, private HTTP routes, host-owned review services, or patches to PromptEditor.
|
|
93
|
+
|
|
94
|
+
Feedback is workspace-scoped rather than session-scoped, so it can be authored before choosing a session and requires no temporary-session migration hooks. The old branch's `pi-web:review-comments:` session stores are not automatically imported or deleted; copy any remaining feedback from the old UI before switching installations.
|
|
95
|
+
|
|
96
|
+
Comment ownership stays in the plugin; the host owns only the staged composer snapshot and its send lifecycle.
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@compn3rd/pi-web-code-review",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@compn3rd/pi-web-code-review",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Workspace file and Git review comments for PI WEB, without host modifications.",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/CompN3rd/pi-web-code-review.git"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"docs",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"lint": "eslint src test",
|
|
25
|
+
"build": "npm run typecheck && vite build && tsc -p tsconfig.server.json",
|
|
26
|
+
"check:artifacts": "node scripts/check-artifacts.mjs",
|
|
27
|
+
"verify": "npm run typecheck && npm run lint && npm test && npm run build && npm run check:artifacts",
|
|
28
|
+
"prepack": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@eslint/js": "^10.0.1",
|
|
32
|
+
"@jmfederico/pi-web": ">=1.202610.3",
|
|
33
|
+
"@noble/hashes": "^2.4.0",
|
|
34
|
+
"@types/node": "^24.13.3",
|
|
35
|
+
"eslint": "^10.6.0",
|
|
36
|
+
"fake-indexeddb": "^6.2.4",
|
|
37
|
+
"globals": "^17.7.0",
|
|
38
|
+
"happy-dom": "^20.11.1",
|
|
39
|
+
"lit": "^3.3.3",
|
|
40
|
+
"marked": "^18.0.6",
|
|
41
|
+
"typescript": "^6.0.3",
|
|
42
|
+
"typescript-eslint": "^8.63.0",
|
|
43
|
+
"vite": "^8.1.4",
|
|
44
|
+
"vitest": "^4.1.10"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.19.0"
|
|
48
|
+
},
|
|
49
|
+
"piWeb": {
|
|
50
|
+
"plugins": [
|
|
51
|
+
{
|
|
52
|
+
"id": "code-review",
|
|
53
|
+
"browserRoot": "dist/browser",
|
|
54
|
+
"module": "dist/browser/index.js",
|
|
55
|
+
"serverModule": "dist/server.js",
|
|
56
|
+
"machineSpecific": true
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|