Skip to content

v3.2.0#36

Open
zendive wants to merge 20 commits intomasterfrom
next
Open

v3.2.0#36
zendive wants to merge 20 commits intomasterfrom
next

Conversation

@zendive
Copy link
Owner

@zendive zendive commented Feb 10, 2026

  • Update dependencies and their usage.
  • Minimise object prototype comparison errors where possible - during clone and actual diffing - by using Object.create(null) instead of just {} for clean object instantiation.
  • Remove custom HTMLFormatter, which resolved some issue on its own.

Summary by CodeRabbit

  • New Features

    • RFC‑6902 export for diffs, HTML diff rendering, and option to hide unchanged sections.
    • Async runtime listeners and improved runtime messaging handling.
  • Removed Features

    • Legacy HTML delta formatter and automatic color‑scheme detection removed (theming unified via CSS variables).
  • Version Updates

    • Chrome extension: 3.1.1 → 3.2.0
    • Firefox extension: 3.1.0 → 3.2.0
  • Chores

    • Consolidated build/packaging targets, enforced build prerequisites, updated build tooling/config and dev dependencies.
  • Tests

    • Added tests for deep-object prototype stripping.

 - when object property is one of the `Object.prototype` `console.diff({},{valueOf:0})`
…rty name w/ changed value more easier e.g. `myProp:`
 - taken care by light-dark() CSS function
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactor build tooling and Makefile (BUILD_MODE, required binaries, manifest-derived ZIP names); migrate delta HTML rendering into a new diffApi using jsondiffpatch; switch serialization to null-prototype objects; add stripDeepObjectPrototype and tests; update runtime/messaging typings and theming to CSS variables; bump manifests and various dependency pins.

Changes

Cohort / File(s) Summary
Build system & docs
Makefile, README.md
Add REQUIRED_BINS checks; introduce BUILD_MODE, BUILD_SCRIPT, BUILD_DIR, OUTPUT_DIR; derive CHROME_MANIFEST_VERSION/FIREFOX_MANIFEST_VERSION and versioned ZIP names; add/adjust PHONY targets and build flows; update README build requirements and commands.
Deno & tooling
deno.json, build.ts
Add "exclude": ["tmp/"]; swap esbuild import to npm:esbuild@0.27.3; update std pins; replace NODE_ENV with BUILD_MODE and expose explicit logLevel in build config.
Packaging / manifests
manifest.chrome.json, manifest.firefox.json
Bump extension version fields to 3.2.0; manifests used to derive artifact filenames.
Dependencies
package.json
Update multiple devDependencies (noble hashes, types, pinia, typescript, vue); remove legacy diff-match-patch entries; add dompurify.
Serialization & cloning
src/api/clone.ts, src/api/cloneCatalog.ts, src/api/const.ts
Introduce ISerializableObject and null-prototype roots (Object.create(null)); export isArray, isObject, add stripDeepObjectPrototype; widen UniqueLookupCatalog.lookup generics; rename TAG_UNIQUE_SYMBOL param to value.
Delta / diff API
src/api/deltaHtml/* (removed), src/api/diffApi.ts
Remove old HtmlFormatter and deltaHtml API; implement consolidated diffApi using jsondiffpatch (with-text-diffs); add buildDeltaElement, hideUnchanged, formatDeltaAsRFC6902; replace previous object-hash logic with identifier-scan.
Runtime & messaging
src/api/useRuntime.ts, src/api/proxy.ts
Refactor runtime listener typing to TRuntimeMessageOptions and async listeners; support async local/global listeners and Firefox port reconnection logic; tighten proxy error handling with instanceof Error checks.
Utilities & console exposure
src/api/toolkit.ts, src/jsdiff-console.ts
Use utf8ToBytes for hashing and update noble import paths; export TConsoleAPI and cast cloneInto result for wrappedJSObject.jsdiff.
Stores & types
src/stores/compare.store.ts, src/@types/index.d.ts
Strip deep object prototypes before diffing; coerce restored lastError to string; move wrappedJSObject typing from Window interface to top-level global with TConsoleAPI import.
Views & theming
src/view/panel.vue, src/view/panel.header.vue, src/view/panel.search.vue
Remove runtime colour-scheme binding and related JS; consume buildDeltaElement/formatDeltaAsRFC6902; migrate theming to CSS variables; minor CSS adjustments.
Removed formatter module
src/api/deltaHtml/HtmlFormatter.ts, src/api/deltaHtml/api.ts
Deleted the HTML delta formatter class and its API wrappers; functionality migrated into diffApi.ts.
Tests
tests/utils.test.ts
Add tests for stripDeepObjectPrototype to assert null-prototype conversion and structural equality.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant Make as Makefile / Shell
    participant Deno as Deno Build Script
    participant Manifest as Manifest JSON
    participant Pack as Zipper / Output

    Dev->>Make: run make target (e.g. all, prod)
    Make->>Deno: invoke BUILD_SCRIPT with BUILD_MODE, BUILD_DIR, OUTPUT_DIR
    Deno->>Manifest: read manifest.chrome.json / manifest.firefox.json
    Manifest-->>Deno: return CHROME_MANIFEST_VERSION / FIREFOX_MANIFEST_VERSION
    Deno->>Pack: build artifacts into BUILD_DIR
    Deno->>Pack: create extension.chrome-<version>.zip & extension.firefox-<version>.zip
    Pack-->>Make: place zips in OUTPUT_DIR
    Make-->>Dev: artifacts ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through Make and bumped the version,

scrubbed prototypes with careful precision.
Diffs now sparkle via jsondiffpatch cheer,
zips named by manifests—releases near.
I munch a carrot and applaud this revision. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'v3.2.0' is vague and generic, using only a version number without describing the actual changes or objectives of the pull request. Consider using a more descriptive title that summarizes the main changes, such as 'v3.2.0: Update dependencies and minimize prototype comparison errors' or similar to convey the PR's intent.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch next

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/api/diffApi.ts`:
- Around line 7-23: The objectHash function uses generic keys in
OBJECT_ID_IN_ARRAY (notably "name" and "key") which can collide across array
items; update the implementation to avoid false matches by either removing
"name" and "key" from OBJECT_ID_IN_ARRAY or enhancing objectHash to only accept
"name"/"key" when their value is unique within the array: inside objectHash
(used by patcher) iterate the parent array to verify uniqueness of the candidate
id value before returning it, otherwise continue to the next candidate or fall
back to the index; reference OBJECT_ID_IN_ARRAY and objectHash/patcher when
making the change.
- Around line 46-59: The formatted HTML produced by format(delta, left) is being
inserted directly into tmpEl.innerHTML (html variable) which can trigger XSS
warnings; import DOMPurify and call DOMPurify.sanitize on the output of
format(delta, left) and use that sanitized string when assigning tmpEl.innerHTML
(i.e., replace the direct innerHTML assignment with tmpEl.innerHTML =
DOMPurify.sanitize(htmlOrUnsafe) and ensure any variable names like html, tmpEl,
delta, left remain consistent).

In `@src/api/useRuntime.ts`:
- Around line 19-27: allListeners is typed only for synchronous callbacks
(TRuntimeListener) but connect registers async callbacks (TRuntimeListenerAsync)
and callAllListeners invokes them without awaiting, causing unhandled
rejections; fix by widening the set type to
Set<TRuntimeListener|TRuntimeListenerAsync>, make callAllListeners async and
await listener invocations (e.g., await
Promise.allSettled([...allListeners].map(l => (l(e) as Promise<void>))), or
iterate with try/catch and await each), and update any callers (e.g., connect)
to await callAllListeners so async errors are handled rather than swallowed.
🧹 Nitpick comments (8)
src/@types/index.d.ts (1)

8-9: The type of jsdiff doesn't reflect the actual assigned value.

wrappedJSObject.jsdiff is typed as () => void, but consoleAPI (the value actually assigned in jsdiff-console.ts) is an object with diff, diffLeft, diffRight, and diffPush methods. Consider using a more accurate type (e.g., typeof consoleAPI or an equivalent interface) so consumers get proper autocompletion and type checking.

README.md (1)

150-151: Ambiguous formatting of build prerequisites.

Line 151 reads as a run-on list where make could be interpreted as a verb rather than a tool name. Consider using inline code formatting or a bullet list to clarify each tool.

Suggested fix
 ### Build requirements
-- Linux: [deno](https://docs.deno.com/runtime/getting_started/installation/) make jq zip tree
+- Linux: [deno](https://docs.deno.com/runtime/getting_started/installation/), `make`, `jq`, `zip`, `tree`
src/view/panel.vue (1)

99-100: --input-background-idle has identical light and dark values.

Both sides of the light-dark() call resolve to rgba(0, 0, 0, 0.05). If intentional, this is fine but the light-dark() wrapper is unnecessary. Same applies to --input-background-active where values also appear very similar.

src/api/diffApi.ts (1)

4-4: Use import type for type-only import.

ISerializableObject is only used as a type assertion on line 12, not as a value at runtime.

Suggested fix
-import { ISerializableObject } from './clone.ts';
+import type { ISerializableObject } from './clone.ts';
src/api/cloneCatalog.ts (1)

23-23: The <(Document | Element) & symbol> cast is unsound but functionally safe here.

This intersection type is impossible at runtime — a value is never simultaneously a DOM node and a symbol. The cast exists to work around TypeScript's contravariant parameter narrowing when calling a union of function types (tag could be either TAG_DOM_ELEMENT or TAG_UNIQUE_SYMBOL). It works in practice because each call site in clone.ts always pairs the correct tag with the correct key type.

A type-safe alternative would be to use overloads or a generic on lookup:

♻️ Optional: type-safe approach using overloads
-type TUniqueInstanceTag = typeof TAG_DOM_ELEMENT | typeof TAG_UNIQUE_SYMBOL;
+// No union type needed; use overloads instead

 export class UniqueLookupCatalog {
   `#records`: WeakMap<WeakKey, ICatalogUniqueRecord> = new WeakMap();
   `#index` = 0;

-  lookup(key: WeakKey, tag: TUniqueInstanceTag) {
+  lookup(key: Document | Element, tag: typeof TAG_DOM_ELEMENT): string;
+  lookup(key: symbol, tag: typeof TAG_UNIQUE_SYMBOL): string;
+  lookup(key: WeakKey, tag: typeof TAG_DOM_ELEMENT | typeof TAG_UNIQUE_SYMBOL) {
     let record = this.#records.get(key);
     if (record) {
       return record;
     }

     const id = index2Id(++this.#index);
-    record = tag(id, <(Document | Element) & symbol> key);
+    record = (tag as (id: string, value: any) => string)(id, key);
     this.#records.set(key, record);

     return record;
   }
 }
src/api/useRuntime.ts (1)

42-48: Firefox ports are created every 30s but never disconnected — potential resource leak.

Each setInterval tick calls getFirefoxPort(callAllListeners), which opens a new port and attaches the listener. The previous port is never explicitly disconnected. While Firefox MV3 ports have a limited lifespan and will eventually be garbage-collected, explicitly disconnecting the old port would be cleaner and avoid accumulating stale listeners during the overlap window.

♻️ Suggested: track and disconnect the old port
 if (typeof browser !== 'undefined') {
   // firefox
-  getFirefoxPort(callAllListeners);
+  let firefoxPort = getFirefoxPort(callAllListeners);

   setInterval(() => {
-    getFirefoxPort(callAllListeners);
+    firefoxPort.disconnect();
+    firefoxPort = getFirefoxPort(callAllListeners);
   }, BACKGROUND_SCRIPT_CONNECTION_INTERVAL);
 } else {
src/api/clone.ts (1)

311-314: The instanceof window.Object branch is redundant under normal circumstances.

Any non-null value where typeof === 'object' is already an Object instance. The instanceof window.Object branch only adds coverage for the unusual case where typeof returns something other than 'object' for an Object instance (cross-realm edge cases in extension content scripts). If that's intentional for Firefox content script compatibility, a brief comment would clarify the intent.

Makefile (1)

61-65: Use $(MAKE) instead of make for recursive invocations.

Hard-coded make breaks when the build is invoked via a differently named binary (e.g., gmake on BSD systems) or when flags need to propagate. $(MAKE) is the portable way to recurse.

♻️ Suggested fix
 all: prod
-	make tune2firefox
+	$(MAKE) tune2firefox
 	rm -rf $(FIREFOX_ZIP)
 	zip -r $(FIREFOX_ZIP) $(OUTPUT_DIR) ./manifest.json > /dev/null

-	make tune2chrome
+	$(MAKE) tune2chrome
 	rm -rf $(CHROME_ZIP)
 	zip -r $(CHROME_ZIP) $(OUTPUT_DIR) ./manifest.json > /dev/null
 	zip --delete $(CHROME_ZIP) "$(BUILD_DIR)firefox/*" > /dev/null

…uely distinguish objects in the array

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
@zendive zendive self-assigned this Feb 10, 2026
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
+ more readable

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/api/clone.ts (1)

248-266: ⚠️ Potential issue | 🟠 Major

window.Float16Array may not exist in older browser versions, causing instanceof to throw a TypeError.

In browsers released before Float16Array support was added (Chrome ≤134, Firefox ≤128, Safari ≤18.1), that instanceof window.Float16Array will throw TypeError: Right-hand side of instanceof is not callable rather than returning false.

Guard with an existence check:

Suggested fix
-    that instanceof window.Float16Array ||
+    (typeof window.Float16Array === 'function' && that instanceof window.Float16Array) ||
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/clone.ts` around lines 248 - 266, The isArray function currently uses
instanceof checks against several typed array constructors (e.g.,
window.Float16Array) which may be undefined in older browsers and cause a
TypeError; update each instanceof check to first verify the constructor exists
(e.g., typeof window.Float16Array !== 'undefined' or window.Float16Array !=
null) before using instanceof so the expression safely evaluates to false when
the constructor is not present; apply this pattern for any optional globals
referenced in isArray to avoid runtime exceptions.
🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@Makefile`:
- Around line 3-5: The Makefile currently runs REQUIRED_BINS := deno jq zip tree
and the $(foreach ...) dependency check at parse time which causes failures for
any target; change this to perform the check lazily by removing the immediate
$(foreach ...) expansion and instead run the shell existence check inside the
specific recipe(s) that need the tools (or wrap the check in a phony target like
check-deps that other targets depend on), and replace any parse-time shell
assignments (!=) used for JSON/jq extraction with simple = assignments and
invoke $(shell ...) inside the recipe or use a command-line pipeline in the
recipe so jq-based version extraction only runs when that target is executed;
reference symbols: REQUIRED_BINS, the $(foreach ...) dependency check, and any
!= assignments used for manifest/jq extraction.

In `@src/api/clone.ts`:
- Around line 248-266: The isArray function currently uses instanceof checks
against several typed array constructors (e.g., window.Float16Array) which may
be undefined in older browsers and cause a TypeError; update each instanceof
check to first verify the constructor exists (e.g., typeof window.Float16Array
!== 'undefined' or window.Float16Array != null) before using instanceof so the
expression safely evaluates to false when the constructor is not present; apply
this pattern for any optional globals referenced in isArray to avoid runtime
exceptions.

In `@src/api/diffApi.ts`:
- Around line 52-56: The call site uses cleanObjectPrototype which only strips
the prototype at the top level, so nested plain objects in delta and left may
retain prototypes and leak when formatHtml is called; implement a recursive
deepCleanObjectPrototype (or extend cleanObjectPrototype to walk nested plain
objects and remove/replace their prototype) and call that on delta and left
before passing them to formatHtml (keep the helper name referenced so you can
find the call site and replace cleanObjectPrototype(delta) /
cleanObjectPrototype(left) with deepCleanObjectPrototype(delta) /
deepCleanObjectPrototype(left)).

In `@src/api/useRuntime.ts`:
- Around line 19-22: allListeners is currently typed as Set<TRuntimeListener |
TRuntimeListenerAsync> but connect only ever adds TRuntimeListenerAsync; update
the declaration to Set<TRuntimeListenerAsync> (or, if you prefer to keep the
union, add a clarifying comment) and adjust usages: ensure connect, disconnect,
and the callAllListeners wrapper reference the narrowed type
(TRuntimeListenerAsync) and when passing a synchronous callback to browser APIs
use an explicit wrapper or cast in the call where callAllListeners is used.
Update references to allListeners, connect, and callAllListeners so the types
are consistent and the intent is documented if the union is retained.

In `@src/view/panel.vue`:
- Line 78: Replace the deprecated document.execCommand('copy') call with the
Clipboard API: use navigator.clipboard.writeText(...) to copy the text; in the
same function where document.execCommand('copy') appears (look for the literal
"document.execCommand('copy')" in panel.vue), call navigator.clipboard.writeText
with the string to copy and handle the returned Promise (then/catch or
async/await) to surface errors; because clipboard availability can be
inconsistent in DevTools contexts, add a safe fallback that attempts execCommand
only if navigator.clipboard is undefined or writeText rejects, and ensure you
log or surface any copy failures.
🧹 Nitpick comments (4)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@Makefile`:
- Around line 3-5: The Makefile currently runs REQUIRED_BINS := deno jq zip tree
and the $(foreach ...) dependency check at parse time which causes failures for
any target; change this to perform the check lazily by removing the immediate
$(foreach ...) expansion and instead run the shell existence check inside the
specific recipe(s) that need the tools (or wrap the check in a phony target like
check-deps that other targets depend on), and replace any parse-time shell
assignments (!=) used for JSON/jq extraction with simple = assignments and
invoke $(shell ...) inside the recipe or use a command-line pipeline in the
recipe so jq-based version extraction only runs when that target is executed;
reference symbols: REQUIRED_BINS, the $(foreach ...) dependency check, and any
!= assignments used for manifest/jq extraction.

In `@src/api/diffApi.ts`:
- Around line 52-56: The call site uses cleanObjectPrototype which only strips
the prototype at the top level, so nested plain objects in delta and left may
retain prototypes and leak when formatHtml is called; implement a recursive
deepCleanObjectPrototype (or extend cleanObjectPrototype to walk nested plain
objects and remove/replace their prototype) and call that on delta and left
before passing them to formatHtml (keep the helper name referenced so you can
find the call site and replace cleanObjectPrototype(delta) /
cleanObjectPrototype(left) with deepCleanObjectPrototype(delta) /
deepCleanObjectPrototype(left)).

In `@src/api/useRuntime.ts`:
- Around line 19-22: allListeners is currently typed as Set<TRuntimeListener |
TRuntimeListenerAsync> but connect only ever adds TRuntimeListenerAsync; update
the declaration to Set<TRuntimeListenerAsync> (or, if you prefer to keep the
union, add a clarifying comment) and adjust usages: ensure connect, disconnect,
and the callAllListeners wrapper reference the narrowed type
(TRuntimeListenerAsync) and when passing a synchronous callback to browser APIs
use an explicit wrapper or cast in the call where callAllListeners is used.
Update references to allListeners, connect, and callAllListeners so the types
are consistent and the intent is documented if the union is retained.

In `@src/view/panel.vue`:
- Line 78: Replace the deprecated document.execCommand('copy') call with the
Clipboard API: use navigator.clipboard.writeText(...) to copy the text; in the
same function where document.execCommand('copy') appears (look for the literal
"document.execCommand('copy')" in panel.vue), call navigator.clipboard.writeText
with the string to copy and handle the returned Promise (then/catch or
async/await) to surface errors; because clipboard availability can be
inconsistent in DevTools contexts, add a safe fallback that attempts execCommand
only if navigator.clipboard is undefined or writeText rejects, and ensure you
log or surface any copy failures.
Makefile (1)

3-5: REQUIRED_BINS check and jq-based version extraction run at Makefile parse time for all targets.

The $(foreach ...) on line 5 and the != shell assignments on lines 8/11 execute when the Makefile is parsed, not when a specific target is invoked. This means even make clean or make install will fail if jq, zip, or tree aren't yet installed, or if the manifest JSON files don't exist.

If this is intentional (all tools must be present before any work), it's fine. Otherwise, consider lazy evaluation (e.g., using = with $(shell ...) inside recipe lines, or moving checks into individual targets).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 3 - 5, The Makefile currently runs REQUIRED_BINS :=
deno jq zip tree and the $(foreach ...) dependency check at parse time which
causes failures for any target; change this to perform the check lazily by
removing the immediate $(foreach ...) expansion and instead run the shell
existence check inside the specific recipe(s) that need the tools (or wrap the
check in a phony target like check-deps that other targets depend on), and
replace any parse-time shell assignments (!=) used for JSON/jq extraction with
simple = assignments and invoke $(shell ...) inside the recipe or use a
command-line pipeline in the recipe so jq-based version extraction only runs
when that target is executed; reference symbols: REQUIRED_BINS, the $(foreach
...) dependency check, and any != assignments used for manifest/jq extraction.
src/api/diffApi.ts (1)

52-56: Note: cleanObjectPrototype only strips the prototype at the top level.

If delta or left contain nested plain objects with prototypes, those remain untouched. This is likely fine since objects coming from customClone already use Object.create(null) at each serialization level, but worth noting if formatHtml is ever called with objects not produced by the clone pipeline.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/diffApi.ts` around lines 52 - 56, The call site uses
cleanObjectPrototype which only strips the prototype at the top level, so nested
plain objects in delta and left may retain prototypes and leak when formatHtml
is called; implement a recursive deepCleanObjectPrototype (or extend
cleanObjectPrototype to walk nested plain objects and remove/replace their
prototype) and call that on delta and left before passing them to formatHtml
(keep the helper name referenced so you can find the call site and replace
cleanObjectPrototype(delta) / cleanObjectPrototype(left) with
deepCleanObjectPrototype(delta) / deepCleanObjectPrototype(left)).
src/view/panel.vue (1)

78-78: document.execCommand('copy') is deprecated.

The Clipboard API (navigator.clipboard.writeText()) is the modern replacement. That said, in a DevTools extension context, clipboard API availability can be inconsistent, so this may be an intentional choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/view/panel.vue` at line 78, Replace the deprecated
document.execCommand('copy') call with the Clipboard API: use
navigator.clipboard.writeText(...) to copy the text; in the same function where
document.execCommand('copy') appears (look for the literal
"document.execCommand('copy')" in panel.vue), call navigator.clipboard.writeText
with the string to copy and handle the returned Promise (then/catch or
async/await) to surface errors; because clipboard availability can be
inconsistent in DevTools contexts, add a safe fallback that attempts execCommand
only if navigator.clipboard is undefined or writeText rejects, and ensure you
log or surface any copy failures.
src/api/useRuntime.ts (1)

19-22: Minor type mismatch: connect only accepts TRuntimeListenerAsync but allListeners includes TRuntimeListener.

allListeners is typed as Set<TRuntimeListener | TRuntimeListenerAsync> but only TRuntimeListenerAsync listeners are ever added via connect(). The TRuntimeListener variant in the union is only there because callAllListeners (which is sync) is used as a direct callback for browser APIs. This works, but the union type on the set is a bit misleading—consider a clarifying comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/useRuntime.ts` around lines 19 - 22, allListeners is currently typed
as Set<TRuntimeListener | TRuntimeListenerAsync> but connect only ever adds
TRuntimeListenerAsync; update the declaration to Set<TRuntimeListenerAsync> (or,
if you prefer to keep the union, add a clarifying comment) and adjust usages:
ensure connect, disconnect, and the callAllListeners wrapper reference the
narrowed type (TRuntimeListenerAsync) and when passing a synchronous callback to
browser APIs use an explicit wrapper or cast in the call where callAllListeners
is used. Update references to allListeners, connect, and callAllListeners so the
types are consistent and the intent is documented if the union is retained.

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/api/clone.ts (1)

311-313: isObject returns true for Map, Set, Date, RegExp, and DOM nodes — the name and type predicate are broader than "plain object".

Within recursiveClone the call-ordering protects against false positives, but as an exported API the signature that is ISerializableObject with no exclusion of well-known non-plain types is easy to misuse (see cleanObjectPrototype below).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/clone.ts` around lines 311 - 313, isObject is too permissive: it
returns true for Map/Set/Date/RegExp/DOM nodes but is exported as
"ISerializableObject", which risks misuse in callers like recursiveClone and
cleanObjectPrototype; change the implementation of isObject to detect plain
objects only (e.g., ensure that that !== null && typeof that === 'object' &&
Object.prototype.toString.call(that) === '[object Object]' OR
Object.getPrototypeOf(that) === Object.prototype || Object.getPrototypeOf(that)
=== null) so Maps/Sets/Dates/Regexps/DOM nodes are excluded, and keep the type
predicate as ISerializableObject (or adjust the name if you prefer) to reflect
the narrower check; update any usages that rely on the old broad behavior
(notably recursiveClone and cleanObjectPrototype) if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/clone.ts`:
- Around line 323-329: cleanObjectPrototype currently treats any non-null object
(per isObject) as a plain object and therefore discards contents of
Map/Set/Date/class instances; change the guard so only true plain objects are
converted by checking the object's direct prototype via
Object.getPrototypeOf(unk) and only proceed when proto === Object.prototype or
proto === null (preserving prototype-less objects), otherwise return unk
unchanged; update the logic around cleanObjectPrototype (and reuse
isArray/isObject checks) to avoid converting Map/Set/Date or class instances.

---

Nitpick comments:
In `@src/api/clone.ts`:
- Around line 311-313: isObject is too permissive: it returns true for
Map/Set/Date/RegExp/DOM nodes but is exported as "ISerializableObject", which
risks misuse in callers like recursiveClone and cleanObjectPrototype; change the
implementation of isObject to detect plain objects only (e.g., ensure that that
!== null && typeof that === 'object' && Object.prototype.toString.call(that) ===
'[object Object]' OR Object.getPrototypeOf(that) === Object.prototype ||
Object.getPrototypeOf(that) === null) so Maps/Sets/Dates/Regexps/DOM nodes are
excluded, and keep the type predicate as ISerializableObject (or adjust the name
if you prefer) to reflect the narrower check; update any usages that rely on the
old broad behavior (notably recursiveClone and cleanObjectPrototype) if
necessary.

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/utils.test.ts (1)

16-19: Consider adding an assertion that trimmed.arr is still an Array.

The function intentionally preserves arrays (only plain objects lose their prototype). A toBeInstanceOf(Array) check on trimmed.arr would document this design contract and catch any future regression where arrays are also stripped:

♻️ Optional addition
     expect(trimmed).not.toBeInstanceOf(Object);
     expect(trimmed.obj).not.toBeInstanceOf(Object);
+    expect(trimmed.arr).toBeInstanceOf(Array);
     expect(trimmed.arr[0]).not.toBeInstanceOf(Object);
     expect(trimmed.arr[0].obj).not.toBeInstanceOf(Object);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils.test.ts` around lines 16 - 19, Add an assertion to verify arrays
are preserved by the trimming behavior: in tests/utils.test.ts add a check that
trimmed.arr is still an Array (e.g. expect(trimmed.arr).toBeInstanceOf(Array))
alongside the existing assertions (trimmed, trimmed.obj, trimmed.arr[0],
trimmed.arr[0].obj) to document and guard the contract that arrays keep their
prototype.
src/api/diffApi.ts (1)

41-43: formatDeltaAsRFC6902 is unguarded unlike buildDeltaElement.

buildDeltaElement wraps formatter calls in a try/catch, but formatDeltaAsRFC6902 lets errors from formatRFC6902 propagate uncaught to callers. If the caller isn't prepared for a throw, this could produce an unhandled rejection. Consider either documenting that callers must handle errors, or wrapping consistently:

♻️ Suggested hardening
 export function formatDeltaAsRFC6902(delta: Delta) {
-  return formatRFC6902(delta);
+  try {
+    return formatRFC6902(delta);
+  } catch (e) {
+    console.error('formatDeltaAsRFC6902', e);
+    return undefined;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/diffApi.ts` around lines 41 - 43, formatDeltaAsRFC6902 currently
calls formatRFC6902 without protection and can propagate throws; wrap the call
in a try/catch (mirroring buildDeltaElement) so failures are handled
consistently—call formatRFC6902(delta) inside try, return the result on success,
and in catch either throw a new Error with contextual message that includes the
original error (or return a safe sentinel like null if your codebase prefers) so
callers won't receive unhandled exceptions; reference function names
formatDeltaAsRFC6902, formatRFC6902, and buildDeltaElement to locate and align
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/clone.ts`:
- Around line 327-350: stripDeepObjectPrototype currently mutates arrays
in-place while creating a fresh prototype-less object for object inputs, causing
asymmetric behavior and potential state corruption; update the array branch in
stripDeepObjectPrototype to allocate and return a new array (e.g., build a new
array and push or map each element through stripDeepObjectPrototype) instead of
assigning into unk[n], ensuring you preserve typings and still recursively
process elements so that arrays are not mutated in-place while objects continue
to return Object.create(null).

In `@src/api/diffApi.ts`:
- Around line 53-56: stripDeepObjectPrototype currently mutates arrays in-place
causing callers like the use in formatHtml(stripDeepObjectPrototype(delta), ...)
to unexpectedly alter referenced arrays; modify stripDeepObjectPrototype (the
array branch) to allocate and return a new array instead of rewriting the
original: iterate/map over each element, recursively call
stripDeepObjectPrototype on elements, push results into a new array, and return
that new array (mirror how the object branch creates Object.create(null));
ensure no in-place assignments occur so callers' original array references
remain unchanged.

In `@tests/utils.test.ts`:
- Around line 13-15: The test currently calls stripDeepObjectPrototype(obj) and
then compares trimmed to obj, but stripDeepObjectPrototype mutates arrays
in-place so obj is already changed; before calling stripDeepObjectPrototype
capture the original expected structure (e.g., deep clone or structured clone of
obj) and then call stripDeepObjectPrototype, and finally assert
expect(trimmed).toEqual(expected) instead of comparing against the mutated obj;
reference stripDeepObjectPrototype, trimmed and obj to locate the test and
replace the assertion.

---

Nitpick comments:
In `@src/api/diffApi.ts`:
- Around line 41-43: formatDeltaAsRFC6902 currently calls formatRFC6902 without
protection and can propagate throws; wrap the call in a try/catch (mirroring
buildDeltaElement) so failures are handled consistently—call
formatRFC6902(delta) inside try, return the result on success, and in catch
either throw a new Error with contextual message that includes the original
error (or return a safe sentinel like null if your codebase prefers) so callers
won't receive unhandled exceptions; reference function names
formatDeltaAsRFC6902, formatRFC6902, and buildDeltaElement to locate and align
behavior.

In `@tests/utils.test.ts`:
- Around line 16-19: Add an assertion to verify arrays are preserved by the
trimming behavior: in tests/utils.test.ts add a check that trimmed.arr is still
an Array (e.g. expect(trimmed.arr).toBeInstanceOf(Array)) alongside the existing
assertions (trimmed, trimmed.obj, trimmed.arr[0], trimmed.arr[0].obj) to
document and guard the contract that arrays keep their prototype.

Comment on lines +327 to +350
export function stripDeepObjectPrototype<T>(unk: T) {
if (Object.prototype.toString.call(unk) === '[object Array]') {
// @ts-expect-error in 2026, typescript doesn't know yet
for (let n = 0; n < unk.length; n++) {
// @ts-expect-error in 2026, typescript doesn't know yet
unk[n] = stripDeepObjectPrototype(unk[n]);
}

return unk;
}

if (Object.prototype.toString.call(unk) === '[object Object]') {
const rv = Object.create(null);
const obj = <ISerializableObject> unk;

for (const key of Reflect.ownKeys(obj)) {
rv[key] = stripDeepObjectPrototype(obj[key]);
}

return <T> rv;
}

return unk;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

stripDeepObjectPrototype mutates arrays in-place — asymmetric with the object branch.

The array branch (lines 328–336) rewrites unk[n] on the original array, returning the same reference. The object branch (lines 338–347) allocates a fresh Object.create(null). This inconsistency means callers passing an object whose properties hold array values will have those arrays silently mutated, corrupting any other holder of those references (e.g., reactive store state). See the companion comment in src/api/diffApi.ts (lines 53–56) for the direct impact. The fix is to return a new array from the array branch instead of rewriting in-place (proposed diff in the diffApi.ts comment).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/clone.ts` around lines 327 - 350, stripDeepObjectPrototype currently
mutates arrays in-place while creating a fresh prototype-less object for object
inputs, causing asymmetric behavior and potential state corruption; update the
array branch in stripDeepObjectPrototype to allocate and return a new array
(e.g., build a new array and push or map each element through
stripDeepObjectPrototype) instead of assigning into unk[n], ensuring you
preserve typings and still recursively process elements so that arrays are not
mutated in-place while objects continue to return Object.create(null).

Comment on lines +53 to +56
html = formatHtml(
stripDeepObjectPrototype(delta),
stripDeepObjectPrototype(left),
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

stripDeepObjectPrototype silently mutates any arrays nested within delta and left.

stripDeepObjectPrototype for the array branch rewrites elements in-place on the original array reference rather than copying it (see clone.ts lines 329–332). A jsondiffpatch delta is a plain object whose property values are typically arrays such as [oldVal, newVal]. Calling stripDeepObjectPrototype(delta) creates a new null-prototype object for the top level, but the inner [oldVal, newVal] arrays are mutated in-place. Any other holder of those array references — e.g., a Pinia store, a cached delta, or the original left object's own arrays — will silently see their elements replaced with null-prototype objects after this call.

The safest fix is to change the array branch in stripDeepObjectPrototype to return a new array instead of mutating in-place, consistent with how the object branch already allocates a fresh Object.create(null):

🛡️ Proposed fix in src/api/clone.ts
 export function stripDeepObjectPrototype<T>(unk: T) {
   if (Object.prototype.toString.call(unk) === '[object Array]') {
-    // `@ts-expect-error` in 2026, typescript doesn't know yet
-    for (let n = 0; n < unk.length; n++) {
-      // `@ts-expect-error` in 2026, typescript doesn't know yet
-      unk[n] = stripDeepObjectPrototype(unk[n]);
-    }
-
-    return unk;
+    // `@ts-expect-error` in 2026, typescript doesn't know yet
+    const rv: unknown[] = (unk as unknown[]).map((item) =>
+      stripDeepObjectPrototype(item)
+    );
+    return rv as unknown as T;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/diffApi.ts` around lines 53 - 56, stripDeepObjectPrototype currently
mutates arrays in-place causing callers like the use in
formatHtml(stripDeepObjectPrototype(delta), ...) to unexpectedly alter
referenced arrays; modify stripDeepObjectPrototype (the array branch) to
allocate and return a new array instead of rewriting the original: iterate/map
over each element, recursively call stripDeepObjectPrototype on elements, push
results into a new array, and return that new array (mirror how the object
branch creates Object.create(null)); ensure no in-place assignments occur so
callers' original array references remain unchanged.

Comment on lines +13 to +15
const trimmed = stripDeepObjectPrototype(obj);

expect(trimmed).toEqual(obj);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

toEqual(obj) compares against the already-mutated original.

Because the array branch of stripDeepObjectPrototype rewrites elements in-place, obj.arr is the same array reference as trimmed.arr, and obj.arr[0] has already been replaced with a null-prototype object by the time Line 15 runs. The toEqual assertion therefore compares trimmed against a partially mutated obj, not the original value — it succeeds trivially but does not validate that the output matches the original structure.

Capture the expected shape before the call to make the assertion meaningful:

♻️ Suggested fix
+    const expected = {
+      obj: {},
+      arr: [{ obj: {} }],
+    };
     const trimmed = stripDeepObjectPrototype(obj);
 
-    expect(trimmed).toEqual(obj);
+    expect(trimmed).toEqual(expected);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const trimmed = stripDeepObjectPrototype(obj);
expect(trimmed).toEqual(obj);
const expected = {
obj: {},
arr: [{ obj: {} }],
};
const trimmed = stripDeepObjectPrototype(obj);
expect(trimmed).toEqual(expected);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/utils.test.ts` around lines 13 - 15, The test currently calls
stripDeepObjectPrototype(obj) and then compares trimmed to obj, but
stripDeepObjectPrototype mutates arrays in-place so obj is already changed;
before calling stripDeepObjectPrototype capture the original expected structure
(e.g., deep clone or structured clone of obj) and then call
stripDeepObjectPrototype, and finally assert expect(trimmed).toEqual(expected)
instead of comparing against the mutated obj; reference
stripDeepObjectPrototype, trimmed and obj to locate the test and replace the
assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant