@compn3rd/pi-web-code-review 0.1.1 → 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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 — 2026-10-16
4
+
5
+ ### Changed
6
+
7
+ - Open and focus comment editors directly beneath the selected code line, inside the file viewer, instead of below the entire file.
8
+ - Use a full-width header with saved-comment and unsaved-draft status, and move **Attach review to composer** to the header.
9
+ - Place file navigation beside the viewer on wide panels and stack it above the viewer on narrow panels.
10
+ - Highlight the active file and improve empty states, keyboard hints, focus indicators, and source-control layout.
11
+ - Keep changed-snapshot drafts accessible outside the code view without losing their text or allowing an invalid save.
12
+
13
+ ### Validation and compatibility
14
+
15
+ - Add regression tests for inline editor placement, focus, header action placement, active-file state, and changed-snapshot draft preservation (84 tests total).
16
+ - Capture browser-rendered before/after screenshots with the same sample file and draft; these are standalone previews, not live-host acceptance tests.
17
+ - No host API, storage format, or server behavior changes. Requirements remain PI WEB `>=1.202610.3` and Node.js `>=22.19.0`.
18
+ - Live-host acceptance and remote-machine testing remain outstanding; see [compatibility results](docs/compatibility.md).
19
+
3
20
  ## 0.1.1 — 2026-10-15
4
21
 
5
22
  Package: **`@compn3rd/pi-web-code-review`** (public npm scope).
package/README.md CHANGED
@@ -4,7 +4,7 @@ A standalone Review tab for workspace files, staged/unstaged Git changes, and ac
4
4
 
5
5
  ## Requirements and installation
6
6
 
7
- Patch release: **0.1.1**. Requires **PI WEB >=1.202610.3**, Node.js **>=22.19.0**, and Git for staged/unstaged and revision-comparison review. Compatibility checks were performed against PI WEB `1.202610.3`; later releases are allowed but have not been independently tested.
7
+ Patch release: **0.1.2**. Requires **PI WEB >=1.202610.3**, Node.js **>=22.19.0**, and Git for staged/unstaged and revision-comparison review. Compatibility checks were performed against PI WEB `1.202610.3`; later releases are allowed but have not been independently tested.
8
8
 
9
9
  Install `npm:@compn3rd/pi-web-code-review` through **Settings → Pi packages** on the target machine, then enable **Code Review** in **Settings → PI WEB plugins**. For local builds and manual installation, see the [usage guide](docs/usage.md).
10
10
 
@@ -14,9 +14,17 @@ This package includes a server plugin: restart the target session daemon when sa
14
14
 
15
15
  Open the **Review** tab, choose workspace files, staged/unstaged changes, or **Compare revisions**, and save line-range comments. Compare `main...HEAD` for branch work since its merge base, or `HEAD~3..HEAD` for accumulated work between commits. Comparison comments retain frozen commit IDs. Select a ready conversation and choose **Attach review to composer** to stage a removable **Review (N)** chip. Send it with a message or by itself. Removing the chip preserves saved comments; server acceptance clears only unchanged submitted comments.
16
16
 
17
+ ## Redesigned review panel
18
+
19
+ Click a line number to open a focused comment editor directly beneath that line. The file sidebar sits beside the viewer on wide panels and stacks above it on narrow panels. Saved-comment and draft counts appear in the full-width header alongside **Attach review to composer**; only saved comments are attached.
20
+
21
+ ![Redesigned review panel](docs/screenshots/review-after.png)
22
+
23
+ Standalone browser preview with sample content, not a live-host screenshot. [Before redesign](docs/screenshots/review-before.png).
24
+
17
25
  ## Validation status
18
26
 
19
- Typechecking, all 82 tests, builds, and unpacked/packed artifact smoke checks passed against PI WEB `1.202610.3`. Full automated verification passes; live-host acceptance testing is pending. Publication is enabled for the scoped patch release; these remaining checks are follow-up work, not claims of completed validation.
27
+ Typechecking, all 84 tests, builds, and unpacked/packed artifact smoke checks passed against PI WEB `1.202610.3`. Full automated verification passes; live-host acceptance testing is pending. Publication is enabled for the scoped patch release; these remaining checks are follow-up work, not claims of completed validation.
20
28
 
21
29
  See the [composer design](docs/composer-design.md), [usage guide](docs/usage.md), [development guide](docs/development.md), and [compatibility results](docs/compatibility.md).
22
30
 
@@ -2658,6 +2658,7 @@ var ar = class extends A {
2658
2658
  feedbackRead = 0;
2659
2659
  connection = 0;
2660
2660
  saving = !1;
2661
+ focusEditor = !1;
2661
2662
  notice = "";
2662
2663
  listLoading = !1;
2663
2664
  viewLoading = !1;
@@ -2670,7 +2671,10 @@ var ar = class extends A {
2670
2671
  this.connection++, this.feedbackRead++, this.listing?.abort(), this.reading?.abort(), this.comparing?.abort(), super.disconnectedCallback();
2671
2672
  }
2672
2673
  updated(e) {
2673
- e.has("revision") && e.get("revision") !== void 0 && this.refresh();
2674
+ if (e.has("revision") && e.get("revision") !== void 0 && this.refresh(), this.focusEditor) {
2675
+ let e = this.renderRoot.querySelector(".editor textarea");
2676
+ e !== null && (this.focusEditor = !1, e.focus({ preventScroll: !0 }), e.closest(".editor")?.scrollIntoView({ block: "nearest" }));
2677
+ }
2674
2678
  }
2675
2679
  updateContext(e) {
2676
2680
  let t = ir(e);
@@ -2799,7 +2803,7 @@ var ar = class extends A {
2799
2803
  }
2800
2804
  let a = nr(e, r, t, i);
2801
2805
  if (n.aborted) return;
2802
- this.view = a, this.editor !== void 0 && (this.editor.path !== e || this.editor.source !== r || this.editor.hash !== a.hash) && (this.notice = "The source changed. Your unfinished comment remains below, but cannot be saved against this snapshot. Copy its text before cancelling.");
2806
+ this.view = a, this.editor !== void 0 && (this.focusEditor = !0), this.editor !== void 0 && (this.editor.path !== e || this.editor.source !== r || this.editor.hash !== a.hash) && (this.notice = "The source changed. Your unfinished comment remains in the draft editor, but cannot be saved against this snapshot. Copy its text before cancelling.");
2803
2807
  } catch (e) {
2804
2808
  n.aborted || (this.view = void 0, this.fail(e));
2805
2809
  } finally {
@@ -2824,7 +2828,7 @@ var ar = class extends A {
2824
2828
  hash: n.hash,
2825
2829
  body: "",
2826
2830
  ...n.comparison === void 0 ? {} : { comparison: n.comparison }
2827
- } : this.notice = "Save or cancel the current comment first. Shift-click extends a range on the same side.", this.requestUpdate());
2831
+ } : this.notice = "Save or cancel the current comment first. Shift-click extends a range on the same side.", this.focusEditor = !0, this.requestUpdate());
2828
2832
  }
2829
2833
  async saveEditor() {
2830
2834
  let e = this.editor;
@@ -2864,7 +2868,7 @@ var ar = class extends A {
2864
2868
  this.notice = "Save or cancel the current comment first.", this.requestUpdate();
2865
2869
  return;
2866
2870
  }
2867
- this.editor = { ...e }, this.comparing?.abort(), this.comparing = void 0, this.source = e.source, this.comparison = e.comparison, this.view = void 0, this.entries = [], e.comparison !== void 0 && (this.baseRef = e.comparison.baseRef, this.headRef = e.comparison.headRef, this.comparisonMode = e.comparison.mode), this.loadList(), this.openFile(e.path);
2871
+ this.editor = { ...e }, this.focusEditor = !0, this.comparing?.abort(), this.comparing = void 0, this.source = e.source, this.comparison = e.comparison, this.view = void 0, this.entries = [], e.comparison !== void 0 && (this.baseRef = e.comparison.baseRef, this.headRef = e.comparison.headRef, this.comparisonMode = e.comparison.mode), this.loadList(), this.openFile(e.path);
2868
2872
  }
2869
2873
  }
2870
2874
  async attachReview() {
@@ -2900,20 +2904,25 @@ var ar = class extends A {
2900
2904
  }} @keydown=${(e) => {
2901
2905
  e.key === "Enter" && (e.ctrlKey || e.metaKey) && !e.isComposing && (e.preventDefault(), this.saveEditor());
2902
2906
  }}></textarea></label>
2903
- <div class="toolbar"><button ?disabled=${this.saving || !t || e.body.trim() === ""} @click=${() => {
2907
+ <div class="toolbar"><button class="primary" ?disabled=${this.saving || !t || e.body.trim() === ""} @click=${() => {
2904
2908
  this.saveEditor();
2905
2909
  }}>Save comment</button>
2906
2910
  <button ?disabled=${this.saving} @click=${() => {
2907
2911
  this.editor = void 0, this.notice = "", this.requestUpdate();
2908
- }}>Cancel edit</button></div>
2912
+ }}>Cancel edit</button><span class="hint">Ctrl / ⌘ + Enter to save</span></div>
2909
2913
  </section>`;
2910
2914
  }
2911
2915
  render() {
2912
- let e = this.view;
2913
- return T`<section aria-label="Code Review">
2914
- <h2>Code Review</h2>
2915
- <p>Save comments, then attach a review chip to the selected conversation. Failed sends retain feedback; server acceptance clears unchanged submitted comments.</p>
2916
- <div class="toolbar"><label>Source <select .value=${this.source} @change=${(e) => {
2916
+ let e = this.view, t = this.editor !== void 0 && this.editor.hash === e?.hash && rr(this.editor, e);
2917
+ return T`<section class="review-panel" aria-label="Code Review">
2918
+ <header class="review-header"><div><h2>Code Review</h2><p class="hint">Review files. Comment in context. Share when ready.</p></div>
2919
+ <div class="review-actions"><span>${this.comments.length} saved · ${this.editor === void 0 ? "No draft" : "1 unsaved draft"}</span>
2920
+ <button class="primary" ?disabled=${this.saving || this.comments.length === 0} @click=${() => {
2921
+ this.attachReview();
2922
+ }}>Attach review to composer</button>
2923
+ <span class="hint">Saved comments only · send from the conversation</span></div>
2924
+ </header>
2925
+ <div class="source-controls"><div class="toolbar"><label>Source <select .value=${this.source} @change=${(e) => {
2917
2926
  e.target instanceof HTMLSelectElement && this.selectSource(e.target.value);
2918
2927
  }}>
2919
2928
  <option value="files">Files (including untracked)</option><option value="git-unstaged">Git unstaged</option><option value="git-staged">Git staged</option><option value="git-compare">Compare revisions</option>
@@ -2942,6 +2951,7 @@ var ar = class extends A {
2942
2951
  }}><option value="merge-base">Branch changes since merge base (A...B)</option><option value="direct">Between revisions (A..B)</option></select></label>
2943
2952
  <button>Compare</button>
2944
2953
  </form><p>Compare committed work only. Use main → HEAD with merge base for a branch review, or a commit → HEAD with between revisions for accumulated work. Branch names, tags, commit IDs, and HEAD~3 are supported. Compare or Refresh resolves revisions again; opening files uses the frozen commits shown below.</p>` : null}
2954
+ </div>
2945
2955
  ${this.error === "" ? null : T`<p role="alert">${this.error}</p>`}
2946
2956
  ${Object.values(this.feedbackErrors).every((e) => e === "") ? null : T`
2947
2957
  ${Object.values(this.feedbackErrors).filter(Boolean).map((e) => T`<p role="alert">${e}</p>`)}
@@ -2949,35 +2959,35 @@ var ar = class extends A {
2949
2959
  this.reportFeedbackError("", "acceptance"), this.reportFeedbackError("", "action"), this.reportFeedbackError("", "read");
2950
2960
  }}>Dismiss feedback warning</button>`}
2951
2961
  <p role="status" aria-live="polite">${this.listLoading || this.viewLoading || this.comparing !== void 0 ? "Loading…" : this.notice}</p>
2952
- <details class="file-list" open><summary>${this.source === "files" ? this.directory || "Workspace files" : `${Qn(this.source, this.comparison)} files`}</summary>
2962
+ <div class="review-workspace"><nav aria-label="Review files"><details class="file-list" open><summary>${this.source === "files" ? this.directory || "Workspace files" : `${Qn(this.source, this.comparison)} files`}</summary>
2953
2963
  ${this.source === "files" && this.directory !== "" ? T`<button @click=${() => {
2954
2964
  this.directory = this.directory.split("/").slice(0, -1).join("/"), this.loadList();
2955
2965
  }}>Parent directory</button>` : null}
2956
- ${this.entries.map((e) => T`<button ?disabled=${this.listLoading} @click=${() => {
2957
- e.type === "directory" ? (this.directory = e.path, this.loadList()) : this.openFile(e.path);
2958
- }}>${e.type === "directory" ? "📁 " : ""}${e.name}</button>`)}
2966
+ ${this.entries.map((t) => T`<button aria-current=${t.path === e?.path ? "page" : "false"} ?disabled=${this.listLoading} @click=${() => {
2967
+ t.type === "directory" ? (this.directory = t.path, this.loadList()) : this.openFile(t.path);
2968
+ }}>${t.type === "directory" ? "📁 " : ""}${t.name}</button>`)}
2959
2969
  ${!this.listLoading && this.comparing === void 0 && this.entries.length === 0 ? T`<p>No files in this view.</p>` : null}
2960
- </details>
2961
- ${e === void 0 ? null : T`<h3>${e.path} — ${Qn(e.source, e.comparison)}</h3><p>Click a line number, then Shift-click to extend its range, or edit the range below (works on touch screens).</p>
2962
- <div class="code" aria-label="Review source" aria-busy=${String(this.viewLoading)}>${e.rows.map((t) => {
2963
- let n = t.line === void 0 || t.side === void 0 ? void 0 : {
2970
+ </details></nav><main class="viewer">
2971
+ ${e === void 0 ? T`<div class="empty-state"><h3>${this.viewLoading ? "Opening file…" : "Choose a file to start reviewing"}</h3><p>Select a file from the list or open a workspace-relative path above.</p></div>` : T`<div class="file-heading"><h3>${e.path}</h3><span class="hint">${Qn(e.source, e.comparison)}</span><p class="hint">Click a line number to comment. Shift-click to extend the range, or use the editor’s line fields.</p></div>
2972
+ <div class="code" aria-label="Review source" aria-busy=${String(this.viewLoading)}>${e.rows.map((n) => {
2973
+ let r = n.line === void 0 || n.side === void 0 ? void 0 : {
2964
2974
  path: e.path,
2965
2975
  source: e.source,
2966
- side: t.side,
2967
- start: t.line,
2968
- end: t.line
2969
- }, r = n !== void 0 && this.editor?.path === e.path && this.editor.source === e.source && this.editor.hash === e.hash && this.editor.side === t.side && n.start >= this.editor.start && n.start <= this.editor.end, i = n === void 0 ? [] : this.comments.filter((n) => n.path === e.path && n.source === e.source && n.hash === e.hash && n.side === t.side && n.start === t.line);
2970
- return T`<div class=${`row ${t.kind}${r ? " selected" : ""}`}>
2971
- ${n === void 0 ? T`<span class="line"></span>` : T`<button class="line" aria-label=${`${t.side === "old" ? "Deleted" : "Current"} line ${String(t.line)}`} aria-pressed=${String(r)} ?disabled=${this.viewLoading} @click=${(e) => {
2972
- this.selectLine(t, e.shiftKey);
2973
- }}>${t.line}</button>`}
2974
- <code>${t.text || " "}</code></div>${i.map((e) => T`<div class="inline-comment">${fn(T, e.body)}</div>`)}`;
2976
+ side: n.side,
2977
+ start: n.line,
2978
+ end: n.line
2979
+ }, i = r !== void 0 && this.editor?.path === e.path && this.editor.source === e.source && this.editor.hash === e.hash && this.editor.side === n.side && r.start >= this.editor.start && r.start <= this.editor.end, a = r === void 0 ? [] : this.comments.filter((t) => t.path === e.path && t.source === e.source && t.hash === e.hash && t.side === n.side && t.start === n.line);
2980
+ return T`<div class=${`row ${n.kind}${i ? " selected" : ""}`}>
2981
+ ${r === void 0 ? T`<span class="line"></span>` : T`<button class="line" aria-label=${`${n.side === "old" ? "Deleted" : "Current"} line ${String(n.line)}`} aria-pressed=${String(i)} ?disabled=${this.viewLoading} @click=${(e) => {
2982
+ this.selectLine(n, e.shiftKey);
2983
+ }}>${n.line}</button>`}
2984
+ <code>${n.text || " "}</code></div>${t && this.editor?.side === n.side && this.editor?.start === n.line ? this.renderEditor() : null}${a.map((e) => T`<div class="inline-comment">${fn(T, e.body)}</div>`)}`;
2975
2985
  })}</div>${e.rows.length === 0 ? T`<p>No text lines to review.</p>` : null}`}
2976
- ${this.renderEditor()}
2977
- <h3>Saved comments (${this.comments.length})</h3>
2978
- <div class="toolbar"><button ?disabled=${this.saving || this.comments.length === 0} @click=${() => {
2979
- this.attachReview();
2980
- }}>Attach review to composer</button></div>
2986
+ ${t ? null : this.renderEditor()}
2987
+ </main></div>
2988
+ <section class="saved-review" aria-label="Saved review"><h3>Saved comments (${this.comments.length})</h3>
2989
+ <p class="hint">Saved locally until sent. Failed sends retain feedback; acceptance clears unchanged submitted comments.</p>
2990
+ ${this.comments.length === 0 ? T`<p class="hint">No comments yet. Click a line number in the file viewer to add one.</p>` : null}
2981
2991
  ${this.comments.map((t) => T`<article><strong>${$n(t)}</strong>
2982
2992
  ${t.path === e?.path && t.source === e.source && t.hash !== e.hash ? T`<p class="stale">Source changed since this comment was saved. Verify its coordinates before sending.</p>` : null}
2983
2993
  <div class="markdown">${fn(T, t.body)}</div>
@@ -2988,18 +2998,46 @@ var ar = class extends A {
2988
2998
  }}>Remove comment</button></div>
2989
2999
  </article>`)}
2990
3000
  <details><summary>Feedback Markdown</summary><textarea aria-label="Feedback Markdown" readonly rows="8" .value=${er(this.comments)}></textarea></details>
2991
- </section>`;
3001
+ </section></section>`;
2992
3002
  }
2993
3003
  static styles = o`
2994
3004
  :host { display: block; color: var(--pi-text, #ddd); font: 14px system-ui, sans-serif; }
2995
- section { padding: 12px; } h2 { margin-top: 0; } p { line-height: 1.5; }
3005
+ * { box-sizing: border-box; }
3006
+ :host { container-type: inline-size; width: 100%; min-width: 0; }
3007
+ .review-panel { padding: 16px; } h2, h3 { margin: 0; } p { line-height: 1.5; }
3008
+ .hint { color: var(--pi-muted, #aaa); font-size: 12px; }
3009
+ .review-header { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; border-bottom: 1px solid var(--pi-border, #666); padding-bottom: 16px; }
3010
+ .review-header p { margin: 6px 0 0; }
3011
+ .review-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }
3012
+ .source-controls { display: flex; flex-wrap: wrap; align-items: end; gap: 8px 20px; padding: 8px 0; }
3013
+ .source-controls form { flex: 1; } .source-controls label { flex: 1; min-width: 140px; }
3014
+ .source-controls > p { flex-basis: 100%; margin: 0; font-size: 12px; color: var(--pi-muted, #aaa); }
3015
+ .review-workspace { display: grid; grid-template-columns: minmax(160px, 22%) minmax(0, 1fr); border: 1px solid var(--pi-border, #666); border-radius: 8px; overflow: hidden; }
3016
+ nav { background: var(--pi-surface, #222); border-right: 1px solid var(--pi-border, #666); min-width: 0; padding: 12px; }
3017
+ .viewer { min-width: 0; }
3018
+ .file-heading { padding: 12px 16px; overflow-wrap: anywhere; } .file-heading p { margin-bottom: 0; }
3019
+ .empty-state { padding: 48px 24px; text-align: center; color: var(--pi-muted, #aaa); }
3020
+ .saved-review { margin-top: 24px; } summary { cursor: pointer; overflow-wrap: anywhere; }
3021
+ [role=status]:empty { display: none; }
3022
+ @container (max-width: 640px) {
3023
+ .review-panel { padding: 10px; } .review-workspace { grid-template-columns: minmax(0, 1fr); }
3024
+ nav { border-right: 0; border-bottom: 1px solid var(--pi-border, #666); }
3025
+ .review-actions { align-items: flex-start; } .file-list { max-height: 150px !important; }
3026
+ }
2996
3027
  .toolbar { display: flex; flex-wrap: wrap; align-items: end; gap: 8px; margin: 8px 0; }
2997
3028
  label { display: flex; flex-direction: column; gap: 4px; }
2998
3029
  button, select, input, textarea { color: inherit; background: var(--pi-surface, #222); border: 1px solid var(--pi-border, #666); border-radius: 4px; padding: 6px; font: inherit; }
2999
- button { cursor: pointer; } button:disabled { opacity: .5; cursor: default; } button:focus-visible { outline: 2px solid var(--pi-accent, #6cf); }
3030
+ button { cursor: pointer; } button:hover:not(:disabled) { border-color: var(--pi-accent, #6cf); }
3031
+ .primary { background: var(--pi-accent, #6cf); color: var(--pi-surface, #222); border-color: transparent; font-weight: 600; }
3032
+ :is(button, input, select, textarea):focus-visible { outline: 2px solid var(--pi-accent, #6cf); outline-offset: 2px; }
3033
+ button:disabled { opacity: .5; cursor: default; } button:focus-visible { outline: 2px solid var(--pi-accent, #6cf); }
3000
3034
  textarea { box-sizing: border-box; width: 100%; resize: vertical; } input[type=number] { width: 6em; }
3001
- .file-list { max-height: 220px; overflow: auto; margin: 12px 0; } .file-list button { display: block; text-align: left; margin: 3px 0; overflow-wrap: anywhere; }
3002
- .code { max-height: 55vh; overflow: auto; border: 1px solid var(--pi-border, #666); }
3035
+ .file-list { max-height: 65vh; overflow: auto; } .file-list button { display: block; width: 100%; text-align: left; margin: 4px 0; overflow-wrap: anywhere; border-color: transparent; background: transparent; }
3036
+ .file-list button[aria-current=page] { background: var(--pi-selection-bg, #336699); border-color: var(--pi-accent, #6cf); }
3037
+ .code { max-height: 65vh; overflow: auto; border-top: 1px solid var(--pi-border, #666); }
3038
+ .code .editor, .code .inline-comment { position: sticky; left: 0; width: calc(100% - 24px); margin: 10px 12px; background: var(--pi-surface, #222); }
3039
+ .editor { border-left: 3px solid var(--pi-accent, #6cf) !important; }
3040
+ .editor strong { display: block; margin-bottom: 8px; }
3003
3041
  .row { display: flex; min-width: max-content; } .row code { white-space: pre; padding: 2px 8px; font: 12px ui-monospace, monospace; }
3004
3042
  .line { flex: 0 0 4em; border-radius: 0; padding: 2px; font: 12px ui-monospace, monospace; text-align: right; }
3005
3043
  .add { background: #22883325; } .remove { background: #bb222225; } .meta { color: var(--pi-muted, #999); }
@@ -24,6 +24,10 @@ No production API migration was required for this release:
24
24
 
25
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
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
+
27
31
  ## Validation
28
32
 
29
33
  After `npm ci --ignore-scripts`:
@@ -32,7 +36,7 @@ After `npm ci --ignore-scripts`:
32
36
  | --- | --- |
33
37
  | `npm run typecheck` | Passed |
34
38
  | `npm run lint` | Passed |
35
- | `npm test` | Passed: 6 files, 82 tests |
39
+ | `npm test` | Passed: 6 files, 84 tests |
36
40
  | `npm run build` | Passed |
37
41
  | `npm run check:artifacts` | Passed |
38
42
  | `npm pack` and smoke check of extracted package | Passed |
@@ -50,6 +54,6 @@ All 38 original lint findings are resolved. Async disposal and callback-mutated
50
54
  1. Automated verification and packed smoke checks pass after lint cleanup; storage/ownership guards remain in place.
51
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.
52
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.
53
- 4. Publication is enabled for the `0.1.1` patch release as `@compn3rd/pi-web-code-review`. The remaining validation is follow-up work; discovered defects will be addressed in patch releases.
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.
54
58
 
55
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.
@@ -4,9 +4,9 @@
4
4
 
5
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
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 82 tests, builds, and unpacked/packed artifact smoke checks pass. Full automated verification passes. See [compatibility results](compatibility.md).
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
8
 
9
- The `0.1.1` 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.
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
10
 
11
11
  Reference: [upstream composer documentation](https://github.com/jmfederico/pi-web/blob/830dc179/docs/plugins.md#attach-text-context-to-a-message).
12
12
 
@@ -49,6 +49,6 @@ Current progress against published PI WEB `1.202610.3`:
49
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
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
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.1` as `@compn3rd/pi-web-code-review`, with PI WEB `>=1.202610.3` supported. Keep outstanding validation documented and address discovered defects in patch releases.
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
53
 
54
54
  There is no scheduled release watcher or automatic installation/restart associated with this gate.
@@ -6,9 +6,9 @@ This repository starts from the plugin snapshot in [CompN3rd/pi-web at `d0c7a212
6
6
 
7
7
  ## Verification status
8
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 82 tests, the build, and unpacked/packed artifact smoke checks pass. Full `verify` passes; live-host testing is still pending. See [compatibility results](compatibility.md).
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
10
 
11
- The `0.1.1` 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:
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
12
 
13
13
  ```sh
14
14
  npm ci --ignore-scripts
@@ -27,4 +27,4 @@ Prepared cases cover actual adapter serialization on two IndexedDB connections,
27
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
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
29
 
30
- Version `0.1.1` 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.
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.
package/docs/usage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Code Review workflow
2
2
 
3
- Version **0.1.1** 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.
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
4
 
5
5
  ## Install the standalone package
6
6
 
@@ -40,7 +40,7 @@ For remote workspaces, install and enable the package on each target machine. Gi
40
40
 
41
41
  1. Select a workspace and open the **Review** tab (or **Open Code Review** in the action palette).
42
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. 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.
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
44
  4. Enter Markdown and choose **Save comment**. Ctrl/Cmd+Enter also saves; ordinary Enter inserts a newline.
45
45
  5. Review the saved list. **Edit comment** reloads its source before allowing a save; **Remove comment** deletes that comment.
46
46
 
@@ -79,7 +79,7 @@ Refreshing or host file invalidation reloads the current snapshot (in comparison
79
79
 
80
80
  ## Attach feedback to the composer
81
81
 
82
- Choose a ready, unarchived conversation in this workspace, then select **Attach review to composer**. 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**.
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
83
 
84
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
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compn3rd/pi-web-code-review",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },