Skip to content

Comments

v3.2.0#36

Open
zendive wants to merge 24 commits intomasterfrom
next
Open

v3.2.0#36
zendive wants to merge 24 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 with sanitization, and option to hide unchanged sections.
    • Async runtime listeners and improved runtime messaging.
  • Removed Features

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

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

    • Consolidated build/packaging targets, enforced build prerequisites, versioned build artifacts, README and tooling updates.
  • 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 formatter into a new diffApi using jsondiffpatch; switch serialization to null-prototype objects and add stripDeepObjectPrototype with tests; update runtime typing to async listeners; bump manifests and several deps.

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/instructions.
Deno & build script
deno.json, build.ts
Exclude tmp/; switch esbuild import to npm:esbuild@0.27.3; update std pins; replace NODE_ENV with BUILD_MODE and expose logLevel.
Manifests & packaging
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; add dompurify.
Serialization & clone utilities
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.
Delta / diff API migration
src/api/deltaHtml/... (removed), src/api/diffApi.ts
Remove old HtmlFormatter and deltaHtml API; implement consolidated diffApi with jsondiffpatch (with-text-diffs); add buildDeltaElement, hideUnchanged, formatDeltaAsRFC6902; change object-hash to identifier-scan.
Runtime & proxy
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 reconnect; tighten proxy error handling (instanceof Error).
Console & toolkit
src/jsdiff-console.ts, src/api/toolkit.ts
Export TConsoleAPI type and cast cloneInto result; use utf8ToBytes and adjust noble import paths for hashing.
Stores & types
src/stores/compare.store.ts, src/@types/index.d.ts
Apply stripDeepObjectPrototype before diffing and when assigning; coerce stored lastError to string; move wrappedJSObject typing 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 JS and dynamic binding; consume new diffApi functions; migrate theming to CSS variables; minor CSS adjustments.
Formatter removal
src/api/deltaHtml/HtmlFormatter.ts, src/api/deltaHtml/api.ts
Deleted HTML delta formatter class and its wrapper API; functionality migrated into diffApi.ts.
Tests
tests/utils.test.ts
Add tests for stripDeepObjectPrototype verifying deep structural equality and null-prototype conversion.

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->>Make: validate REQUIRED_BINS
    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,

I stripped strange prototypes with gentle precision.
Diffs now bloom via jsondiffpatch delight,
zips wear manifest tags and everything's right.
A carrot for CI — cheers to the new build night.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'v3.2.0' is a version number, not a description of the changes. While it relates to the version bump in manifest files, it fails to convey the main changes (dependency updates, null-prototype object handling, HTML formatter removal). Use a descriptive title summarizing the primary change, such as 'v3.2.0: Update dependencies and minimize prototype comparison errors' or 'Bump version to 3.2.0 and refactor object serialization'.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 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.

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
+ get rid of `__v_skip`, coming from vue@3.5.28

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

zendive commented Feb 22, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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/diffApi.ts (1)

41-47: formatDeltaAsRFC6902 has an implicit Operation[] | string union return type.

On success it returns formatRFC6902(delta) (an Operation[]); on error it returns String(e). Without an explicit annotation callers can't statically distinguish the two.

♻️ Suggested explicit return type
-export function formatDeltaAsRFC6902(delta: Delta) {
+export function formatDeltaAsRFC6902(delta: Delta): ReturnType<typeof formatRFC6902> | string {
🤖 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 - 47, The function formatDeltaAsRFC6902
currently returns either formatRFC6902(delta) (Operation[]) or String(e)
(string) implicitly; add an explicit return type to make this union clear (e.g.,
annotate as Operation[] | string on formatDeltaAsRFC6902) and keep the existing
try/catch behavior (ensure you reference formatDeltaAsRFC6902, formatRFC6902 and
the Delta type when updating the signature); optionally improve the catch to
return a clearer error string but the primary change is adding the explicit
return type annotation.
🤖 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/stores/compare.store.ts`:
- Around line 24-30: The code calls markRaw on a frozen object which can throw
when Vue tries to define __v_skip; in castrateObject you should first create the
null-prototype object via stripDeepObjectPrototype(that), call markRaw on that
mutable object (so markRaw can set __v_skip), then freeze the result with
Object.freeze before returning; update castrateObject to avoid calling
markRaw(Object.freeze(...)) and instead markRaw(...) then Object.freeze(...)
while still returning the frozen, raw-marked object.

---

Duplicate comments:
In `@src/api/diffApi.ts`:
- Around line 67-68: The linter flags virtualEl.innerHTML assignment as unsafe
but the html variable is already sanitized via DOMPurify.sanitize; fix by making
the sanitization explicit and clear to static analysis: either assign the
sanitized result to a new variable (e.g., sanitizedHtml =
DOMPurify.sanitize(html || '')) and use sanitizedHtml when setting
virtualEl.innerHTML, or annotate/suppress the specific rule
(dom-content-modification / unsafe-html-content-assignment) immediately before
the innerHTML assignment so the analyzer recognizes the value is safe; reference
the DOMPurify.sanitize call and the virtualEl.innerHTML assignment when making
the change.

---

Nitpick comments:
In `@src/api/diffApi.ts`:
- Around line 41-47: The function formatDeltaAsRFC6902 currently returns either
formatRFC6902(delta) (Operation[]) or String(e) (string) implicitly; add an
explicit return type to make this union clear (e.g., annotate as Operation[] |
string on formatDeltaAsRFC6902) and keep the existing try/catch behavior (ensure
you reference formatDeltaAsRFC6902, formatRFC6902 and the Delta type when
updating the signature); optionally improve the catch to return a clearer error
string but the primary change is adding the explicit return type annotation.

Signed-off-by: 🕷️ <3756473+zendive@users.noreply.github.com>
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